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 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 Login(UserCredentials userCredentials,
string returnUrl = null)
{
// I will fill this in a bit...
}
view raw login.cs hosted with ❤ by GitHub

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 userClaims);
string GenerateAccessToken(string userName,
IEnumerable 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. Update Aug-2021: Don’t put PII (Personally Identifiable Information) or other security critical information like roles, permissions etc in the JWT because this information will most likely not be encrypted so if a token were to get compromised, you’d be leaking critical information to bad actors that can help make their jobs easier! Don’t just assume that the JWT is clean and honest, always verify the claims and permissions against an identity/authz database before letting the request through. Better to be slower but safer than blazing fast but with security holes the size of a woolly mammoth!

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 IHostingEnvironment Environment {get; set;}
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,
TicketSerializer>();
services.AddScoped(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>(),
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 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);
}
view raw LoginAction.cs hosted with ❤ by GitHub

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 Logout()
{
await HttpContext.SignOutAsync(
CookieAuthenticationDefaults.AuthenticationScheme);
return RedirectToAction(nameof(HomeController.Index), "Home");
}
view raw Logout.cs hosted with ❤ by GitHub

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.

33 Replies to “JWT Token Authentication with Cookies in ASP.NET Core”

  1. 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.

    1. 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

      1. 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. 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. 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. 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. 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 ?

  6. 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.

  7. Can you show an example using your helper and doing a claims based authorization? I have added a role claim of “admin” and attempt to use [Authorize(Roles = “Admin”)] on both an API controller or View controller with no success.

    1. Hi Jason,

      Your question has actually lead to a couple of simple changes to the helper that I am going to be pushing out in a couple of days and that should make it easier to use the helper without cookies in WebAPI and with cookies in ASP.NET Core MVC scenarios. I will then post a basic sample of how you can achieve what you asked.

      In the mean time these links might be of help: https://docs.microsoft.com/en-us/aspnet/core/security/authorization/roles?view=aspnetcore-2.1#policy-based-role-checks

      https://docs.microsoft.com/en-us/aspnet/core/security/authorization/claims?view=aspnetcore-2.1

      These form the basis of the change and the sample that’s coming up. I will reply to your comment once this is done so you can have a playaround. Cheers for your question, good one!

    2. Ok…that’s done! Code’s on Github. Checkout the Web API project, I have added a policy based claims check sample in the Startup.cs file and then after “authentication” in the Auth controller, I added the appropriate claim to the list of my claims that then get wrapped into the token by the helper. Finally, the [Authorize(Policy=”RequiresAdmin”)] attribute on the Values controller will automatically take care of ensuring that you are only able to access the endpoints in it if you have admin rights claim.

      Was this what you were looking for? Suggestions/PRs are always welcome.

  8. Hi your code is so good, i implemented but i have some problems with the token renew, please can you suggest some steps to code that functionality, thanks so much, your code has helped me so much 🙂

  9. I have a question. What if I want to build an app as two (hosted separately) projects: webapi and (empty) web project with just index.html (to serve a vue.js-based frontend). How to store jwt securely then? How to avoid localStorage? Does storing it inside a cookie still apply? Thank you!

    1. You can store the token in the sessionStorage instead which gets cleared when you close the browser window, alternatively, you can also use cookies because XHR requests (which I presume you will be making from the Vue.js front end) will (should?) by default include any ASP.NET cookie data along with any requests you send anyway.

      1. Can we somehow use AntiForgeryToken given we’re on different hosts: the backend and frontend app?

  10. That can work as well, although I am not familiar with Vue.js so I am not sure how you would go about doing that.

  11. Can we create machine specific token, means If I generate jwt token then that other user using different machine should not be able to use same token for accessing web api ?

  12. first of all THANK YOU!!!
    Now here is my question
    What if the token is generated by another web app. And I am supposed to use that token to implement the cookie based implementation. How would I handle that ?
    Any hint/example/resource link is much appreciated!
    Thanks in advance!
    -RonniE

    1. You’re welcome!

      I am sorry, I am not sure I understand your question. Are you talking about authenticating via another authentication API for e.g. Google SignIn etc ?

  13. Couldn’t find Environment.ApplicationName in any way

    1. Hi Daniel,

      If you look at the GitHub repo for this helper library, you will find that the `ApplicationName` property is on the `IHostingEnvironment` interface (https://github.com/explorer14/JwtAuthenticationHelper/blob/master/src/JwtAuthenticationHelper/Extensions/ServiceCollectionExtensions.cs#L45).

      Hope this helps. Sorry for the confusion, in an older version I must have created a local property called Environment of type IHostingEnvironment. Thanks for pointing this out.

      Cheers

  14. Problem with Environment.ApplicationName, link to the answer to similar question is broken.
    Also, had problems with services.AddScoped(compiler couldnt implicitly convert from TicketSerializer to IDataSerializer.

    1. Odd! I wouldn’t have expected any compiler errors as long as you have all the dependencies installed, just a matter of running `dotnet build`. I just cloned the repo on a different machine and built it from scratch myself without errors. How are you building it? Perhaps try clean and rebuild!

      Fixed the link btw!

    2. IDataSerializer is expecting a TModel to be specified. TicketSerialize implements IDataSerializer

      1. Hm, it keep stripping out my text because of the angle brackets. I thought I put it correctly the first time
        IDataSerializer with a model of AuthenticationTicket

  15. Excellent work! For my use case, I needed to use a JWT that was used for authentication and authorization, couldn’t be stored in local or session storage, and inaccessible to any JS code. I create a JWT, encrypt the json object being sent back to the client, and package it into an HttpOnly cookie. I wrote middleware to unwrap the JWT from the cookie being sent back with the client request and append to the header of the incoming request to carry out normal authentication/authorization using a JWT. Your examples were an excellent guide, and I learned just as much from how well structured and documented your code is. Thank you!

    1. Thanks and I am really glad it helped, mission achieved! 🙂 You did the right thing being careful not to expose JWT to JS code and using encrypted cookie, its a secure practice. Good on you!

  16. It’s very inconvenient this is not supported out of the box. your solution works beautifully but then i realized there was a simpler approach.

    I settled for setting a cookie upon login manually instead of using HttpContext.SignInAsync

    Then i added a piece of middleware that simply copies the cookie value to the Authorization header and it all works from there on.

    p.s. This blog does now allow me to select text

    1. Interesting approach! Care to share the code? And you can select text from my blog, its just that the selection seems to have a transparent backgroud so it makes it look as though nothing is selected even though, it is. 🙂 Its odd and I haven’t been able to figure a way out of it yet.

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 )

Facebook photo

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

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.