Be Sociable, Share!

Tweet



Email

WhatsApp



After my previous Token Based Authentication post I’ve received many requests to add OAuth Refresh Tokens to the OAuth Resource Owner Password Credentials flow which I’m currently using in the previous tutorial. To be honest adding support for refresh tokens adds a noticeable level of complexity to your Authorization Server. As well most of the available resources on the net don’t provide the full picture of how to implement this by introducing clients nor how to persist the refresh tokens into database.

So I’ve decided to write a detailed post with live demo application which resumes what we’ve built in the previous posts, so I recommend you to read part 1 at least to follow along with this post. This detailed post will cover adding Clients, persisting refresh tokens, dynamically configuring refresh tokens expiry dates, and revoking refresh tokens.

You can check the demo application, play with the back-end API for learning purposes (http://ngauthenticationapi.azurewebsites.net), and check the source code on Github.

Enable OAuth Refresh Tokens in AngularJS App using ASP .NET Web API 2, and Owin

Before start into the implementation I would like to discuss when and how refresh tokens should be used, and what is the database structure needed to implement a complete solution.

Using Refresh Tokens

The idea of using refresh token is to issue short lived access token at the first place then use the refresh token to obtain new access token and so on, so the user needs to authenticate him self by providing username and password along with client info (we’ll talk about clients later in this post), and if the information provided is valid a response contains a short lived access token is obtained along with long lived refresh token (This is not an access token, it is just identifier to the refresh token). Now once the access token expires we can use the refresh token identifier to try to obtain another short lived access token and so on.

But why we are adding this complexity, why not to issue long lived access tokens from the first place?

In my own opinion there are three main benefits to use refresh tokens which they are:

Updating access token content: as you know the access tokens are self contained tokens, they contain all the claims (Information) about the authenticated user once they are generated, now if we issue a long lived token (1 month for example) for a user named “Alex” and enrolled him in role “Users” then this information get contained on the token which the Authorization server generated. If you decided later on (2 days after he obtained the token) to add him to the “Admin” role then there is no way to update this information contained in the token generated, you need to ask him to re-authenticate him self again so the Authorization server add this information to this newly generated access token, and this not feasible on most of the cases. You might not be able to reach users who obtained long lived access tokens. So to overcome this issue we need to issue short lived access tokens (30 minutes for example) and use the refresh token to obtain new access token, once you obtain the new access token, the Authorization Server will be able to add new claim for user “Alex” which assigns him to “Admin” role once the new access token being generated. Revoking access from authenticated users: Once the user obtains long lived access token he’ll be able to access the server resources as long as his access token is not expired, there is no standard way to revoke access tokens unless the Authorization Server implements custom logic which forces you to store generated access token in database and do database checks with each request. But with refresh tokens, a system admin can revoke access by simply deleting the refresh token identifier from the database so once the system requests new access token using the deleted refresh token, the Authorization Server will reject this request because the refresh token is no longer available (we’ll come into this with more details). No need to store or ask for username and password: Using refresh tokens allows you to ask the user for his username and password only one time once he authenticates for the first time, then Authorization Server can issue very long lived refresh token (1 year for example) and the user will stay logged in all this period unless system admin tries to revoke the refresh token. You can think of this as a way to do offline access to server resources, this can be useful if you are building an API which will be consumed by front end application where it is not feasible to keep asking for username/password frequently.

Refresh Tokens and Clients

In order to use refresh tokens we need to bound the refresh token with a Client, a Client means the application the is attempting communicate with the back-end API, so you can think of it as the software which is used to obtain the token. Each Client should have Client Id and Secret, usually we can obtain the Client Id/Secret once we register the application with the back-end API.

The Client Id is a unique public information which identifies your application among other apps using the same back-end API. The client id can be included in the source code of your application, but the client secret must stay confidential so in case we are building JavaScript apps there is no need to include the secret in the source code because there is no straight way to keep this secret confidential on JavaScript application. In this case we’ll be using the client Id only for identifying which client is requesting the refresh token so it can be bound to this client.

In our case I’ve identified clients to two types (JavaScript – Nonconfidential) and (Native-Confidential) which means that for confidential clients we can store the client secret in confidential way (valid for desktop apps, mobile apps, server side web apps) so any request coming from this client asking for access token should include the client id and secret.

Bounding the refresh token to a client is very important, we do not want any refresh token generated from our Authorization Server to be used in another client to obtain access token. Later we’ll see how we will make sure that refresh token is bounded to the same client once it used to generate new access token.

Database structure needed to support OAuth Refresh Tokens

It is obvious that we need to store clients which will communicate with our back-end API in persistent medium, the schema for Clients table will be as the image below:

The Secret column is hashed so anyone has an access to the database will not be able to see the secrets, the Application Type column with value (1) means it is Native – Confidential client which should send the secret once the access token is requested.

The Active column is very useful; if the system admin decided to deactivate this client, so any new requests asking for access token from this deactivated client will be rejected. The Refresh Token Life Time column is used to set when the refresh token (not the access token) will expire in minutes so for the first client it will expire in 10 days, it is nice feature because now you can control the expiry for refresh tokens for each client.

Lastly the Allowed Origin column is used configure CORS and to set “Access-Control-Allow-Origin” on the back-end API. It is only useful for JavaScript applications using XHR requests, so in my case I’ m setting the allowed origin for client id “ngAuthApp” to origin “http://ngauthenticationweb.azurewebsites.net/” and this turned out to be very useful, so if any malicious user obtained my client id from my JavaScript app which is very trivial to do, he will not be able to use this client to build another JavaScript application using the same client id because all preflighted requests will fail and return 405 HTTP status (Method not allowed) All XHR requests coming for his JavaScript app will be from different domain. This is valid for JavaScript application types only, for other application types you can set this to “*”.

Note: For testing the API the secret for client id “consoleApp” is “123@abc”.

Now we need to store the refresh tokens, this is important to facilitate the management for refresh tokens, the schema for Refresh Tokens table will be as the image below:

The Id column contains hashed value of the refresh token id, the API consumer will receive and send the plain refresh token Id. the Subject column indicates to which user this refresh token belongs, and the same applied for Client Id column, by having this columns we can revoke the refresh token for a certain user on certain client and keep the other refresh tokens for the same user obtained by different clients available.

The Issued UTC and Expires UTC columns are for displaying purpose only, I’m not building my refresh tokens expiration logic based on these values.

Lastly the Protected Ticket column contains magical signed string which contains a serialized representation for the ticket for specific user, in other words it contains all the claims and ticket properties for this user. The Owin middle-ware will use this string to build the new access token auto-magically (We’ll see how this take place later in this post).

Now I’ll walk you through implementing the refresh tokens, as I stated before you can read the previous post to be able to follow along with me:

Step 1: Add the new Database Entities

Add new folder named “Entities”, inside the folder you need to define 2 classes named “Client” and “RefreshToken”, the definition for classes as the below:

public class Client { [Key] public string Id { get; set; } [Required] public string Secret { get; set; } [Required] [MaxLength(100)] public string Name { get; set; } public ApplicationTypes ApplicationType { get; set; } public bool Active { get; set; } public int RefreshTokenLifeTime { get; set; } [MaxLength(100)] public string AllowedOrigin { get; set; } } public class RefreshToken { [Key] public string Id { get; set; } [Required] [MaxLength(50)] public string Subject { get; set; } [Required] [MaxLength(50)] public string ClientId { get; set; } public DateTime IssuedUtc { get; set; } public DateTime ExpiresUtc { get; set; } [Required] public string ProtectedTicket { get; set; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class Client { [ Key ] public string Id { get ; set ; } [ Required ] public string Secret { get ; set ; } [ Required ] [ MaxLength ( 100 ) ] public string Name { get ; set ; } public ApplicationTypes ApplicationType { get ; set ; } public bool Active { get ; set ; } public int RefreshTokenLifeTime { get ; set ; } [ MaxLength ( 100 ) ] public string AllowedOrigin { get ; set ; } } public class RefreshToken { [ Key ] public string Id { get ; set ; } [ Required ] [ MaxLength ( 50 ) ] public string Subject { get ; set ; } [ Required ] [ MaxLength ( 50 ) ] public string ClientId { get ; set ; } public DateTime IssuedUtc { get ; set ; } public DateTime ExpiresUtc { get ; set ; } [ Required ] public string ProtectedTicket { get ; set ; } }

Then we need to add simple Enum which defined the Application Type, so add class named “Enum” inside the “Models” folder as the code below:

public enum ApplicationTypes { JavaScript = 0, NativeConfidential = 1 }; 1 2 3 4 5 public enum ApplicationTypes { JavaScript = 0 , NativeConfidential = 1 } ;

To make this entities available on the DbContext, open file “AuthContext” and paste the code below:

public class AuthContext : IdentityDbContext<IdentityUser> { public AuthContext() : base("AuthContext") { } public DbSet<Client> Clients { get; set; } public DbSet<RefreshToken> RefreshTokens { get; set; } } 1 2 3 4 5 6 7 8 9 10 11 public class AuthContext : IdentityDbContext < IdentityUser > { public AuthContext ( ) : base ( "AuthContext" ) { } public DbSet < Client > Clients { get ; set ; } public DbSet < RefreshToken > RefreshTokens { get ; set ; } }

Step 2: Add new methods to repository class

The methods I’ll add now will add support for manipulating the tables we’ve added, they are self explanatory methods and there is nothing special about them, so open file “AuthRepository” and paste the code below:

public Client FindClient(string clientId) { var client = _ctx.Clients.Find(clientId); return client; } public async Task<bool> AddRefreshToken(RefreshToken token) { var existingToken = _ctx.RefreshTokens.Where(r => r.Subject == token.Subject && r.ClientId == token.ClientId).SingleOrDefault(); if (existingToken != null) { var result = await RemoveRefreshToken(existingToken); } _ctx.RefreshTokens.Add(token); return await _ctx.SaveChangesAsync() > 0; } public async Task<bool> RemoveRefreshToken(string refreshTokenId) { var refreshToken = await _ctx.RefreshTokens.FindAsync(refreshTokenId); if (refreshToken != null) { _ctx.RefreshTokens.Remove(refreshToken); return await _ctx.SaveChangesAsync() > 0; } return false; } public async Task<bool> RemoveRefreshToken(RefreshToken refreshToken) { _ctx.RefreshTokens.Remove(refreshToken); return await _ctx.SaveChangesAsync() > 0; } public async Task<RefreshToken> FindRefreshToken(string refreshTokenId) { var refreshToken = await _ctx.RefreshTokens.FindAsync(refreshTokenId); return refreshToken; } public List<RefreshToken> GetAllRefreshTokens() { return _ctx.RefreshTokens.ToList(); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 public Client FindClient ( string clientId ) { var client = _ctx . Clients . Find ( clientId ) ; return client ; } public async Task < bool > AddRefreshToken ( RefreshToken token ) { var existingToken = _ctx . RefreshTokens . Where ( r = > r . Subject == token . Subject && r . ClientId == token . ClientId ) . SingleOrDefault ( ) ; if ( existingToken != null ) { var result = await RemoveRefreshToken ( existingToken ) ; } _ctx . RefreshTokens . Add ( token ) ; return await _ctx . SaveChangesAsync ( ) > 0 ; } public async Task < bool > RemoveRefreshToken ( string refreshTokenId ) { var refreshToken = await _ctx . RefreshTokens . FindAsync ( refreshTokenId ) ; if ( refreshToken != null ) { _ctx . RefreshTokens . Remove ( refreshToken ) ; return await _ctx . SaveChangesAsync ( ) > 0 ; } return false ; } public async Task < bool > RemoveRefreshToken ( RefreshToken refreshToken ) { _ctx . RefreshTokens . Remove ( refreshToken ) ; return await _ctx . SaveChangesAsync ( ) > 0 ; } public async Task < RefreshToken > FindRefreshToken ( string refreshTokenId ) { var refreshToken = await _ctx . RefreshTokens . FindAsync ( refreshTokenId ) ; return refreshToken ; } public List < RefreshToken > GetAllRefreshTokens ( ) { return _ctx . RefreshTokens . ToList ( ) ; }

Step 3: Validating the Client Information

Now we need to implement the logic responsible to validate the client information sent one the application requests an access token or uses a refresh token to obtain new access token, so open file “SimpleAuthorizationServerProvider” and paste the code below:

public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) { string clientId = string.Empty; string clientSecret = string.Empty; Client client = null; if (!context.TryGetBasicCredentials(out clientId, out clientSecret)) { context.TryGetFormCredentials(out clientId, out clientSecret); } if (context.ClientId == null) { //Remove the comments from the below line context.SetError, and invalidate context //if you want to force sending clientId/secrects once obtain access tokens. context.Validated(); //context.SetError("invalid_clientId", "ClientId should be sent."); return Task.FromResult<object>(null); } using (AuthRepository _repo = new AuthRepository()) { client = _repo.FindClient(context.ClientId); } if (client == null) { context.SetError("invalid_clientId", string.Format("Client '{0}' is not registered in the system.", context.ClientId)); return Task.FromResult<object>(null); } if (client.ApplicationType == Models.ApplicationTypes.NativeConfidential) { if (string.IsNullOrWhiteSpace(clientSecret)) { context.SetError("invalid_clientId", "Client secret should be sent."); return Task.FromResult<object>(null); } else { if (client.Secret != Helper.GetHash(clientSecret)) { context.SetError("invalid_clientId", "Client secret is invalid."); return Task.FromResult<object>(null); } } } if (!client.Active) { context.SetError("invalid_clientId", "Client is inactive."); return Task.FromResult<object>(null); } context.OwinContext.Set<string>("as:clientAllowedOrigin", client.AllowedOrigin); context.OwinContext.Set<string>("as:clientRefreshTokenLifeTime", client.RefreshTokenLifeTime.ToString()); context.Validated(); return Task.FromResult<object>(null); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 public override Task ValidateClientAuthentication ( OAuthValidateClientAuthenticationContext context ) { string clientId = string . Empty ; string clientSecret = string . Empty ; Client client = null ; if ( ! context . TryGetBasicCredentials ( out clientId , out clientSecret ) ) { context . TryGetFormCredentials ( out clientId , out clientSecret ) ; } if ( context . ClientId == null ) { //Remove the comments from the below line context.SetError, and invalidate context //if you want to force sending clientId/secrects once obtain access tokens. context . Validated ( ) ; //context.SetError("invalid_clientId", "ClientId should be sent."); return Task . FromResult < object > ( null ) ; } using ( AuthRepository _repo = new AuthRepository ( ) ) { client = _repo . FindClient ( context . ClientId ) ; } if ( client == null ) { context . SetError ( "invalid_clientId" , string . Format ( "Client '{0}' is not registered in the system." , context . ClientId ) ) ; return Task . FromResult < object > ( null ) ; } if ( client . ApplicationType == Models . ApplicationTypes . NativeConfidential ) { if ( string . IsNullOrWhiteSpace ( clientSecret ) ) { context . SetError ( "invalid_clientId" , "Client secret should be sent." ) ; return Task . FromResult < object > ( null ) ; } else { if ( client . Secret != Helper . GetHash ( clientSecret ) ) { context . SetError ( "invalid_clientId" , "Client secret is invalid." ) ; return Task . FromResult < object > ( null ) ; } } } if ( ! client . Active ) { context . SetError ( "invalid_clientId" , "Client is inactive." ) ; return Task . FromResult < object > ( null ) ; } context . OwinContext . Set < string > ( "as:clientAllowedOrigin" , client . AllowedOrigin ) ; context . OwinContext . Set < string > ( "as:clientRefreshTokenLifeTime" , client . RefreshTokenLifeTime . ToString ( ) ) ; context . Validated ( ) ; return Task . FromResult < object > ( null ) ; }

By looking at the code above you will notice that we are doing the following validation steps:

We are trying to get the Client id and secret from the authorization header using a basic scheme so one way to send the client_id/client_secret is to base64 encode the (client_id:client_secret) and send it in the Authorization header. The other way is to sent the client_id/client_secret as “x-www-form-urlencoded”. In my case I’m supporting the both approaches so client can set those values using any of the two available options. We are checking if the consumer didn’t set client information at all, so if you want to enforce setting the client id always then you need to invalidate the context. In my case I’m allowing to send requests without client id for the sake of keeping old post and demo working correctly. After we receive the client id we need to check our database if the client is already registered with our back-end API, if it is not registered we’ll invalidate the context and reject the request. If the client is registered we need to check his application type, so if it was “JavaScript – Non Confidential” client we’ll not check or ask for the secret. If it is Native – Confidential app then the client secret is mandatory and it will be validated against the secret stored in the database. Then we’ll check if the client is active, if it is not the case then we’ll invalidate the request. Lastly we need to store the client allowed origin and refresh token life time value on the Owin context so it will be available once we generate the refresh token and set its expiry life time. If all is valid we mark the context as valid context which means that client check has passed and the code flow can proceed to the next step.

Step 4: Validating the Resource Owner Credentials

Now we need to modify the method “GrantResourceOwnerCredentials” to validate that resource owner username/password is correct and bound the client id to the access token generated, so open file “SimpleAuthorizationServerProvider” and paste the code below:

public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) { var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin"); if (allowedOrigin == null) allowedOrigin = "*"; context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin }); using (AuthRepository _repo = new AuthRepository()) { IdentityUser user = await _repo.FindUser(context.UserName, context.Password); if (user == null) { context.SetError("invalid_grant", "The user name or password is incorrect."); return; } } var identity = new ClaimsIdentity(context.Options.AuthenticationType); identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName)); identity.AddClaim(new Claim("sub", context.UserName)); identity.AddClaim(new Claim("role", "user")); var props = new AuthenticationProperties(new Dictionary<string, string> { { "as:client_id", (context.ClientId == null) ? string.Empty : context.ClientId }, { "userName", context.UserName } }); var ticket = new AuthenticationTicket(identity, props); context.Validated(ticket); } public override Task TokenEndpoint(OAuthTokenEndpointContext context) { foreach (KeyValuePair<string, string> property in context.Properties.Dictionary) { context.AdditionalResponseParameters.Add(property.Key, property.Value); } return Task.FromResult<object>(null); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 public override async Task GrantResourceOwnerCredentials ( OAuthGrantResourceOwnerCredentialsContext context ) { var allowedOrigin = context . OwinContext . Get < string > ( "as:clientAllowedOrigin" ) ; if ( allowedOrigin == null ) allowedOrigin = "*" ; context . OwinContext . Response . Headers . Add ( "Access-Control-Allow-Origin" , new [ ] { allowedOrigin } ) ; using ( AuthRepository _repo = new AuthRepository ( ) ) { IdentityUser user = await _repo . FindUser ( context . UserName , context . Password ) ; if ( user == null ) { context . SetError ( "invalid_grant" , "The user name or password is incorrect." ) ; return ; } } var identity = new ClaimsIdentity ( context . Options . AuthenticationType ) ; identity . AddClaim ( new Claim ( ClaimTypes . Name , context . UserName ) ) ; identity . AddClaim ( new Claim ( "sub" , context . UserName ) ) ; identity . AddClaim ( new Claim ( "role" , "user" ) ) ; var props = new AuthenticationProperties ( new Dictionary < string , string > { { "as:client_id" , ( context . ClientId == null ) ? string . Empty : context . ClientId } , { "userName" , context . UserName } } ) ; var ticket = new AuthenticationTicket ( identity , props ) ; context . Validated ( ticket ) ; } public override Task TokenEndpoint ( OAuthTokenEndpointContext context ) { foreach ( KeyValuePair < string , string > property in context . Properties . Dictionary ) { context . AdditionalResponseParameters . Add ( property . Key , property . Value ) ; } return Task . FromResult < object > ( null ) ; }

By looking at the code above you will notice that we are doing the following:

Reading the allowed origin value for this client from the Owin context, then we use this value to add the header “Access-Control-Allow-Origin” to Owin context response, by doing this and for any JavaScript application we’ll prevent using the same client id to build another JavaScript application hosted on another domain; because the origin for all requests coming from this app will be from a different domain and the back-end API will return 405 status. We’ll check the username/password for the resource owner if it is valid, and if this is the case we’ll generate set of claims for this user along with authentication properties which contains the client id and userName, those properties are needed for the next steps. Now the access token will be generated behind the scenes when we call “context.Validated(ticket)”

Step 5: Generating the Refresh Token and Persisting it

Now we need to generate the Refresh Token and Store it in our database inside the table “RefreshTokens”, to do the following we need to add new class named “SimpleRefreshTokenProvider” under folder “Providers” which implements the interface “IAuthenticationTokenProvider”, so add the class and paste the code below:

public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider { public async Task CreateAsync(AuthenticationTokenCreateContext context) { var clientid = context.Ticket.Properties.Dictionary["as:client_id"]; if (string.IsNullOrEmpty(clientid)) { return; } var refreshTokenId = Guid.NewGuid().ToString("n"); using (AuthRepository _repo = new AuthRepository()) { var refreshTokenLifeTime = context.OwinContext.Get<string>("as:clientRefreshTokenLifeTime"); var token = new RefreshToken() { Id = Helper.GetHash(refreshTokenId), ClientId = clientid, Subject = context.Ticket.Identity.Name, IssuedUtc = DateTime.UtcNow, ExpiresUtc = DateTime.UtcNow.AddMinutes(Convert.ToDouble(refreshTokenLifeTime)) }; context.Ticket.Properties.IssuedUtc = token.IssuedUtc; context.Ticket.Properties.ExpiresUtc = token.ExpiresUtc; token.ProtectedTicket = context.SerializeTicket(); var result = await _repo.AddRefreshToken(token); if (result) { context.SetToken(refreshTokenId); } } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider { public async Task CreateAsync ( AuthenticationTokenCreateContext context ) { var clientid = context . Ticket . Properties . Dictionary [ "as:client_id" ] ; if ( string . IsNullOrEmpty ( clientid ) ) { return ; } var refreshTokenId = Guid . NewGuid ( ) . ToString ( "n" ) ; using ( AuthRepository _repo = new AuthRepository ( ) ) { var refreshTokenLifeTime = context . OwinContext . Get < string > ( "as:clientRefreshTokenLifeTime" ) ; var token = new RefreshToken ( ) { Id = Helper . GetHash ( refreshTokenId ) , ClientId = clientid , Subject = context . Ticket . Identity . Name , IssuedUtc = DateTime . UtcNow , ExpiresUtc = DateTime . UtcNow . AddMinutes ( Convert . ToDouble ( refreshTokenLifeTime ) ) } ; context . Ticket . Properties . IssuedUtc = token . IssuedUtc ; context . Ticket . Properties . ExpiresUtc = token . ExpiresUtc ; token . ProtectedTicket = context . SerializeTicket ( ) ; var result = await _repo . AddRefreshToken ( token ) ; if ( result ) { context . SetToken ( refreshTokenId ) ; } } } }

As you notice this class implements the interface “IAuthenticationTokenProvider” so we need to add our refresh token generation logic inside method “CreateAsync”, and by looking at the code above we can notice the below:

We are generating a unique identifier for the refresh token, I’m using Guid here which is enough for this or you can use your own unique string generation algorithm. Then we are reading the refresh token life time value from the Owin context where we set this value once we validate the client, this value will be used to determine how long the refresh token will be valid for, this should be in minutes. Then we are setting the IssuedUtc, and ExpiresUtc values for the ticket, setting those properties will determine how long the refresh token will be valid for. After setting all context properties we are calling method “context.SerializeTicket();” which will be responsible to serialize the ticket content and we’ll be able to store this magical serialized string on the database. After this we are building a token record which will be saved in RefreshTokens table, note that I’m checking that the token which will be saved on the database is unique for this Subject (User) and the Client, if it not unique I’ll delete the existing one and store new refresh token. It is better to hash the refresh token identifier before storing it, so if anyone has access to the database he’ll not see the real refresh tokens. Lastly we will send back the refresh token id (without hashing it) in the response body.

The “SimpleRefreshTokenProvider” class should be set along with the “OAuthAuthorizationServerOptions”, so open class “Startup” and replace the code used to set “OAuthAuthorizationServerOptions” in method “ConfigureOAuth” with the code below, notice that we are setting the access token life time to a short period now (30 minutes) instead of 24 hours.

OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions() { AllowInsecureHttp = true, TokenEndpointPath = new PathString("/token"), AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30), Provider = new SimpleAuthorizationServerProvider(), RefreshTokenProvider = new SimpleRefreshTokenProvider() }; 1 2 3 4 5 6 7 8 OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions ( ) { AllowInsecureHttp = true , TokenEndpointPath = new PathString ( "/token" ) , AccessTokenExpireTimeSpan = TimeSpan . FromMinutes ( 30 ) , Provider = new SimpleAuthorizationServerProvider ( ) , RefreshTokenProvider = new SimpleRefreshTokenProvider ( ) } ;

Once this is done we we can now test obtaining refresh token and storing it in the database, to do so open your favorite REST client and I’ll use PostMan to compose the POST request against the endpoint http://ngauthenticationapi.azurewebsites.net/token the request will be as the image below:

What worth mentioning here that we are setting the client_id parameter in the request body, and once the “/token” end point receives this request it will go through all the validation we’ve implemented in method “ValidateClientAuthentication”, you can check how the validation will take place if we set the client_id to “consoleApp” and how the client_secret is mandatory and should be provided with this request because the application type for this client is “Native-Confidential”.

As well by looking at the response body you will notice that we’ve obtained a “refresh_token” which should be used to obtain new access token (we’ll see this later in this post) this token is bounded to user “Razan” and for Client “ngAuthApp”. Note that the “expires_in” value is related to the access token not the refresh token, this access token will expires in 30 mins.

Step 6: Generating an Access Token using the Refresh Token

Now we need to implement the logic needed once we receive the refresh token so we can generate a new access token, to do so open class “SimpleRefreshTokenProvider” and implement the code below in method “ReceiveAsync”:

public async Task ReceiveAsync(AuthenticationTokenReceiveContext context) { var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin"); context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin }); string hashedTokenId = Helper.GetHash(context.Token); using (AuthRepository _repo = new AuthRepository()) { var refreshToken = await _repo.FindRefreshToken(hashedTokenId); if (refreshToken != null ) { //Get protectedTicket from refreshToken class context.DeserializeTicket(refreshToken.ProtectedTicket); var result = await _repo.RemoveRefreshToken(hashedTokenId); } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public async Task ReceiveAsync ( AuthenticationTokenReceiveContext context ) { var allowedOrigin = context . OwinContext . Get < string > ( "as:clientAllowedOrigin" ) ; context . OwinContext . Response . Headers . Add ( "Access-Control-Allow-Origin" , new [ ] { allowedOrigin } ) ; string hashedTokenId = Helper . GetHash ( context . Token ) ; using ( AuthRepository _repo = new AuthRepository ( ) ) { var refreshToken = await _repo . FindRefreshToken ( hashedTokenId ) ; if ( refreshToken != null ) { //Get protectedTicket from refreshToken class context . DeserializeTicket ( refreshToken . ProtectedTicket ) ; var result = await _repo . RemoveRefreshToken ( hashedTokenId ) ; } } }

What we’ve implemented in this method is the below:

We need to set the “Access-Control-Allow-Origin” header by getting the value from Owin Context, I’ve spent more than 1 hour figuring out why my requests to issue access token using a refresh token returns 405 status code and it turned out that we need to set this header in this method because the method “GrantResourceOwnerCredentials” where we set this header is never get executed once we request access token using refresh tokens (grant_type=refresh_token). We get the refresh token id from the request, then hash this id and look for this token using the hashed refresh token id in table “RefreshTokens”, if the refresh token is found, we will use the magical signed string which contains a serialized representation for the ticket to build the ticket and identities for the user mapped to this refresh token. We’ll remove the existing refresh token from tables “RefreshTokens” because in our logic we are allowing only one refresh token per user and client.

Now the request context contains all the claims stored previously for this user, we need to add logic which allows us to issue new claims or updating existing claims and contain them into the new access token generated before sending it to the user, to do so open class “SimpleAuthorizationServerProvider” and implement method “GrantRefreshToken” using the code below:

public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context) { var originalClient = context.Ticket.Properties.Dictionary["as:client_id"]; var currentClient = context.ClientId; if (originalClient != currentClient) { context.SetError("invalid_clientId", "Refresh token is issued to a different clientId."); return Task.FromResult<object>(null); } // Change auth ticket for refresh token requests var newIdentity = new ClaimsIdentity(context.Ticket.Identity); newIdentity.AddClaim(new Claim("newClaim", "newValue")); var newTicket = new AuthenticationTicket(newIdentity, context.Ticket.Properties); context.Validated(newTicket); return Task.FromResult<object>(null); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public override Task GrantRefreshToken ( OAuthGrantRefreshTokenContext context ) { var originalClient = context . Ticket . Properties . Dictionary [ "as:client_id" ] ; var currentClient = context . ClientId ; if ( originalClient != currentClient ) { context . SetError ( "invalid_clientId" , "Refresh token is issued to a different clientId." ) ; return Task . FromResult < object > ( null ) ; } // Change auth ticket for refresh token requests var newIdentity = new ClaimsIdentity ( context . Ticket . Identity ) ; newIdentity . AddClaim ( new Claim ( "newClaim" , "newValue" ) ) ; var newTicket = new AuthenticationTicket ( newIdentity , context . Ticket . Properties ) ; context . Validated ( newTicket ) ; return Task . FromResult < object > ( null ) ; }

What we’ve implement above is simple and can be explained in the points below:

We are reading the client id value from the original ticket, this is the client id which get stored in the magical signed string, then we compare this client id against the client id sent with the request, if they are different we’ll reject this request because we need to make sure that the refresh token used here is bound to the same client when it was generated. We have the chance now to add new claims or remove existing claims, this was not achievable without refresh tokens, then we call “context.Validated(newTicket)” which will generate new access token and return it in the response body. Lastly after this method executes successfully, the flow for the code will hit method “CreateAsync” in class “SimpleRefreshTokenProvider” and a new refresh token is generated and returned in the response along with the new access token.

To test this out we need to issue HTTP POST request to the endpoint http://ngauthenticationapi.azurewebsites.net/token the request will be as the image below:

Notice how we set the “grant_type” to “refresh_token” and passed the “refresh_token” value and client id with the request, if all went successfully we’ll receive a new access token and refresh token.

Step 7: Revoking Refresh Tokens

The idea here is simple, all you need to do is to delete the refresh token record from table “RefreshTokens” and once the user tries to request new access token using the deleted refresh token it will fail and he needs to authenticate again using his username/password in order to obtain new access token and refresh token. By having this feature your system admin can have control on how to revoke access from logged in users.

To do this we need to add new controller named “RefreshTokensController” under folder “Controllers” and paste the code below:

[RoutePrefix("api/RefreshTokens")] public class RefreshTokensController : ApiController { private AuthRepository _repo = null; public RefreshTokensController() { _repo = new AuthRepository(); } [Authorize(Users="Admin")] [Route("")] public IHttpActionResult Get() { return Ok(_repo.GetAllRefreshTokens()); } //[Authorize(Users = "Admin")] [AllowAnonymous] [Route("")] public async Task<IHttpActionResult> Delete(string tokenId) { var result = await _repo.RemoveRefreshToken(tokenId); if (result) { return Ok(); } return BadRequest("Token Id does not exist"); } protected override void Dispose(bool disposing) { if (disposing) { _repo.Dispose(); } base.Dispose(disposing); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 [ RoutePrefix ( "api/RefreshTokens" ) ] public class RefreshTokensController : ApiController { private AuthRepository _repo = null ; public RefreshTokensController ( ) { _repo = new AuthRepository ( ) ; } [ Authorize ( Users = "Admin" ) ] [ Route ( "" ) ] public IHttpActionResult Get ( ) { return Ok ( _repo . GetAllRefreshTokens ( ) ) ; } //[Authorize(Users = "Admin")] [ AllowAnonymous ] [ Route ( "" ) ] public async Task < IHttpActionResult > Delete ( string tokenId ) { var result = await _repo . RemoveRefreshToken ( tokenId ) ; if ( result ) { return Ok ( ) ; } return BadRequest ( "Token Id does not exist" ) ; } protected override void Dispose ( bool disposing ) { if ( disposing ) { _repo . Dispose ( ) ; } base . Dispose ( disposing ) ; } }

Nothing special implemented here, we only have 2 actions which lists all the stored refresh tokens on the database, and another method which accepts a query string named “tokeId”, this should contains the hashed value of the refresh token, this method will be used to delete/revoke refresh tokens.

For the sake of the demo, I’ve authorized only user named “Admin” to execute the “Get” method and obtain all refresh tokens, as well I’ve allowed anonymous access for the “Delete” method so you can try to revoke your own refresh tokens, on production you need to secure this end point.

To hash your refresh tokens you have to use the helper class below, so add new class named “Helper” and paste the code below:

public static string GetHash(string input) { HashAlgorithm hashAlgorithm = new SHA256CryptoServiceProvider(); byte[] byteValue = System.Text.Encoding.UTF8.GetBytes(input); byte[] byteHash = hashAlgorithm.ComputeHash(byteValue); return Convert.ToBase64String(byteHash); } 1 2 3 4 5 6 7 8 9 10 public static string GetHash ( string input ) { HashAlgorithm hashAlgorithm = new SHA256CryptoServiceProvider ( ) ; byte [ ] byteValue = System . Text . Encoding . UTF8 . GetBytes ( input ) ; byte [ ] byteHash = hashAlgorithm . ComputeHash ( byteValue ) ; return Convert . ToBase64String ( byteHash ) ; }

So to test this out and revoke a refresh token we need to issue a DELETE request to the following end point “http://ngauthenticationapi.azurewebsites.net/api/refreshtokens“, the request will be as the image below:

Once the refresh token has been removed, if the user tries to use it he will fail obtaining new access token until he authenticate again using username/password.

Step 8: Updating the front-end AngularJS Application

I’ve updated the live front-end application to support using refresh tokens along with Resource Owner Password Credentials flow, so once you log in, and if you want to user refresh tokens you need to check the check box “Use Refresh Tokens” as the image below:

One you log in successfully, you will find new tab named “Refresh Tokens” on the top right corner, this tab will provide you with a view which allows you to obtain a new access token using the refresh token you obtained on log in, the view will look as the image below:

Update (11-08-2014) Thanks to Nikolaj for forking the repo and add support for seamless refresh token requests, you can check it here.

Lastly I’ve added new view named “/tokens” which is accessible only by username “Admin”. This view will list all available refresh tokens along with refresh token issue and expiry date. This view will allow the admin only to revoke a refresh tokens, the view will look as the below image:

I will write another post which shows how I’ve implemented this in the AngularJS application, for now you can check the code on my GitHub repo.

Conclusion

Security is really hard! You need to think about all the ins and outs, this post turned out to be too long and took way longer to compile and write than I expected, but hopefully it will be useful for anyone looking to implement refresh tokens. I would like to hear your feedback and comments if there is something missing or there is something we can enhance on this post.

You can check the demo application, play with the back-end API for learning purposes (http://ngauthenticationapi.azurewebsites.net), and check the source code on Github.

Follow me on Twitter @tjoudeh

References

Special thanks goes to Dominick Baier for his detailed posts about OAuth, especially this post was very useful. As well I highly recommend checking the Thinktecture.IdentityServer. Great post by Andrew Timney on persisting refresh tokens.