Using JSON Web Tokens with Katana and WebAPI

Thursday, January 15, 2015

A common question I’ve been getting is how to use tokens with ASP.NET, specifically JSON Web Tokens (JWT) with ASP.NET WebAPI where the OAuth server and the resource server are the same. In other words, you have a single web site that wants to both issue tokens to authenticated clients and verify the same tokens on incoming requests.

Setup

To pull this off with Microsoft’s OWIN based components you’ll need the Microsoft.Owin.Security.Jwt package from NuGet, which brings in a few dependencies, including System.IdentityModel.Tokens.Jwt.

The app building setup code is simple (it is the details that are a bit trickier). We need:

  1. OAuth middleware to issue a token
  2. Authentication middleware to validate a token and set the user identity for a request.

OWIN startup code can register both of these middleware pieces using app builder extension methods.

[assembly: OwinStartup(typeof(OwinStartup))]
namespace NgPlaybook.Server.Startup
{
   
    public class OwinStartup
    {
        public void Configuration(IAppBuilder app)
        {
            app.UseOAuthAuthorizationServer(new MyOAuthOptions());            
            app.UseJwtBearerAuthentication(new MyJwtOptions());
        }
    }
}

 

Let’s talk about the two custom classes in the startup code – MyOAuthOptions (which helps in issuing tokens) and MyJwtOptions (which helps validate tokens).

OAuthOptions

The devil in the details starts with MyOAuthOptions, which is a wrapper for the OAuth server options.

public class MyOAuthOptions : OAuthAuthorizationServerOptions
{
    public MyOAuthOptions()
    {
        TokenEndpointPath = "/token";
        AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(60);
        AccessTokenFormat = new MyJwtFormat();
        Provider = new MyOAuthProvider();
        #if DEBUG
            AllowInsecureHttp = true;
        #endif
    }
}

Here we setup the server to listen at /token, just like the token middleware in an out of the box Web API project with authentication.

The AccessTokenFormat property references an object implementing ISecureDataFormat<AuthenticationTicket>, which in this case is our own MyJwtFormat. The purpose of this class is to encode and sign information about an authenticated user into a string.

JwtFormat

An ISecureDataFormat<T> object has a Protect method which does all the heavy lifting. The AuthenticationTicket object given to this method is constructed from claims put together by the server when a user successfully authenticates, we’ll look at this later.

public class MyJwtFormat: ISecureDataFormat<AuthenticationTicket>
{
    private readonly OAuthAuthorizationServerOptions _options;

    public MyJwtFormat(OAuthAuthorizationServerOptions options)
    {
        _options = options;
    }

    public string SignatureAlgorithm
    {
        get { return "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256"; }
    }

    public string DigestAlgorithm
    {
        get { return "http://www.w3.org/2001/04/xmlenc#sha256"; }
    }

    public string Protect(AuthenticationTicket data)
    {
        if (data == null) throw new ArgumentNullException("data");

        var issuer = "localhost";
        var audience = "all";
        var key = Convert.FromBase64String("UHxNtYMRYwvfpO1dS5pWLKL0M2DgOj40EbN4SoBWgfc");
        var now = DateTime.UtcNow;
        var expires = now.AddMinutes(_options.AccessTokenExpireTimeSpan.TotalMinutes);
        var signingCredentials = new SigningCredentials(
                                    new InMemorySymmetricSecurityKey(key), 
                                    SignatureAlgorithm,
                                    DigestAlgorithm);
        var token = new JwtSecurityToken(issuer, audience, data.Identity.Claims, 
                                         now, expires, signingCredentials);

        return new JwtSecurityTokenHandler().WriteToken(token);
    }

    public AuthenticationTicket Unprotect(string protectedText)
    {
        throw new NotImplementedException();
    }
}

Of course many of these details, like the audience, issuer, expiration time, and the key, you’ll want to keep in a configuration file. Also, instead of using InMemorySymmetricSecurityKey, you might want to use a certificate and X509SecurityKey.

Note the OAuth server never uses the Unprotect method. You’d think Unprotect might come in useful when verifying  a token, but that’s just not how these components work.

Now, back to how a user is actually authenticated and given claims that are encrypted into the token. That’s the responsibility of the OAuth provider that plugs into the OAuth server.

OAuthProvider

public class MyOAuthProvider : OAuthAuthorizationServerProvider
{
    public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        var identity = new ClaimsIdentity("otc");
        var username = context.OwinContext.Get<string>("otc:username");
        identity.AddClaim(new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", username));
        identity.AddClaim(new Claim("http://schemas.microsoft.com/ws/2008/06/identity/claims/role", "user"));
        context.Validated(identity);
        return Task.FromResult(0);
    }

    public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    {
        try
        {
            var username = context.Parameters["username"];
            var password = context.Parameters["password"];
            
            if (username == password)
            {
                context.OwinContext.Set("otc:username", username);
                context.Validated();
            }
            else
            {
                context.SetError("Invalid credentials");
                context.Rejected();
            }
        }
        catch
        {
            context.SetError("Server error");
            context.Rejected();
        }
        return Task.FromResult(0);
    }
}

The ValidateClientAuthentication method is the place where you’ll make calls to a database or membership system to determine if a user is providing the correct credentials. If so, the method will add data into the OWIN request context for GrantResourceOwnerCredentials to pick up and place into claims (which ultimately become part of the auth ticket and serialized into the JWT). The code above only simulates validation by granting access to any user whose password is the same as their username.

The above code is all you’ll need to grant tokens. We also wanted middleware in the application to verify incoming tokens, and back in the beginning of the post we setup UseJwtBearerAuthentication using the following options class.

JwtBearerAuthenticationOptions

public class MyJwtOptions : JwtBearerAuthenticationOptions
{
    public MyJwtOptions()
    {      
        var issuer = "localhost";
        var audience = "all"; 
        var key = Convert.FromBase64String("UHxNtYMRYwvfpO1dS5pWLKL0M2DgOj40EbN4SoBWgfc");;

        AllowedAudiences = new[] { audience };
        IssuerSecurityTokenProviders = new[]
        {
            new SymmetricKeyIssuerSecurityTokenProvider(issuer, key)
        };
    }
}

You’ll need the issuer, audience, and most importantly the key to matchup with the values provided by the JWT formatter we looked at earlier.

JWT In Action

To get a token with the above code in place, a client needs to POST form encoded credentials to the /token endpoint. Be sure to include a grant_type of password.

POST /login HTTP/1.1
Host: localhost:17648
Content-Length: 51
Accept: application/json, text/plain, */*
Origin: http://localhost:17648
User-Agent: Mozilla/5.0 ...
Content-Type: application/x-www-form-urlencoded
username=sallen&password=sallen&grant_type=password

If the credentials are correct, the server will respond with a JSON payload that includes the access_token.

{"access_token":"eiJ9............AkUg","token_type":"bearer","expires_in":119}

Subsequent requests just need to include the token in an Authorization header.

GET /api/secret HTTP/1.1
Host: localhost:17648
Accept: application/json, text/plain, */*
Authorization: Bearer eiJ9............AkUg

And now you can protect API and MVC controllers with the usual Authorize attribute.

[Authorize]
public class SecretController : ApiController
{
    public IHttpActionResult Get()
    {
        return Ok(new ModelThing());
    }
}

If you were to decode the token, you’d see it consist of a header and a payload that look like the following.

{"typ":"JWT","alg":"HS256"}

{"unique_name":"sallen","role":"user","iss":"localhost",
 "aud":"all","exp":1421127827,"nbf":1421127707}

Which demonstrates one of the features of JWT – all the claims are in the payload. Once the server decodes and verifies the information inside the token, the server has all the information it needs about a user.

Summary

There is a fair amount of code required to setup a JWT OAuth server with the ASP.NET middleware components, but I hope this code might give you a starting point to experiment with.


Comments
Dominick Friday, January 16, 2015
Or just use IdentityServer ;) https://thinktecture.github.io/Thinktecture.IdentityServer.v3.Documentation/
gravatar Paul Friday, January 16, 2015
We found the NuGet JWT package much simpler to use for our purposes: https://www.nuget.org/packages/JWT
gravatar Taiseer Joudeh Thursday, February 19, 2015
Hi Scott, Thanks for this post, I've some minor comments: 1- The method "ValidateClientAuthentication" should be used to validate the client_id and client_secret if you want to use OAuth refresh tokens, OAuth client credentials grant, or OAuth Authorization code grant, as well there is no need to set the resource owner username and password in the Owin context in this method. 2 - You can to do the validation (DB checks against your membership provider) for the resource owner in method "GrantResourceOwnerCredentials" by reading the UserName and Password directly from the "OAuthGrantResourceOwnerCredentialsContext" context, you can do this by calling context.UserName and context.Password. 3 - You can set the claims using "ClaimTypes.Role" instead of hard-coding it to "http://schemas.microsoft.com/ws/2008/06/identity/claims/role". As well it is better to use "sub" claim instead of claim type of "Name" which translates to "uniqe_name" claim; in order to adhere to JWT specifications (https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32#section-4.1.2), yet this is optional. I've posted about this using JWT tokens in Web Api here: http://bitoftech.net/2014/10/27/json-web-token-asp-net-web-api-2-jwt-owin-authorization-server/ so it might be useful for other readers. Thanks again for this great post.
gravatar Scott Thursday, February 19, 2015
Thanks, @Taiseer, you have a great post there (http://bitoftech.net/2014/10/27/json-web-token-asp-net-web-api-2-jwt-owin-authorization-server/).
Comments are closed.

My Pluralsight Courses

K.Scott Allen OdeToCode by K. Scott Allen
What JavaScript Developers Should Know About ECMAScript 2015
The Podcast!