OAuth2 Interoperability

Hi Mythz,

A question about ServiceStack and interoperability with 3rd party clients/services.

Does ServiceStack have anything to help ServiceStack services that use the AuthFeature to interop with clients or services that use OAuth authz on the wire? (i.e. Bearer tokens)
In other words, suppose we have a 3rd party that calls our ServiceStack API, and they send requests with a Bearer token in the Authorization HTTP header. They obtained the access_token of the Bearer token from an OAuth server that we support with our own CustomIAuthProvider configured in the AuthFeature of our service.

Is there a built in RequestFilter (or something in the request pipeline) that looks for and extracts the access_token from the Authorization header, validates it, and creates an equivalent Session from it?
(Probably in much the same way that the AuthFeature populates a session from a ss_id in the cookie?)
So that, we can reuse the ServiceStack authz assertion framework (i.e. [Authenticate] and [RequiesAnyRoles] etc).

If there is nothing out there, do you think that would of sufficient value to support in ServiceStack at some point?

Reluctantly, I am probably going to have to build something if it is not already there now. And would probably base that implementation on how the AuthFeature creates sessions once a IAuthProvider has done the authentication. Any directions from you on how to do that would be very helpful.

We don’t have anything specific for OAuth, but have a look at IAuthWithRequest Auth Providers which authenticates on-the-fly with each request by setting up a User Session that only lasts for the scope of the request. See the v4.0.60 Release Notes for a bit of info on how they work and which Auth Providers use them, including the newer API Key and JWT Auth Providers.

Nice.

I might be confused a little here, but since the ApiKeyAuthProvider creates request-scoped-session when a request comes in with an ?apikey=xxx in it. Isn’t the best course of action for me just to create a OAuthBearerAuthProvider, just like the ApiKeyAuthProvider then, and register that in my AuthFeature?

After all, all OAuthBearerAuthProvider wants to do is create a request-scoped-session for the user that is identified in the access_token?

Yeah if you want to authenticate with the request implementing an IAuthWithRequest AuthProvider would be my approach.

Ah, the bit I am confused about, is the double sided nature of an AuthProvider.

Let me ask a different question.

All I want to do is plug in something into my service, that handles any request that has a BearerToken containing an OAuth2 access_token. If this component can understand the access_token (i.e. can validate and decrypt it), then all it needs to do is create a request-scoped-session for that user.

What do you suggest this component should be, and where should it plugin?

Another option is adding a Request Filter that processes the access_token and populates the User Session in IRequest.Items[Keywords.Session] to authenticate the request.

Have a look at how JwtAuthProvider populates the Session, I’ve extracted out the key parts:

var session = SessionFeature.CreateNewSession(req, sessionId);
//... Populates User Session,  
HostContext.AppHost.OnSessionFilter(session, sessionId); // Call any filters
req.Items[Keywords.Session] = session;

You may also want to populate the Users Session from User data stored in the IAuthRepository in which case you can resolve it with req.TryResolve<IAuthRepository>(), although this is what creating an Auth Provider does for you, so it’s something I’d look at.

Is it safe to do this: SessionFeature.CreateNewSession(req, req.GetOrCreateSessionId()):

Yeah just as long as you haven’t started writing to the response yet in-case it needs to adds Cookies when they don’t exist.

OK, we are talking about this being a GlobalRequestFilter here, right?

So I can add it by creating my own IPlugin, and adding it to the appHost.GlobalRequestFilters?

Yeah if you want to use a Request Filter instead of an IAuthWithRequest Auth Provider.

Well, I tried to create a IAuthWithRequest AuthProvider of my own, but I just don’t get what to do in either Authenticate or PreAuthenticate methods. even looking at the others that are there. Its very confusing.

I thought what I was supposed to do was in PreAuthenticate, extract my BearerToken from the request and Post an Authenticate request to my provider. But then what do I do in my Authenticate method? Or do I have it the other way around?

Can your refer to the impl for ApiKeyAuthProvider, it’s pretty self-contained and I expect the flow to be similar, i.e. the PreAuthenticate() method calls the AuthenticateService which calls the Authenticate() method which populates the User Session.

Mythz,

Thanks for your patience with me on this.
I followed your guidance, and created my own OAuth2AccessTokenAuthProvider, with many of the same patterns in ApiKeyAuthProvider.

There is just one wrinkle left, that I would ask about for your guidance.

In my specific use case, a client is only ever going to send a request with an access_token in it, and the client would normally have to remember that access_token themselves, and send it again with any subsequent requests. You could say it is session-less. Or the session is defined in the client. Either way, the service would not have any pre-knowledge of the access_token before the client presents one. That is because the access_token was obtained directly from an OAuthZServer elsewhere in our network. Presenting a valid access_token that is signed, not expired is enough to authenticate the user.

So when an access_token arrives, my new provider does the PreAuthenticate, which posts an Authenticate. The token is verified, and a session is setup with whatever I can get out of the access_token. (which is simply an ID of the user, and the roles they have)

Do I understand it correctly, that there would never ever be any user in the IUserAuthRepository associated with the access_token in the IUserAuthRepository? Should I even be setting up a session at all?

[The ApiKeyAuthProvider assumes the user is to be found in the IUserAuthRepository and setups up a new session with that user]

I am not sure I should be doing either. What is the recommended practice here?
The way I understand it at this point, the usual way to get a user into the IUserAuthRepository is when a client calls the ‘/auth/someprovider’ endpoint, and then the user is created in the IUserAuthRepository at that point,and session is setup so the client can continue along with the session?
We are neither doing that in this scenario, nor plan to ever call the /auth/myprovider endpoint with an access_token.
Perhaps I actually don’t actually need an AuthProvider at all then, maybe a GlobalRequestFilter that looks for the access_token and somehow authenticates the request we be all that is necessary?

Honestly I would use a JWT Token here, Gistlyn does something similar to what you’re doing, it’s completely stateless and has no back-end db, user repository or session storage. It authenticates with Github OAuth provider and stores the access token in the JWT Cookie using a CreatePayloadFilter then extracts it out and populates it in the Users Session with a PopulateSessionFilter. Here’s what Gistlyn’s AuthFeature looks like:

appHost.Plugins.Add(new AuthFeature(() => new AuthUserSession(),
    new IAuthProvider[] {
        new GithubAuthProvider(appHost.AppSettings),
        new JwtAuthProvider(appHost.AppSettings)
        {
            CreatePayloadFilter = (payload, session) =>
            {
                var githubAuth = session.ProviderOAuthAccess.Safe()
                    .FirstOrDefault(x => x.Provider == "github");
                payload["ats"] = githubAuth != null ? githubAuth.AccessTokenSecret : null;
            },

            PopulateSessionFilter = (session, obj, req) => 
            {
                session.ProviderOAuthAccess = new List<IAuthTokens>
                {
                    new AuthTokens {Provider="github", AccessTokenSecret = obj["ats"]}
                };
            } 
        },
    }));

It stores the access token in the Users ProviderOAuthAccess collection, the same place it would be if they just authenticated with Github OAuth Provider.

We use the Github OAuth provider to initially authenticate with Github which is then stored in an In Memory CacheClient then after the user is redirected back we immediately convert it to a JWT Token with:

fetch("/session-to-token", { method:"POST", credentials:"include" });

At which point the current Users Session is converted into a JWT Token cookie and removed from the Server, so it’s now stored on the client and completely stateless.

I have to be honest with you. This new information does not help me make a decision. Not that your suggestion is not of value at all. I am finding it hard to digest though, because I have an incomplete mental model of how things in the auth framework are supposed to work. Once I have that I can decide how to proceed.

The wiki page on JWT Provider gave me some great understanding - good page. The idea that we have a centralized AuthZ server for all our services is exactly the scenario we have been running to this point, except its based on OAuth2 bearer token, not JWT bearer token. Now we can change over to JWT for our own services, but we still need to maintain the interop with OAuth2 bearer token from external partners (that wont change in the short term).

The piece I am missing first, is understanding conceptually what the auth framework is expecting to happen once any AuthProvider.Authenticate (via PreAuthenticate method) is called, when things go well. And how the IUserAuthRepository and ISession are expected to participate during the current request, in terms of the what happens when the [Authenticate] and [RequireAnyRoles] authz framework attributes execute.

Once I get that full picture, I am certain your suggestion will make more sense to me.
Are those interactions explained anywhere? I have totally missed them.

(right now, I am debugging the [RequiresAnyRoleAttribute] with our custom AuthProvider to try and figure out what I am missing wrt the IAuthRepository and ISession.)

The Auth Repository is where the User Auth information is persisted, e.g. an RDBMS (OrmLiteAuthRepository).

When the User is Authenticated, the User Info from the IAuthRepository is used to populate the Users Session. Sometimes the IAuthRepository contains the Users password and is what’s used to authenticate the user, e.g with:

  • CredentialsAuthProvider
  • BasicAuthProvider
  • DigestAuthProvider

Other times, e.g. with OAuth Providers it just persists the User Info collected when they’ve authenticated with the OAuth Provider, which is merged with along with any other OAuth Provider the user has authenticated with. This information is used to populate the Users Session when the user has successfully registered with any AuthProvider.

The purpose of Authenticating is to populate an Authenticated Typed Users Session which is what’s stored in the Caching Provider that’s referenced by the ss-id/ss-pid Cookies. All runtime Auth Validators, e.g [Authenticate], [RequireAnyRoles] uses the ss-id/pid on each Request (primarily from Request Cookies but also available from HTTP Headers, QueryString or IHasSessionId Request DTOs) to first retrieve the Users Session from the registered Caching Provider which [Authenticate] (and all other auth attrs) uses to verify the User has an Authenticated Session. The [RequireAnyRoles] or permission attrs is validated using the HasRoles/HasPermission methods on the Users Typed Session (e.g. AuthUserSession) to validate whether the User has the specified role/permission, unless the IAuthRepository implements IManageRoles in which case it looks up whether the User has the specified role in the Auth Repository - which is the only time the Auth Repository is used outside of Authentication.

How JWT’s enable Stateless Authentication

I recommend JWT’s and have started using them a lot because it can greatly simplify Authentication for Services that need it since it essentially encapsulates the Users Session (partially with most essential info) in a single Token (i.e. JSON Web Token) which is an alternative to populating the Users Session from the ss-id/ss-pid Cookies as it’s instead populated entirely from the JWT Token (so doesn’t require Services to have either a Caching Provider to store Sessions or a IAuthRepository to persist User Info). Authentication is taken care of by the JWT Signature which verifies that the token was created with someone with access to the Auth Key (i.e. the Service issuing JWT Tokens) which is how you can trust the info in the JWT Token.

I effectively think of JWT’s as a format for wrapping an Authenticated Users Session, this is ideal in Micro Services as there only needs to be a single “Auth” Service that needs to have Auth Providers / Auth Repositories and Caching Provider configured which authenticates the User, all other Micro Services will only need to validate the token which it can configure with just:

Plugins.Add(new AuthFeature(() => new AuthUserSession(),
    new [] { new JwtAuthProviderReader(AppSettings) }));

Without needing to any have access to any other dependencies.

If the client Authenticates by sending a Authenticate Request DTO (e.g. Username/Password credentials) then the JWT for the Users Session is returned in AuthenticateResponse which if JwtAuthProvider is registered will return the JWT Token in AuthenticateResponse.BearerToken, if the Authenticate request is sent with UseTokenCookie=true then the JWT Token is also set the in the ss-tok Cookie which automatically configures the Service Client to send authenticated requests using JWT on subsequent requests.

If Authenticating via OAuth then the client isn’t using the Authenticate Request DTO and can instead convert their Authenticated User Session into a JWT Token with a call to ConvertSessionToToken Service, e.g:

fetch("/session-to-token", { method:"POST", credentials:"include" });

Manually Creating JWT Auth Tokens

There’s a lot of flexibility in JWT’s which can also be created manually using just the registered JwtAuthProvider which is handy if you have specific Auth Requirements or would like to impersonate a user:

var jwtProvider = (JwtAuthProviderReader)
    AuthenticateService.GetAuthProvider(JwtAuthProvider.Name);

var header = JwtAuthProvider.CreateJwtHeader(jwtProvider.HashAlgorithm);
var payload = JwtAuthProvider.CreateJwtPayload(new AuthUserSession
{
    UserAuthId = "1",
    DisplayName = "Test",
    Email = "test@email.com"
}, "manual-jwt", TimeSpan.FromDays(14));

var token = JwtAuthProvider.CreateJwtBearerToken(header, payload,
    data => JwtAuthProviderReader.HmacAlgorithms["HS256"](fallbackAuthKey, data));

Which you can configure in your Service Clients with:

var client = new JsonServiceClient(BaseUrl) { BearerToken = token };

So if you’re thinking about Stateless Sessions I’d recommend that you consider encapsulating them in JWT’s since it’s a popular industry standard format and there’s already a lot of support for them in ServiceStack and Service Clients.

Yep. Totally on board with JWT’s as a session less way for our services to talk to our clients internally. Externally however, our partners are using OAuth2 access tokens.

You don’t need to sell me the benefits of session-less exchanges, we’ve already been running with those benefits across all our services. However, now that we have gone to using the AuthFeature We still need to support our legacy, and we need a provider that is session-less but supports OAuth ‘access_token’

This is what I would like to do.

  1. Rename our custom OAuthAccessTokenAuthProviderto OAuthAccessTokenAuthProviderReaderso the intent is clear, on what it does, in line with the JWTAuthProviderReader.
  2. Prevent our provider sending any session cookies to a client. They will never be needed, and we don’t want our clients using them to bypass the (pre)authentication of the access_token in the Bearer token each and every time they call back (they must always present the access_token).
  3. Then, we can upgrade our AuthZServer to issue JWT tokens (as well as supporting access_token at a dedicated endpoint), and we use JWT’s and sessions internally in our clients and services. We can offer JWT to our external partners too.

On point 2, how would we prevent session cookies being sent back to the client, when our OAuthAccessTokenAuthProvider is called at PreAuthenticate (then Authenticate)? Given that we still need the [Authenticate]and [RequireAnyRoles] to still work for the current request?

I see that the JwtAuthProviderReader does not call Authenticate but it does set the Session using relevant lines like these: (JWTAuthProviderReader)

var sessionId = jwtPayload.GetValue("jid", SessionExtensions.CreateRandomSessionId);
var session = SessionFeature.CreateNewSession(req, sessionId);

session.PopulateFromMap(jwtPayload);

if (PopulateSessionFilter != null)
    PopulateSessionFilter(session, jwtPayload, req);

HostContext.AppHost.OnSessionFilter(session, sessionId);
...
req.Items[Keywords.Session] = session;

Is that how to stop the cookies going to the client?

Yeah I was trying to say you could do both with the approach Gistlyn does which embeds the Github OAuth token in JWT which is created after OAuth redirect back after a successful login. But yeah you can also take care of populating the User Session with a custom impl.

No, the Session Cookies are added by the SessionFeature which can be disabled with:

SetConfig(new HostConfig {
    AllowSessionCookies = false
});

This seems to be a global thing that will affect all providers. I want to avoid side-effects for the services that don’t opt to use my authprovider.

Is there a more local alternative that I can do just when my authprovider is used? i.e. something I can do or setup in the PreAuthenticate of my AuthProvider, but only if my AuthProvider is used, otherwise don’t affect sessions at all. If in the future we use multiple AuthProviders and some use session, but mine doesn’t I don’t want to cause issues with them.

(I still need [Authenicate] and [RequiresAnyRoles] to work during the current request).

Over here: v4.0.60 Release Notes it says

By default all IAuthWithRequest Auth Providers no longer persist the Users Session in the Cache.

Doesn’t that mean that they don’t send session cookies as well? or is that another thing entirely?

I was expecting that if I implemented an MyCustomAuthProvider : AuthProvider, IAuthWithRequest just like the JWTAuthProviderReader that I would not get any session stored and no session cookies. Hence the confusion.