Issuing and authenticating JWT tokens in ASP.NET Core WebAPI – Part II


Introduction

This post is the second part of an example of how you can issue JWT tokens with ASP.NET Core 1 and automatically control access to bearers through the simple application of an [Authorize] attribute (specifically focusing on claims-based authorisation using ASP.NET Core MVC’s policy features) in a Web API project.

Part I addressed how you could issue the JSON Web Tokens and, in this part, I will cover the authorisation aspects.

This post is therefore organised as follows:

  1. Configuration – addresses what needs to be added to the middleware and services in order for the auth setup to work
  2. You Shall Not Pass! – I couldn’t help myself, but yes, access control!
    1. Quick Revision – how we got here
    2. Authorise – setting up a controller to test all this stuff
    3. Testing – actually testing all this stuff
      1. Access Granted
      2. Access Denied

Once again I wish to reiterate that, if you would like to go straight to production-ready frameworks, both IdentityServer as well as OpenIddict are actively developed and maintained.

Requirements

In order to make sense of this post, you’d most likely need to have read Part I, however, if you would prefer just getting straight to the code, you can download/fork the example project and reference the relevant sections as we go.

Configuration

First things first, we need to add a claims-based authorisation policy. This is easily done in the ConfigureServices method in Startup.cs

What this does is add a policy named DisneyUser, with the requirement that the DisneyCharacter claim on the incoming token payload have the value of IAmMickey (I will get to how the policy is applied a little bit later).

The next thing that is required, is to tell ASP.NET that incoming requests might contain JWTs. Fortunately, there is middleware support for this (although it does require that you add the Microsoft.AspNetCore.Authentication.JwtBearer package as a dependency in your project.json file).

Adding middleware is done in the Configure method of Startup.cs and since this is driving authorisation, it needs to be added pretty much first in the middleware pipeline (in case you did not know this, the order in which you add middleware is important):

  • Lines 8 – 23: sets up the validation parameters that we’re going to pass to ASP.NET’s JWT bearer authentication middleware (there are many more options available than what are utilised here, for reference, TokenValidationParameters live in the Microsoft.IdentityModel.Tokens namespace).  I believe the parameters set in this example are reasonably self-explanatory, but if anything is unclear, feel free to ask in the comments.
  • Lines 25 – 30: add the JWT token bearer middleware that tells the application that we expect JWT tokens as part of the authentication and authorisation processes and to automatically challenge and authenticate.

That’s it for configuration, next up, restricting access to controllers with authorisation policies.

You Shall Not Pass!

Quick Revision

  • In Part I, I set up claims identities in JwtController.cs and gave the user MickeyMouse, a claim named DisneyCharacter with the value IAmMickey (refer back to lines 110 – 134 of that file).
  • In the Configuration  section above, I added a DisneyUser authorisation policy to the services collection.

Authorise

In order to test the authorisation setup, we need a controller that can serve our requests. For this, I created a very basic JwtAuthTestController

  • Line 7: take note of the route configuration for the controller
  • Lines 20 – 32: this is the only method on this controller
    • Line 20: tell the application that this method expects GET requests
    • Line 21: tell the authentication middleware to apply the DisneyUser authorisation policy to this mehod. This will ensure that ONLY bearers of JWTs that fulfill the claim requirements that we set up in Configuration are allowed access to this method
    • Lines 22 – 32: execute some basic logic in the controller method and return a response.

Testing

(The basic test setup is going to be the same as for that in Part I. For ease of reference, I repeat the instructions here)

For the testing and validation part, you’ll need to have some way to send requests to the application.  I am particular to Postman and will be using it throughout.

  1. Fire up the example solution/your application
  2. If you are using the example solution, the application’s URL is http://localhost:1783/ (configured in launchSettings.json)
  3. Start up Postman

Access Granted

Before we can test the access control, we need a bearer token to add to our requests.

Configure a POST to http://localhost:1783/api/jwt with ‘x-www-form-urlencoded’ selected under Body. Provide two key/values for the form, ‘username’ as ‘MickeyMouse’ and ‘password’ as ‘MickeyMouseIsBoss123’ (remember these values are hard coded in the GetClaimsIdentity method on the JwtController)

MickeyMousePOST

Click ‘Send’ and wait (if you are running your solution through VS in Debug mode, you can put some breakpoints into the code to trace the process flow).  If everything was set up correctly, you should receive some JSON in the response body that looks similar to the below:

MickeyMousePOSTResponse

As you can see, the token expires in 5 min (300 seconds), so you need to move fast! (OK, not really, you can always just send another request, or even change the JwtIssuerOptions.ValidFor setting and recompile, so don’t stress)

Copy the token (everything between the “”s for access_token) and create another request in Postman, but this time a GET. Provide the relevant request endpoint (URL) which, if you are using the sample application, is http://localhost:1783/api/test and, under the Headers tab (not Authorization!) add a key/value pair where the key is Authorization (NB! Take note of the American spelling!) and the value is ‘Bearer blahblahblahyourtokencontenthereblahblahblah’. In other words, you need to provide, as key, the word Bearer followed by a single white space, followed by the content of the access_token value you copied above response above (excluding the “”s).

MickeyMouseGET

In the screenshot, the token is cut off, but it looks something like this in plain text:

Note: this value will obviously differ every time you request a token, so please do not use this and expect it to work, you must use the token your token server provides you with!

If everything was set up correctly, you should receive a response such as the one below (check out the Body)

MickeyMouseGETResponse

Access Denied

OK, so this is all nice and well, the user we expected to have access to that method, had access to that method, but now we also need to confirm that users who do not have the necessary claim credentials are denied access.

Following pretty much the exact same steps as above, set up a POST method for our unauthorised user (note that this user is authenticated, i.e. a valid user, but unauthorised, in other words, although the user is a legitimate user, he/she does not have permission to access the method under test).

The user we set up for this is NotMickeyMouse (refer again to GetClaimsIdentity in JwtController)

NotMickeyMousePOST

Once again, copy the bearer token into the Authorization header for a GET request (you can use the exact same GET as before and simply replace the Bearer with the one you get back from NotMickeyMouse). This time, however, you will notice that access is denied.NotMickeyMouseGETResponse

And that, ladies and gentlemen, is ASP.NET’s identity middleware taking care of access control based on JSON Web Tokens through a claims-based authorisation policy!

In Closing

I recognise that the above could be a touch confusing if you have never worked with Postman or don’t fully understand how the HTTP requests are set up, so please do not hesitate to ask in the comments below if anything is unclear. I will do my best to shed further light on any issues you might encounter.

Cheers!

(If you got this far and liked what you read, consider following me @thewillhallatt if you would like to be notified of future articles)

 

Advertisements

55 Replies to “Issuing and authenticating JWT tokens in ASP.NET Core WebAPI – Part II”

  1. This is fantastic William. I appreciate it very much. It’s so very difficult to find a straightforward article on 1) issuing a JWT and 2) consuming it via claims in asp.net core. Identity Server 4 is hard to follow as there are essentially no “ver 4 docs”, and the water gets deep quickly, so your post is very enlightening and helpful!

    Quick question:
    Id Svr 4 uses a .pfx certificate to sign. Do you feel that’s necessary for production? …or would your “secret-signing” example be just as effective if we kept the secret-key in an Azure Environment setting (ie not in code as you mentioned)?

    Thanks again!

    1. Hi Mike!

      I will answer your query to the best of my ability, but please note that my knowledge on this topic is still incomplete.

      As far as I know, the standard for production is certificates. Having said that, the primary requirement when signing tokens is that the secret signing key should only be available to the issuer and the consumer. How you ensure the confidentiality of that secret key is up to you. I don’t know the difference in the risk profiles of environment settings vs signing certificates (it might be huge, it might be non-existent) so I would start there if I were you and seriously wanted to pursue the Azure environment setting option.

      You might find these useful:

      https://brockallen.com/2015/06/01/makecert-and-creating-ssl-or-signing-certificates/
      http://www.hackered.co.uk/articles/create-signing-certificate-jwt-token

      Cheers!
      William.

    1. Hi there! Token expiry is enforced by the JWT Bearer Authentication middleware. Have a look at the ‘Configure’ method in Startup.cs (pay particular attention to how the TokenValidationParameters object is initialised).

  2. Hi William,
    I liked your article and helped me to implement the same.I had a clarification if we have given the [Authorize] in a method and if it fails with the Token validation set up in startup can we customize our response and send it back to the user other than 401 and 403.Please let me know if u have any insights in it.
    Liju

    1. Hi Liju,

      I have never tried to do that before, but you should be able to do so through a custom handler (they Handle method you have to implement has access to the AuthorizationContext). Be wary of returning information on authentication or authorisation failures though, the more information malicious users have, the easier it is for them to figure out attack vectors.

  3. Hi thanks for the article (Part I and II), i have a question everything is working good, how to get the token info in the Api Controller ? My code looks like this
    [HttpGet]
    [Authorize(ActiveAuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
    public IEnumerable GetFieldType()
    { //How to get the Token here

    Thanks

  4. These blog posts are amazing work. I know it must have taken you a lot of time to writes these, so thank you for your hard work. I really appreciate it.

  5. Hi William,
    you really made a very good job, so thank you very much! 🙂
    …but what about refresh tokens? 🙂 Your tutorials are the best in the net!
    Andrea

    1. Hi Patrick,

      Yes. How you do it will be very dependent on your implementation, but the gist of it is to have an identifier in the token’s payload that you can use to retrieve user information from your DB.

  6. William Hello!
    First, thank you for sharing your knowledge.
    Please, when the application will validate the token, it performs an HTTP request in Audience even being in the same application?
    Thank you very much!

    1. Hi Tiago, if I understand your question correctly, then yes. Remember that the application is (ideally) sitting behind a RESTful API, so there is no concept of state (i.e. the server doesn’t “remember” who is busy doing what at any given point in time). Does that make sense?

      1. Means that there is no type of user information store, and your token, but that all information about the user is within the token itself?

      2. No, no, you will still need a DB with user information. The token carries authentication and authorisation information to determine which parts of your application the user has access to, but everything about the user itself will have to be retrieved from the DB through some sort of identifier (that you will also put in the token). You should never put sensitive information (such as personally identifiable information or bank details) in a JWT! It seems to me that you need to first study what a JWT actually is (and how to use it) before proceeding further down the route of building an auth server. A good starting point is this: https://jwt.io/

      3. No, no, about user information are in a database I know. My question about the information stored on it would validate the “session” of the user, since no state in the authentication server (in the same application as he said) there would also be a “memory” valid token associated with the user etc. All my question was only on the application make an HTTP request to itself. But ok, I understand, thank you!

  7. Very good tutorial. Thanks for the post! I am having one last issue. Instead of getting a 403 when using a non- disneyuser policy, I am getting a 404. Do you know what could cause this? Thanks!

  8. Great tutorial, William! Everything works like a charm with perhaps one exception. I am experiencing an expired token whenever my localhost session has run for more than 5 minutes and I issue a POST to get a ‘new’ token. This is the point in your tutorial where you say “As you can see, the token expires in 5 min (300 seconds), so you need to move …”. Maybe I am not understanding Postman or API lifecyle in the real world, but shouldn’t there be logic inserted here to re-issue or refresh the token (ie, one that updates the IssuedAt property)? One thing that differs between my implementation and yours is I am using your logic in a combined MVC Web App with API endpoints rather than using it in a pure Web Api app.

    1. Hi Carl, you are right re refreshing and re-issuing, there is no logic in my demo application to refresh tokens as it is not meant to be a solid solution but purely to demonstrate how JWT’s can be used in conjunction with ASP.NET Core. Please don’t ever, EVER use the code as-is in anything resembling production. If you want to look at production ready libraries, IdentityServer4 is currently the most up to date (as far as I know). Cheers!

    1. Hi Tri, Once a user has a token, and the token is valid, the user can still authenticate with it even if the user gets deleted from the system (this is one of the down sides of using JWT’s). One way to defend against this is through the use of refresh tokens. I’d recommend that you look at existing production-ready libraries to deal with this on your behalf (e.g. IdentityServer4).

      1. well I need to generate a anonymous token for guests that will enable them to access a page on the web app only for limited time

      2. Oh I see. I have not ever had to deal with this scenario, but if I had to give it a shot, I’d probably start by creating general users that expire after a certain time through one way or another.

      3. I want to generate an anonymous token to enable unauthenticated users to access specific resource on the web app for a limited time

  9. I’m finding that when i retrieve my token it is already expired and that my .net core project is using the same token from when the projected it run (Never updating the DateTime.UtcNow). Even when it goes through the login process i find that the “Not Before” is correct but the “Expiry” is in the past. Is this something I have done wrong when following your guide?

  10. Hi William,

    Thank you so much for this (and Part 1)… I’ve been scratching my head for awhile searching for a straightforward JWT server implementation in plain english, and the fact that this is in ASP.Net Core is a huge bonus! Helps me understand Core a little bit better too.

    One question. I understand this likely isn’t a short answer, and understand if it goes beyond the scope of what you would like to respond to… but (sorry if I don’t have the right terminology), let’s say I want to store a block of metadata somewhere in the token in the form of JSON) say, for example something like

    metaData: {“roles”: [“admin”]}

    Then check upon the existence of a certain role via something like options.AddPolicy(…) so that I can make auth work against a custom schema. What might be an example of how to write this in

    a) JwtController.GetClaimsIdentity – Would this just be stored in a custom-named Claim?

    b) Startup.ConfigureServices – How might I go about parsing JSON and checking for existence of a specific role in the provided JWT?

    Any help would be greatly appreciated. Thanks again for this 🙂

  11. Hi William,
    Excellent article,
    Thank you very much for this tutorial.
    The only thing that not works for me, is the last request for my :
    http://localhost:18940/api/test

    Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJNaWNrZXlNb3VzZSIsImp0aSI6IjhmMzE3N2ExLTdjZDEtNDM1Mi1iZjAxLTc2YmFjNjg2OTZlZiIsImlhdCI6MTQ4MTQ0NzE5NCwiRGlzbmV5Q2hhcmFjdGVyIjoiSUFtTWlja2V5IiwibmJmIjoxNDgxNDQ3MTkzLCJleHAiOjE0ODE0NDc0OTN9.7Ebo7_zMh3tmm8el8BIhw6ZacKA66Xh18NVpUf1-KUU

    that i get from Post

    I get 401 unauthorized status 🙂

    You have an idea what this could be?
    Thanks a lot!

  12. hi William, very good article. i’ve a question, at first login the system works, i get status 200 but when token expires and I generate new one with the same credential I get 401 status why? Thanks and good work!!

    1. Hi Stefano, I am not sure as I never really tested this demo code for robustness. It could be the time stamps. Perhaps just check if the newly generated tokens have correct, updated time stamps? You might have to dig around a bit to solve this one 🙂 Cheers!

      1. Hi William, thanks for reply!!, the problem is timestamp, i’ve change for test the jwtoption class and now works:
        private DateTime _nb;
        public DateTime NotBefore
        {
        get { return DateTime.Now; }
        set { _nb = value; }
        }
        private DateTime _ia;
        public DateTime IssuedAt
        {
        get { return DateTime.Now; }
        set { _ia = value; }
        }
        private TimeSpan _vf;
        public TimeSpan ValidFor
        {
        get { return TimeSpan.FromMinutes(1); }
        set { _vf = value; }
        }

  13. hi! William, thank you very much for this article, it is very good… Could you give an example in case logout for specific user logged in server side? … Greetings from Peru!!

  14. Great article, William! Congratulations!
    I wanted to ask you if you have an idea how can I get rid of the “Policy” part in the Authorize attribute?
    I would like my attribute to be just [Authorize] and not [Authorize(Policy = “DisneyUser”)]. Do you have an idea how can I achieve this?

    Many thanks,
    Anatoli

  15. Congratulations! But I have a question.
    how can I redirect action If you authorize?

    [Authorize(Policy = “DisneyUser”)]

    1. Hi there!

      Do you mean redirect if unauthorised? I might be wrong, but in my opinion a redirect would not be the responsibility of the Web API as it would increase coupling between our API and the client (i.e. the routing is not the responsibility of the RESTful Web API). The API will return an unauthorised response, which you can then process on the front end for a redirect.

  16. Hi William,

    You suggests IdentityServer for refresh tokens. In my api, I am checking users via PostgreSql functions. To your knowledge, can IdentityServer work with that?

    Thanks,
    Sara

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