Token Based Authentication in ASP.Net using JWTs Part 2: Using Refresh Tokens

In Part 1 we setup basic token authentication using JWT’s with asp.net.  Things are setup reasonably but all is not well.  As a developer, you could give the token a lifespan of 30 days and just force the user to re-login after those days but what if you make the user inactive and don’t want him to login anymore?  There must be better way.AJ

Generally, I token has a lifetime of about an hour and when it expires, we want to refresh that token, verifying that the user still has access to the system, etc.  The method that this is handled is using refresh tokens.  A refresh token is returned along with the normal token and it’s stored for when we must refresh normal token.

Starting from our previous app, let’s support refresh tokens.  Note, the completed code for this blog can be found here.

This will require us to track refresh tokens in our database, so first, let’s create the RefreshToken model.

Setting up the Database

Under “Models”, create a class named “RefreshToken.cs” and paste the following:


using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Web;
using api.Models;

namespace WebApplication1.Models
{
    public class RefreshToken
    {
        [Key]
        public int Id { get; set; }

        public string UserId { get; set; }
        [ForeignKey("UserId")]
        public User User { get; set; }

        public string Token { get; set; }
        public DateTime IssuedUtc { get; set; }
        public DateTime ExpiresUtc { get; set; }
        [Required]
        public string ProtectedTicket { get; set; }
    }
}

The token is associated to the user via UserId and the refresh token, itself, is stored in the Token Property.

Now, we need to change MyDbContext.cs to handle the refresh token.  Replace MyDbContext with the following:

using api.Models;
using Microsoft.AspNet.Identity.EntityFramework;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Threading.Tasks;
using System.Web;

namespace WebApplication1.Models
{
    public class MyDbContext : IdentityDbContext<User>
    {
        public MyDbContext()
            : base("DefaultConnection", throwIfV1Schema: false)
        {
        }

        public static MyDbContext Create()
        {
            return new MyDbContext();
        }

        public DbSet<RefreshToken> RefreshTokens { get; set; }

        public async Task<bool> AddRefreshToken(RefreshToken token)
        {

            var existingToken = RefreshTokens.Where(r => r.UserId == token.UserId).SingleOrDefault();

            if (existingToken != null)
            {
                var result = await RemoveRefreshToken(existingToken);
            }

            RefreshTokens.Add(token);

            return await SaveChangesAsync() > 0;
        }

        public async Task<RefreshToken> FindRefreshTokenAsync(string token)
        {
            return await RefreshTokens.SingleOrDefaultAsync(i => i.Token == token);
        }

        public async Task<bool> RemoveRefreshToken(string refreshToken)
        {
            var refreshTokenModel = await RefreshTokens.SingleAsync(i => i.Token == refreshToken);

            if (refreshToken != null)
            {
                RefreshTokens.Remove(refreshTokenModel);
                return await SaveChangesAsync() > 0;
            }

            return false;
        }

        public async Task<bool> RemoveRefreshToken(RefreshToken refreshToken)
        {
            RefreshTokens.Remove(refreshToken);
            return await SaveChangesAsync() > 0;
        }
    }
}

This now adds a table for the refresh tokens and the functions for working with them.

Next, we need to add a migration so the database get the new table.  Open the Package Manager Console via Tools -> NuGet Package Manager -> Package Manager Console.  From the console, type “Add-Migration” and name it “RefreshTokens” and hit Enter.  When that finishes, you should have a file named “RefreshTokens” (and a timestamp appended) under migrations.

Let’s execute the migration.  Inside the Package Manager Console, type “update-database” and that will update the database to the latest version.

Now, we need to create Refresh Token Provider.

Creating the Provider

Next, we need to create the refresh token provider.  Under Providers, create a class named “RefreshTokenProvider.cs” and add the following:


using Microsoft.Owin.Security.Infrastructure;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Threading.Tasks;
using api.Models;

namespace api.Providers
{
    public class RefreshTokenProvider : IAuthenticationTokenProvider
    {
        public async Task CreateAsync(AuthenticationTokenCreateContext context)
        {
            var userId = context.Ticket.Properties.Dictionary["userId"];

            if (string.IsNullOrEmpty(userId))
            {
                return;
            }

            var refreshTokenId = Guid.NewGuid().ToString("n");

            using (var repo = new MyDbContext())
            {
                var refreshTokenLifeTime = api.Utils.Configuration.RefreshTokenExpireTimeMinutes;
                var token = new RefreshToken()
                {
                    IssuedUtc = DateTime.UtcNow,
                    ExpiresUtc = DateTime.UtcNow.AddMinutes(Convert.ToDouble(refreshTokenLifeTime)),
                    Token = refreshTokenId,
                    UserId = userId
                };

                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);
                }

            }
        }

        public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
        {

            using (var repo = new MyDbContext())
            {
                var refreshToken = await repo.FindRefreshTokenAsync(context.Token);

                if (refreshToken != null)
                {
                    //Get protectedTicket from refreshToken class
                    context.DeserializeTicket(refreshToken.ProtectedTicket);
                    var result = await repo.RemoveRefreshToken(context.Token);
                }
            }
        }

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

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

You’ll notice that I added a property to our configuration file named “RefreshTokenExpireTimeMinutes”.  Let’s add this to our configuration file:


 using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Web;

namespace api.Utils
{
    public class Configuration
    {
        public static string TokenIssuer => ConfigurationManager.AppSettings["Token:Issuer"];
        public static string TokenAudienceId => ConfigurationManager.AppSettings["Token:AudienceId"];
        public static string TokenAudienceSecret => ConfigurationManager.AppSettings["Token:AudienceSecret"];

        public static int TokenExpireTimeMinutes => Convert.ToInt32(ConfigurationManager.AppSettings["Token:TokenExpireTimeMinutes"]);
        public static int RefreshTokenExpireTimeMinutes => Convert.ToInt32(ConfigurationManager.AppSettings["Token:RefreshTokenExpireTimeMinutes"]);
    }
}

Ok, now we need to add an entry for this inside our web.config file, inside the “appSettings” section (put it underneath “Token:TokenExpireTimeMinutes”:


<add key="Token:RefreshTokenExpireTimeMinutes" value="1440"/>

Next, we’ll add a function to our ApplicationOAuthProvider to handle refresh tokens.  Place the following inside the ApplicationOAuthProvider class:


        public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
        {
            var userId = context.Ticket.Properties.Dictionary["userId"];
            if (string.IsNullOrEmpty(userId))
            {
                context.SetError("invalid_grant", "User Id not set.");
                return Task.FromResult<object>(null);
            }

            var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
            User user = userManager.Users.Single(i => i.Id == userId);

            if (user == null)
            {
                context.SetError("invalid_grant", "User not found.");
                return Task.FromResult<object>(null);
            }

            if (!user.IsActive)
            {
                context.SetError("invalid_grant", "Error logging in user.");
                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);
        }

So, when a refresh token is used to obtain a new JWT, the system verifies the user is real and still has access to the system (IsActive).

Next, we need to wire up refresh tokens in out startup.cs file.  Replace the function “ConfigureOAuthForJWT” with the following (this also adds a new function):

public void ConfigureOAuthForJWT(IAppBuilder app)
        {
            var expireTime =
            PublicClientId = "self";
            OAuthOptions = new OAuthAuthorizationServerOptions
            {
                TokenEndpointPath = new PathString("/api/Token"),
                Provider = new ApplicationOAuthProvider(PublicClientId),
                AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
                AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(api.Utils.Configuration.TokenExpireTimeMinutes),
                // In production mode set AllowInsecureHttp = false
                AllowInsecureHttp = true,
                AccessTokenFormat = new JWTFormat(api.Utils.Configuration.TokenIssuer),
                RefreshTokenProvider = new RefreshTokenProvider()
            };
            app.UseOAuthAuthorizationServer(OAuthOptions);
            app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
        }

The only new part is where we are setting the RefreshTokenProvider to our newly created provider.

Testing The Refresh Token

Now, from postman (inside Chrome) if we login, the system will return our token and (among other things) a refresh_token field an that is our refresh token:

postman-1.PNG

When the JWT token expires, the rest call will return a 401 error, and at this time, you should use the refresh token to update the JWT.  Here’s an example in postman:

postman-2.PNG

This only requires a few things to refresh the token:

  • the grant_type must be set to “refresh_token”
  • “refresh_token” must be set to the value of the refresh token.

Once this is done, the system will return a new token that can be used in any new rest calls.  NOTE: the refresh token changes here, so you’ll want to store the new refresh token for later use.

Ok that’s it on refresh tokens.  My next post will be on consuming token based auth in ASP.Net Core 2.0.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s