The ValidateIssuerSigningKey and ValdiateIssuer properties indicate that the token’s signature should be validated and that the key’s property indicating it’s issuer must match an expected value. This is an alternate way to make sure the issuer is validated since we’re not using an Authority parameter in our JwtBearerOptions (which would have implicitly checked that the JWT’s issuer matched the authority). Instead, the JWT’s issuer is matched against custom values that are provided by the ValidIssuer or ValidIssuers properties of the TokenValidationParameters object.The IssuerSigningKey is the public key used for validating incoming JWT tokens. By specifying a key here, the token can be validated without any need for the issuing server. What is needed, instead, is the location of the public key. The certLocation parameter in the sample above is a string pointing to a .cer certificate file containing the public key corresponding to the private key used by the issuing authentication server. Of course, this certificate could just as easily (and more likely) come from a certificate store instead of a file.

In my previous posts on the topic of issuing authentication tokens with ASP.NET Core, it was necessary to generate a certificate to use for token signing. As part of that process, a .cer file was generated which contained the public (but not private) key of the certificate. That certificate is what needs to be made available to apps (like this sample) that will be consuming the generated tokens.

With UseJwtBearerAuthentication called in Startup.Configure, our web app should now respect identities sent as JWT bearer tokens in a request’s Authorization header.

Authorizing with Custom Values from JWT

To make the web app consuming tokens a little more interesting, we can also add some custom authorization that only allows access to APIs depending on specific claims in the JWT bearer token.

Role-based Authorization

Authorizing based on roles is available out-of-the-box with ASP.NET Identity. As long as the bearer token used for authentication contains a roles element, ASP.NET Core’s JWT bearer authentication middleware will use that data to populate roles for the user.

So, a roles-based authorization attribute (like [Authorize(Roles = "Manager,Administrator")] to limit access to managers and admins) can be added to APIs and work immediately.

Custom Authorization Policies

Custom authorization in ASP.NET Core is done through custom authorization requirements and handlers. ASP.NET Core documentation has an excellent write-up on how to use requirements and handlers to customize authorization. For a more in-depth look at ASP.NET Core authorization, check out this ASP.NET Authorization Workshop.

The important thing to know when working with JWT tokens is that in your AuthorizationHandler‘s HandleRequirementAsync method, all the elements from the incoming token are available as claims on the AuthorizationHandlerContext.User. So, to validate that a custom claim is present from the JWT, you might confirm that the element exists in the JWT with a call to context.User.HasClaim and then confirm that the claim is valid by checking its value.

Again, details on custom authorization policies can be found in ASP.NET Core documentation, but here’s a code snippet demonstrating claim validation in an AuthorizationHandler that authorizes users based on the (admittedly strange) requirement that their office number claim be lower than some specified value. Notice that it’s necessary to parse the office number claim’s value from a string since (as mentioned in my previous post), ASP.NET Identity stores all claim values as strings.

// A handler that can determine whether a MaximumOfficeNumberRequirement is satisfied
internal class MaximumOfficeNumberAuthorizationHandler : AuthorizationHandler<MaximumOfficeNumberRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MaximumOfficeNumberRequirement requirement)
    {
        // Bail out if the office number claim isn't present
        if (!context.User.HasClaim(c => c.Issuer == "http://localhost:5000/" && c.Type == "office"))
        {
            return Task.CompletedTask;
        }

		// Bail out if we can't read an int from the 'office' claim
        int officeNumber;
        if (!int.TryParse(context.User.FindFirst(c => c.Issuer == "http://localhost:5000/" && c.Type == "office").Value, out officeNumber))
        {
            return Task.CompletedTask;
        }

        // Finally, validate that the office number from the claim is not greater
        // than the requirement's maximum
        if (officeNumber <= requirement.MaximumOfficeNumber)
        {
            // Mark the requirement as satisfied
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

// A custom authorization requirement which requires office number to be below a certain value
internal class MaximumOfficeNumberRequirement : IAuthorizationRequirement
{
    public MaximumOfficeNumberRequirement(int officeNumber)
    {
        MaximumOfficeNumber = officeNumber;
    }

    public int MaximumOfficeNumber { get; private set; }
}

This authorization requirement can be registered in Startup.ConfigureServices with a call to AddAuthorization to add a requirement that an office number not exceed a particular value (200, in this example), and by adding the handler with a call to AddSingleton:

// Add custom authorization handlers
services.AddAuthorization(options =>
{
    options.AddPolicy("OfficeNumberUnder200", policy => policy.Requirements.Add(new MaximumOfficeNumberRequirement(200)));
});

services.AddSingleton<IAuthorizationHandler, MaximumOfficeNumberAuthorizationHandler>();

Finally, this custom authorization policy can protect APIs by decorating actions (or controllers) with appropriate Authorize attributes with their policy argument set to the name used when defining the custom authorization requirement in startup.cs:

[Authorize(Policy = "OfficeNumberUnder200")]

Testing it All Together

Now that we have a simple web API that can authenticate and authorize based on tokens, we can try out JWT bearer token authentication in ASP.NET Core end-to-end.

The first step is to login with the authentication server we created in my previous post. Once that’s done, copy the token out of the server’s response.

Now, shut down the authentication server just to be sure that our web API can authenticate without it being online.

Then, launch our test web API and using a tool like Postman or Fiddler, create a request to the web API. Initially, the request should fail with a 401 error because the APIs are protected with an [Authorize] attribute. To make the calls work, add an Authorization header with the value “bearer X” where “X” is the JWT bearer token returned from the authentication server. As long as the token hasn’t expired, its audience and authority match the expected values for this web API, and the user indicated by the token satisfies any custom authorization policies on the action called, a valid response should be served from our web API.

Here are a sample request and response from testing out the sample created in this post:

Request:

GET /api/values/1 HTTP/1.1
Host: localhost:5001
Authorization: bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IkU1N0RBRTRBMzU5NDhGODhBQTg2NThFQkExMUZFOUIxMkI5Qzk5NjIiLCJ0eXAiOiJKV1QifQ.eyJ1bmlxdWVfbmFtZSI6IkJvYkBDb250b3NvLmNvbSIsIkFzcE5ldC5JZGVudGl0eS5TZWN1cml0eVN0YW1wIjoiM2M4OWIzZjYtNzE5Ni00NWM2LWE4ZWYtZjlmMzQyN2QxMGYyIiwib2ZmaWNlIjoiMjAiLCJqdGkiOiI0NTZjMzc4Ny00MDQwLTQ2NTMtODYxZi02MWJiM2FkZTdlOTUiLCJ1c2FnZSI6ImFjY2Vzc190b2tlbiIsInNjb3BlIjpbImVtYWlsIiwicHJvZmlsZSIsInJvbGVzIl0sInN1YiI6IjExODBhZjQ4LWU1M2ItNGFhNC1hZmZlLWNmZTZkMjU4YWU2MiIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6NTAwMS8iLCJuYmYiOjE0Nzc1MDkyNTQsImV4cCI6MTQ3NzUxMTA1NCwiaWF0IjoxNDc3NTA5MjU0LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjUwMDAvIn0.Lmx6A3jhwoyZ8KAIkjriwHIOAYkgXYOf1zBbPbFeIiU2b-2-nxlwAf_yMFx3b1Ouh0Bp7UaPXsPZ9g2S0JLkKD4ukUa1qW6CzIDJHEfe4qwhQSR7xQn5luxSEfLyT_LENVCvOGfdw0VmsUO6XT4wjhBNEArFKMNiqOzBnSnlvX_1VMx1Tdm4AV5iHM9YzmLDMT65_fBeiekxQNPKcXkv3z5tchcu_nVEr1srAk6HpRDLmkbYc6h4S4zo4aPcLeljFrCLpZP-IEikXkKIGD1oohvp2dpXyS_WFby-dl8YQUHTBFHqRHik2wbqTA_gabIeQy-Kon9aheVxyf8x6h2_FA

Response:

HTTP/1.1 200 OK
Date: Thu, 15 Sep 2016 21:53:10 GMT
Transfer-Encoding: chunked
Content-Type: text/plain; charset=utf-8
Server: Kestrel

value

Conclusion

As shown here, authenticating using JWT bearer tokens is straightforward in ASP.NET Core, even in less common scenarios (such as the authentication server not being available). What’s more, ASP.NET Core’s flexible authorization policy makes it easy to have fine-grained control over access to APIs. Combined with my previous posts on issuing bearer tokens, you should have a good overview of how to use this technology for authentication in ASP.NET Core web apps.

Resources