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.
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:
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).
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.
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.
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.
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.
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.
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.