JWT Token Authentication with Cookies in ASP.NET Core

Authentication for modern web applications is usually done in 2 major ways:

  1. Token based authentication: this is usually done for APIs used by 3rd party developers. Client requests exchange a client id and secret key for an access token that they then pass in each request to the server to establish identity and claims.
  2. Cookie based authentication: this is done for browser based web applications that have a web front end like views and pages. After the user signs-in, the server packages the user details into a cookie and sends out in the response. The browser then auto-sends the cookie back with each request so the user stays authenticated on the server. To keep the size of cookie within the 4KB limit, ASP.NET stores the details on the server in a Session object and just sends the session id back so that later it can look up the session in memory.

The problem with maintaining server side session is that it limits scalability and availability in a web-farm/multi-instanced scenarios. The authenticated client request can land on any instance and it should still be able to stay authenticated but with in-memory session, a client request will be forced to login again if the requests lands on an instance that has no record of the session created by another instance.

To eliminate this, the simplest thing is to transmit the encrypted authentication details with each request so that any instance can validate them and the authentication “session” won’t be lost.

In this post I will show one way to JWT (Json Web Token) tokens can be sent out in a cookie using the cookie authentication system in ASP.NET Core. The base for this post is this blog post from Stormpath where my post differs is: a) I have extended by showing the data protection aspects of it, b) I am not using a custom middleware but instead leveraging controller actions and c) I have shown the full code including how I have created necessary abstractions to decouple application code from authentication.

I will start by enabling the ASP.NET Core authentication middleware by a call to app.UseAuthentication() in StartUp.cs. Please note the code in this post uses ASP.NET Core 2.0, things with regards to the authentication middleware have changed quite a bit since version 1.1:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    // ...other middleware

    app.UseAuthentication();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

This call adds the authentication middleware to the ASP.NET Core request processing pipeline so understandably it needs to go before the call to UseMvc() before any secure part of the application is accessed.

Then in the ConfigureServices method of the Startup.cs file, I will add the authentication services for cookies and set a couple of defaults:

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(options =>
    {
        // these must be set other ASP.NET Core will throw exception that no
        // default authentication scheme or default challenge scheme is set.
        options.DefaultAuthenticateScheme =
                CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme =
                CookieAuthenticationDefaults.AuthenticationScheme;
    })
    .AddCookie();

    services.AddMvc(options => options.Filters.Add(new
                     RequireHttpsAttribute()));
}

I will come back to this method later on when configuring the JWT bit.

For the purposes of this post, I will just secure the About page in the application by marking the About() action with the [Authorize] attribute. Any unauthorised requests will automatically be redirected to the login page:

public class HomeController : Controller
{
    [Authorize]
    public IActionResult About()
    {
        ViewData["Message"] = "Your application description page.";

        return View();
    }
}

Next, in the AccountController I will create the Login() action that will accept a LoginModel object which is a POCO with 2 string properties to hold the username and password supplied by the user:

[AllowAnonymous]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(UserCredentials userCredentials,
                                      string returnUrl = null)
{
    // I will fill this in a bit...
}

One of the login actions is a GET type action to navigate to the login page and the other one is a POST type action that will validate the username and password.

For the login to work correctly, I need to:

a) Verify that the user does infact exist.
b) If verification succeeds, I will then create a JWT along with a ClaimsPrincipal.
c) This token and ClaimsPrincipal object will then be packaged securely in the cookie and sent back as the response.

I created an interface – IJwtTokenGenerator which is just a simple interface that I can implement to provide token generation facility in a reusable fashion across applications requiring JWT authentication.

public interface IJwtTokenGenerator
{
    TokenWithClaimsPrincipal GenerateAccessTokenWithClaimsPrincipal(string userName, 
    IEnumerable<Claim> userClaims);

    string GenerateAccessToken(string userName,
IEnumerable<Claim> userClaims);
}

UPDATE: This code has undergone a fair bit of refactoring and a bit of a design overhaul. I am no longer passing in an IIdentityResolver. Reason: leaking abstractions! The token generator should only be responsible for generating tokens and shouldn’t take part in the authentication itself. It must work on the assumption that if the control flow has reached this far then the user has probably been successfully authenticated.
This makes the design simpler, removes unnecessary dependencies and makes it easier to test.

For the implementation of this interface, please check out the code on GitHub.

 

JWTs can also contain claims information about the user, claims can be about what the user is allowed to do within the application. There are also some standard claims that form a part of the JWT standard. Claims are simple strings and can be anything as long as their names don’t clash with the registered claim names. To make things simple the System.IdentityModel.Tokens.Jwt namespace has a JwtRegisteredClaimNames enum that defines these registered names and can be used to add claims to the generated JWT as shown above in the generator class. In addition to the standard claims, I would also put things like permissions and roles in the JWT claims set, this way I don’t have to go to the identity database again and again to find out what authorisations does the user have neither I would need to maintain any server side state for that and potentially hamper scalability. I have all the information needed to make authorisation decisions right in the token that will be sent back to the server with each request.

Since the token is signed by a key only known to the server and then put in a cookie that is encrypted, even if someone got hold of the cookie containing the token they will have a tough time guessing the signing key and the decrypting key in order to get to the actual content of the token, so security wise, I would say its pretty good. Of course, no security is ever perfect which is why signing and encryption keys should be rotated often and they should be stored in secure places like Azure Key Vault if hosting on cloud.

The TokenWithClaimsPrincipal class encapsulates the JWT and the ClaimsPrincipal object and the AuthenticationProperties (this class resides in the Microsoft.AspNetCore.Authentication namespace and exposes an dictionary store where tokens can be stored) objects:

public class TokenWithClaimsPrincipal
{
    public string AccessToken { get; internal set; }

    public ClaimsPrincipal ClaimsPrincipal { get; internal set; }

    public AuthenticationProperties AuthProperties { get; internal set; }
}

This ClaimsPrincipal and AuthenticationProperties objects will be passed into the HttpContext.SignInAsync() method later on in the controller which will kick off the cookie creation process which will in turn wrap the claims principal and auth properties containing the actual token into the cookie ticket and send out the user.

The way JWTs work is by encoding the following things into the token body and then verifying against them:

1. Issuer: the entity that’s issuing the token, could be any string. Ideally, your application URL.
2. Audience: the entity that the token is being issued for. Again, could be any string.
3. Secret Signing Key: at least 128-bit (or 16 character long) string that is only known to the server, could be any string really as long as its not easily guessable.
4. Expiration Time Limit: the time from the generation after which the token will expire and will need to regenerated either automatically or logging user out. For consistency, I set the cookie’s expiration same as that of the token so they both expire near about the same time although ASP.NET Core is clever enough to check the token inside the auth ticket and if that has expired, it will reject the cookie even if the cookie hasn’t expired yet.

Now I need a way to tell the cookie authentication to use the JWT token instead of executing its default behaviour. To do this, I can customise the call to AddCookie by passing an anonymous Action delegate and setting up the options. The way to ask cookie authentication middleware to use JWT token for generating the authentication ticket is to override the CookieAuthenticationOptions.TicketDataFormat property and pass in a custom ISecureDataFormat implementation. I will list the implementation code a bit later.

ASP.NET Core’s default cookie authentication also uses it to generate a secure authentication ticket and packs in the claims principal that tells it if the request is authenticated or not. This ISecureDataFormat implementation needs a few things: an IDataSerialiser which it will use to serialise the cookie before sending to client, an IDataProtector which is a part of the Data Protection API of ASP.NET Core which is use to protect the cookie prior to sending it out and unprotect it when it receives it in a request to validate it. I was able to find all this magic by reverse engineering ASP.NET Core’s Authentication nuget package (if there is such a thing as reverse engineering in the open source world of today) and seeing how Microsoft implemented their cookie validation mechanism. I copied from their stuff and tweaked as needed to get this to work.

public void ConfigureServices(IServiceCollection services)
{
    var signingKey = new SymmetricSecurityKey(
        Encoding.ASCII.GetBytes(
         Configuration["Token:SigningKey"]);

    var validationParams = new TokenValidationParameters()
    {
        ClockSkew = TimeSpan.Zero,

        ValidateAudience = true,
        ValidAudience = Configuration["Token:Audience"],

        ValidateIssuer = true,
        ValidIssuer = Configuration["Token:Issuer"],

        IssuerSigningKey = signingKey,
        ValidateIssuerSigningKey = true,

        RequireExpirationTime = true,
        ValidateLifetime = true
    };

    services.AddDataProtection(options =>
options.ApplicationDiscriminator = $"{Environment.ApplicationName}")
        .SetApplicationName($"{Environment.ApplicationName}");

    services.AddScoped<IDataSerializer<AuthenticationTicket>,
        TicketSerializer>();

    services.AddScoped<IJwtTokenGenerator, JwtTokenGenerator>(serviceProvider =>
        new JwtTokenGenerator(validationParams.ToTokenOptions()));

    services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    })
    .AddCookie(options =>
    {
        options.Cookie.Expiration = TimeSpan.FromMinutes(5);
        options.TicketDataFormat = new JwtAuthTicketFormat(validationParams,            services.BuildServiceProvider()
.GetService<IDataSerializer<AuthenticationTicket>>(),
<span 				data-mce-type="bookmark" 				id="mce_SELREST_start" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			></span>services.BuildServiceProvider().GetDataProtector(new[] { $"{Environment.ApplicationName}-Auth1" }));

        options.LoginPath = "/Account/Login";
        options.LogoutPath = "/Account/Logout";
        options.AccessDeniedPath = options.LoginPath;
        options.ReturnUrlParameter = "returnUrl";
    });

    services.AddMvc();
}

The data protection API ensures unique secret keys per machine are being used encrypt and decrypt sensitive data on the server. Microsoft has in-depth (and quite dense) articles about the data protection API and what you need to consider when deploying apps to on-premise single instance server vs multi-instanced cloud based environments. There are extension methods in the API that allow you to store the encryption keys (called a “key-ring”) on a distributed storage like Azure Table Storage or Redis Cache in order to make the application process stateless. For this example since the application is just on my local server, I will use the default which is storing the keys on “C:\Users\{username}\AppData\Local\ASP.NET\DataProtection-Keys\” on my machine.

The custom ticket format class provides Protect() and Unprotect() methods for securing cookie before sending out and validating an incoming cookie respectively. Internally, this format class also runs validation on the JWT using the inbuilt JwtSecurityTokenHandler class and the ValidateToken() method will throw appropriate exceptions if the token has expired or if its otherwise invalid. These exceptions, however, don’t bubble out to the consuming application, instead, null is returned from the Unprotect() method which triggers the default sign-out behaviour in ASP.NET Core apps as the cookie authentication handler detects a null authentication ticket.

Please also note that this custom ticket format is only useful if you are using a cookie based authentication in an ASP.NET Core app, if you are building a Web API application chances are you are going to go down the token based auth route in which case, you don’t have to use the custom ISecureDataFormat implementation.

As you can imagine on its way out the cookie will be serialised -> encrypted -> encoded and on its way in for validation will be decoded -> decrypted -> deserialised.

In the AccountController, the Login action now becomes:

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(UserCredentials userCredentials,
	string returnUrl = null)
{
    ViewBag.returnUrl = returnUrl;
    var returnTo = "/Account/Login";

    // Replace this with your custom authentication logic
    if (userCredentials.Username == "user1" &&
        userCredentials.Password == "pass1")
    {
        var userInfo = new UserInfo
        {
            FirstName = "UserFName",
            LastName = "UserLName"
        };

        var accessTokenResult =
                  tokenGenerator.GenerateAccessTokenWithClaimsPrincipal(
                    userCredentials.Username,
                    AddMyClaims(userInfo));

        await HttpContext.SignInAsync(accessTokenResult.ClaimsPrincipal,
            accessTokenResult.AuthProperties);

        returnTo = returnUrl;
    }

    return RedirectToLocal(returnTo);
}

Doing HttpContext.SigninAsync() is the standard way to sign-in in ASP.NET Core in a bare bones fashion. You don’t have to use the ASP.NET Identity system at all to use cookie authentication as is evident so far.

That is pretty much it.

You can create a Logout action as well which is even simpler than all that above:

[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Logout()
{
    await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);

    return RedirectToAction(nameof(HomeController.Index), "Home");
}

The default cookie authentication that ASP.NET Core provides out of the box along with the identity system is probably enough for most web front end based applications but the amount of boilerplate that the template generates can be quite confusing especially if you are debugging it and trying to understand what is happening under the hood. That’s what happened to me which forced me to build this authentication feature myself adding just the bits that I needed. This way I ended up having a simpler code base and better understanding of how ASP.NET Core’s default cookie authentication templates work.

Json Web Tokens are a nicer way to encode authorisation information and validate it as a part of each request than maintaining any server side state and putting all this in cookies just takes the responsibility of sending it with each request away from your shoulders too. Quite neat!

Update 16-Nov-2017: For now, if anyone wants to play with the code will need to copy and paste from this post, over the coming weeks as I find some time I will put the code for token generator and a reference application on GitHub for readers to play with more easily. Apologies for any confusions because of this.

Update 18-Nov-2017: Following reader feedback, the post has been updated to reflect the most current code and the code itself has been put on GitHub for people to download and play with (both the library and a reference application). Sorry, no nuget package yet but soon.

Tagged with: , , , , , , , , , , , , , , , , , , , , , , , , ,
Posted in API, architecture, design, ASP.NET Core, Azure Storage SDK, Microsoft Azure, refactoring, RESTful, software architecture, SOLID principles, Uncategorized
8 comments on “JWT Token Authentication with Cookies in ASP.NET Core
  1. Liran Friedman says:

    You don’t specify all the references needed for your code so it doesn’t compile. You should at least provide a sample project to download and have a look at.

    • artineering says:

      I do apologise for this. The IJwtTokenGenerator is just my invention its not a part of any standard package and hence the compiler errors. You are right, I should have either mentioned this in the blog or provided the code separately for readers to download or play with. I have since changed the main interface and that token generator is in my private nuget feed as a package, I might put that on GitHub if its useful for people in similar situation.

      I will edit this post in the mean time so people who want to play with the code can just copy and paste from my post. Over the coming weeks I will also put the code on GitHub so its easier to play with it.

      Thanks for your comment. Appreciate the feedback.

      Aman

      • Liran Friedman says:

        I’m now running it to test the outcome. If you’ll post the Nuget or the Github page here I’ll be happy to have a look at it 🙂

  2. artineering says:

    It will take me a couple days to get it out on GitHub, I will update the post when I have done it then you can have a look.

  3. Liran Friedman says:

    BTW, you have this line of code in the ConfigureServices method within the Startup.cs file:
    var serialiser = services.BuildServiceProvider().GetService<IDataSerializer>();

    and the serializer return null…

  4. Denis Vakulishin says:

    What the reason to encrypt ticket with embedded JWT? So, main question, why we need JWT if we already encrypt and protect ticket with data ?

  5. artineering says:

    Well, its the other way round. The JWT is embedded inside the encrypted authentication ticket its just a way to use JWT with cookie based auth following the standard cookie encryption protocol in ASP.NET Core. You can just as easily use pure JWT based authentication as well, as is normally done in RESTful stateless APIs. The only difference being cookies are sent by the browser automatically so you don’t have to write code for it in JS whereas a JWT needs to be included in the Authorization header everytime you want to access a secure resource.

    One key thing with either approaches is that the tokens/tickets shouldn’t be very long lived and they should have a set expiry time to avoid any chances of replay attacks where an attacker could hijack still valid token/cookie and send it to the server pretending to be a genuine user. Also, never store any sensitive information about your users in either approach to further reduce the attack surface area. Hope this helps.

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

Categories
Follow Artineering on WordPress.com
Links
%d bloggers like this: