Adding a Simple Refresh Token to OAuth Bearer Tokens

If you’re using a .NET as your web platform and are looking to expand it to another platform such as mobile applications, and need to authenticate users from that external application, one of the best ways of going about it is through the use of OAuth Bearer Tokens.

James Randall has a great post here about getting started with the OAuth Bearer token Authentication. This post isn’t going to focus on getting started, but will use this example to expand upon.

Using Bearer (access) Tokens allows you to authenticate users without having to send their password through the pipes with each request. Using an access token in your header will let you authorize requests to your api as well as through SignalR or other web services.

Here is an example of the authorization header sent with a request to authorize a user:
“Authorize Bearer YOUR_ACCESS_TOKEN”

However, what happens when this token expires? Of course, you can set an outrageously long expiration date, but that is a security nightmare. You don’t want to store the users password locally to continuously send requests to get a new token. You also don’t want to require the user to re-login every time the token expires.

The solution? Refresh tokens! A refresh token will allow you to receive a new access token after it expires without sending the user’s password.

The first step is to create a RefreshTokenProvider that we can add during our Startup processing. Here is a simple Provider that will work for this example:


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

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

         // maybe only create a handle the first time, then re-use for same client
         // copy properties and set the desired lifetime of refresh token
         var refreshTokenProperties = new AuthenticationProperties(context.Ticket.Properties.Dictionary)
         {
             IssuedUtc = context.Ticket.Properties.IssuedUtc,
             ExpiresUtc = DateTime.UtcNow.AddYears(1)
         };
         var refreshTokenTicket = new AuthenticationTicket(context.Ticket.Identity, refreshTokenProperties);

         //_refreshTokens.TryAdd(guid, context.Ticket);
         _refreshTokens.TryAdd(guid, refreshTokenTicket);

         // consider storing only the hash of the handle
         context.SetToken(guid);
     }

     public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
     {
         AuthenticationTicket ticket;
         if (_refreshTokens.TryRemove(context.Token, out ticket))
         {
             context.SetTicket(ticket);
         }
     }

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

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

We can now use this provider to add the setting to our Startup.Auth.cs:


 OAuthOptions = new OAuthAuthorizationServerOptions
 {
     TokenEndpointPath = new PathString("/Token"),
     Provider = new ApplicationOAuthProvider(PublicClientId, UserManagerFactory),
     AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
     AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(60),
     AllowInsecureHttp = true,
     RefreshTokenProvider = new SimpleRefreshTokenProvider() //REFERENCE TO PROVIDER

 };

Now we have our access token that expires every hour, and a refresh token that expires every year. The time-spans to use for both of these is completely up to you.

Now we can use requests like these with our external application-

Authorize with username and password:

Method: POST
Url: http://yourdomain/Token?username=yourusername&password=yourpassword&grant_type=password

Response:

{

"access_token":"VSt0JjzP-9PO6OFk-i_dp7Xs7RA4JTai_nv1FXxTiZ-iMoYjCt42Jw8eJqV66EouqAxnsHIUzDucHKSQUhEch9tftf_dNgi0pDKFUZn5UVJ0rybZ8keG4LjT2oI851D1OnE0Ij0KnEr5ox_RNFpYW5Srqj_4Uy4uYkhrOLKxo3TEt_nBFNhVsvTAxoY5ggDdTK_th945XzeZeXjRSX-j8clYJpaxAUmA-Z38qhbyXiq29wSZKswhloaHcIVIJDXe9Fhpfe1nM4IfJT5Lwy1tjYH4XIphd7UX_nprX4JEwlJUFENJE9E-Gq6y7deXQa7j3JXIg8YBtvcR0Mj0Fjxhj6Bdaq2hCE1Ot6KgZUxOzzRkiuJlkMoQgmg8T2MM6STfQnX-cEd328n6oYgYBxg34kLbi8NGSHiAKEtxcF8Fuj7gizMOCK91iaVQTf_7UsJIkW6KFGeGLz0MG8A71jj-kNjzSFApYGo6VCoQJqXzREY",

"token_type":"bearer",

"expires_in": 3600,

"refresh_token":"969c9b04-afe5-48a3-9353-62509f71e906",

"userName":"yourusername",

".issued":"Thu, 30 Apr 2015 02:21:08 GMT",

".expires":"Thu, 30 Apr 2015 02:22:08 GMT"

}

Authorize with username and refresh token:

Method: POST
Url: http://yourdomain/Token?username=yourusername&refresh_token=969c9b04-afe5-48a3-9353-62509f71e906&grant_type=refresh_token

Response:

{

"access_token":"SAPmj6kWat4KcwhASsTkkuQ0hxeIZaq4ztZBduHV_Mr-0SoxzQZ61ojdiXDtUIo_ptfbuzx5sA9_3-GpPWZhQ702qvAXdYSnMy_OUVVypfVkP-9mUsS7iR_4uFd67MFrSVEfQ4Er1Tm9AiFLC1j4kR7WjAmZgn6YuhU1Z3NNOFMu6UGEutJEWZte4mcnHinYKxskwVt_45DBGqEaLQ1OQoPhYwLTPGIhAcvsjiVLOxHCWp46bfYOyP5tVBoZxoaftuYyQfEgOkeU44TRWdGRZBh6vKWKdjWqa-qpy8fCNoJkwpSSjWYGEyhG4IkDyRCRGpCfMHP5rbP6dfaWAAchk7qQCmcia_vuEFoZWFWER6_LFe58avh_ZqfmJQhl7lVaM4z5SEKmKP4RPXgK32T4jQEqoisOGi66bcueLzRGmCsW2BlBnxPC1QloY_VQR8bEoCqK7_C0haMH7t30sJz_2Cz9CgnMnIjeyVhdcQsg_4U",

"token_type":"bearer",

"expires_in": 3600,

"refresh_token":"9a773700-0b48-411e-9138-1fc0e266d8a9",

"userName":"yourusername",

".issued":"Thu, 30 Apr 2015 02:21:08 GMT",

".expires":"Thu, 30 Apr 2015 02:22:08 GMT"

}

This is obviously very simplified and lacks typical uses with things like Client Ids and handling proper storage and error handling, but it will hopefully help you get started with your Authorization layer for any external applications through Web API.

Advertisements