17

I am building a web api using ASP.NET WebApi 2 using claims authentication, and my users can have very large number of claims. With a large number of claims the bearer token grows very large quickly, so I am attempting to find a way of returning a much shorter bearer token.

SO far I have discovered that I can provide a IAuthenticationTokenProvider to the OAuth options OAuthAuthorizationServerOptions.AccessTokenProvider property:

OAuthOptions = new OAuthAuthorizationServerOptions
{
    TokenEndpointPath = new PathString("/Token"),
    Provider = new ApplicationOAuthProvider(PublicClientId),
    AccessTokenExpireTimeSpan = TimeSpan.FromHours(12),
    AccessTokenProvider = new GuidProvider() // <-- here
};

And this gives me a chance to intercept the AuthenticationTicket and stash it away, replacing it with something simpler - in my example below a hashed guid. (Note: At the moment this class simply holds a ConcurrentDictionary<string,AuthenticationTicket> with my sessions - in a real-world example I intend to store the sessions in some persistent storage)

public class GuidProvider : IAuthenticationTokenProvider
{
    private static ConcurrentDictionary<string, AuthenticationTicket> tokens 
        = new ConcurrentDictionary<string, AuthenticationTicket>();

    public void Create(AuthenticationTokenCreateContext context)
    {
        throw new NotImplementedException();
    }

    public async System.Threading.Tasks.Task CreateAsync(AuthenticationTokenCreateContext context)
    {
        var guid = Guid.NewGuid().ToString();

        var ticket = Crypto.Hash(guid);

        tokens.TryAdd(ticket, context.Ticket);

        context.SetToken(ticket);
    }

    public void Receive(AuthenticationTokenReceiveContext context)
    {
        throw new NotImplementedException();
    }

    public async System.Threading.Tasks.Task ReceiveAsync(AuthenticationTokenReceiveContext context)
    {
        AuthenticationTicket ticket;

        if (tokens.TryGetValue(context.Token, out ticket))
        {
            if (ticket.Properties.ExpiresUtc.Value < DateTime.UtcNow)
            {
                tokens.TryRemove(context.Token, out ticket);
            }
            context.SetTicket(ticket);
        }
    }
}

So my questions:

  • Is this an appropriate (and secure!) way of providing a surrogate key in place of my long claims-generated token?
  • Is there perhaps a better/easier place where I should be doing this within the webapi/OAuth stack?

Another thing to note is that I intend to support refresh tokens, and in fact the example above was pulled from examples which use this sort of mechanism for the Refresh token - except with a refresh token they appear to be single-use, so the ReceiveAsync method would usually always remove the refresh token supplied from the ConcurrentDictionary, I'm not entirely sure I understand why?

Jamiec
  • 133,658
  • 13
  • 134
  • 193
  • This approach is fine if your client will resend this authorization go to get back the access token if you use this approached to handle access tokens which in OAuth2 will be jet will not be a right choice – Saravanan Dec 13 '14 at 08:36
  • Because the authorization can be isolated from the client app and you cannot infer the claim from the surrogate token. – Saravanan Dec 13 '14 at 08:37
  • @jamiec did my answer using JWT helped you to shorten your access token full of claims? – Taiseer Joudeh Dec 20 '14 at 15:55
  • 1
    @TaiseerJoudeh -it has and it hasnt. Although it didnt answer my question directly, it has given me a new avenue to explore which I had only barely come accross. Thank you for your answer, I hope half the bounty is acceptable at this time. I may consider putting anther bounty on this question in the new year. – Jamiec Dec 20 '14 at 21:15

1 Answers1

7

I do not recommend to do this because you are eventually going to store the authentication tickets into the database or Redis server, the draw back here that with each request containing a bearer token, you are going to check this permanent store in order to resolve the Guid and get the ticket again to construct it.

I suggest that you use JSON Web Token JWT instead of the default bearer access tokens format, to do this you need implement your custom access token format CustomOAuthProvider in property Provider in OAuthAuthorizationServerOptions as the code below:

 OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
        {
            //For Dev enviroment only (on production should be AllowInsecureHttp = false)
            AllowInsecureHttp = true,
            TokenEndpointPath = new PathString("/oauth2/token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
            Provider = new CustomOAuthProvider(),
            AccessTokenFormat = new CustomJwtFormat("http://jwtauthzsrv.azurewebsites.net")
        };

I've noticed that adding more claims to the JWT token won't increase its size dramatically as the case of default access token format.

Below a sample of 2 JWTs with different claims inside each one, the second one is larger than the first by only 50 chars. I recommend you to check the encoded content of each one using jwt.io First JWT:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1bmlxdWVfbmFtZSI6InRhaXNlZXIiLCJzdWIiOiJ0YWlzZWVyIiwicm9sZSI6WyJNYW5hZ2VyIiwiU3VwZXJ2aXNvciJdLCJpc3MiOiJodHRwOi8vand0YXV0aHpzcnYuYXp1cmV3ZWJzaXRlcy5uZXQiLCJhdWQiOiIwOTkxNTNjMjYyNTE0OWJjOGVjYjNlODVlMDNmMDAyMiIsImV4cCI6MTQxODY0NzMyNywibmJmIjoxNDE4NjQ1NTI3fQ.vH9XPtjtAv2-6SwlyX4fKNJfm5ZTVHd_9a3bRgkA_LI

Second JWT (More claims):

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1bmlxdWVfbmFtZSI6InRhaXNlZXIiLCJzdWIiOiJ0YWlzZWVyIiwicm9sZSI6WyJNYW5hZ2VyIiwiU3VwZXJ2aXNvciIsIlN1cGVydmlzb3IxIiwiU3VwZXJ2aXNvcjIiLCJTdXBlcnZpc29yMyJdLCJpc3MiOiJodHRwOi8vand0YXV0aHpzcnYuYXp1cmV3ZWJzaXRlcy5uZXQiLCJhdWQiOiIwOTkxNTNjMjYyNTE0OWJjOGVjYjNlODVlMDNmMDAyMiIsImV4cCI6MTQxODY0NzQ1NiwibmJmIjoxNDE4NjQ1NjU2fQ.TFEGDtz1RN8VmCQu7JH4Iug0B8UlWDLVrIlvc-7IK3E

The JWT format is becoming the standard way to issue OAuth 2.0 bearer tokens, as well it will work with refresh token grant. But keep in mind that JWT is only signed tokens and not encrypted as the case in default access token format, so do not store confidential data in.

I've written detailed blog post on bitoftech.net on how to use JWT tokens in ASP.NET Web API along with a live demo API and source code on GIthub, feel free to check it and let me know if you need more help.

Good luck!

Taiseer Joudeh
  • 8,953
  • 1
  • 41
  • 45
  • You mention that JWT's work with refresh_tokens. Can you elaborate? Right now, I have an implementation that will give me a JWT as my access_token and in the same response, it includes a much smaller token (a guid) that is my refresh_token. Is that the correct process or should the refresh_token also be a JWT with a longer lifetime? – Chris Swain Feb 05 '15 at 19:01
  • No the refresh token is just oy an identifier to a protected ticket stored in your database, you represent this identifier with grant_type=refresh_token and if it's valid (not deleted from DB and not expired) you will receive new JWT access token, hope it's clear. – Taiseer Joudeh Feb 05 '15 at 19:04