02 December 2014
    Share via:

Prerequisites

Visual Studio 2012-2013, Microsoft ASP.NET Web API 2.2 (Obtained via Nuget), System.IdentityModel.Tokens.Jwt (Obtained via Nuget)

Overview

I originally planned to release a web application which documents technology stacks. Unfortunately, it looks like a SF startup beat me to it (although admittedly, my product slightly differs). I decided I am going to re-purpose my project into another one. In the meantime, I thought I’d share how I was authorising my application via JWT as there’s tons of approaches online and this is a singular combined solution from many sources. Due to the simplicity of my application, I thought an ideal way to authorize would be using Json Web Tokens (JWT) as it would mean I wouldn’t have to deal with sessions etc. Also, it was shiny and new(ish)…that’s reason enough for me.

So, to begin, I decided I wanted to intercept any request coming into my API to check whether the user has firstly specified a token and secondly, if so, whether it is valid or not. Delegating handlers are essentially pipelines in which a request is passed through for some processing to be done; these are also chain-able i.e. you could also have a DelegatingHandler for logging if you wished, so for this, they are perfect for authorising a user’s request using JWT. I added the JsonWebTokenHandler class which implements DelegatingHandler to achieve this (I will break it down into small chunks after the full source). NB: I realise this code is a little bloated and the class is doing too much, I thought it’s easier to demonstrate rather than injected separate dependencies in, it would be easy to separate token validation from token retrieval but I thought this was a little out of scope

public class JsonWebTokenHandler : DelegatingHandler
{
	protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, 
	CancellationToken cancellationToken)
	{
		HttpStatusCode statusCode;
		string token;

		var authHeader = request.Headers.Authorization;
		if (authHeader == null)
		{
			// Missing authorization header
			return base.SendAsync(request, cancellationToken);
		}

		if (!TryRetrieveToken(request, out token))
		{
			return Task<HttpResponseMessage>.Factory.StartNew(() => new HttpResponseMessage(HttpStatusCode.Unauthorized));
		}

		try
		{
			ValidateToken(token);
			return base.SendAsync(request, cancellationToken);
		}
		catch (SecurityTokenValidationException)
		{
			statusCode = HttpStatusCode.Unauthorized;
		}
		catch (Exception)
		{
			statusCode = HttpStatusCode.InternalServerError;
		}

		return Task<HttpResponseMessage>.Factory.StartNew(() => new HttpResponseMessage(statusCode));
	}

	private void ValidateToken(string token)
	{
		var tokenHandler = new JwtSecurityTokenHandler();
		var validationParameters = GetValidationParameters();

		SecurityToken validToken;
		IPrincipal principal = tokenHandler.ValidateToken(token, validationParameters, out validToken);

		Thread.CurrentPrincipal = principal;
		HttpContext.Current.User = principal;
	}

	private TokenValidationParameters GetValidationParameters()
	{
		return new TokenValidationParameters
		{
			ValidAudience = Constants.Constants.AllowedAudience,
			ValidIssuer = Constants.Constants.ValidIssuerName,
			IssuerSigningToken =
			new BinarySecretSecurityToken(Utility.Utility.GetBytes(Constants.Constants.SymmetricKey))
		};
	}

	private static bool TryRetrieveToken(HttpRequestMessage request, out string token)
	{
		token = null;
		IEnumerable<string> authorizationHeaders;

		if (!request.Headers.TryGetValues("Authorization", out authorizationHeaders) ||
		authorizationHeaders.Count() > 1)
		{
			return false;
		}

		var bearerToken = authorizationHeaders.ElementAt(0);
		token = bearerToken.StartsWith("Bearer ") ? bearerToken.Substring(7) : bearerToken;
		return true;
	}
}

The first portion attempts to get the Authorization header from the request, if it’s null, the user isn’t authorized so we’ll send the request through (If a method is annotated with the [Authorize] attribute, the user will receive an Unauthorized response). We then try and attempt to get the token, if this is unsuccessful I just return HttpStatusCode.Unauthorized immediately.

protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
    HttpStatusCode statusCode;
    string token;

    var authHeader = request.Headers.Authorization;
    if (authHeader == null)
    {
        // Missing authorization header
        return base.SendAsync(request, cancellationToken);
    }

    if (!TryRetrieveToken(request, out token))
    {
        return Task<HttpResponseMessage>.Factory.StartNew(() => new HttpResponseMessage(HttpStatusCode.Unauthorized));
    }

    //....
}

The TryRetrieveToken method attempts to get the Authorization headers from the request. It gets the first Authorization header and strips the token out after “Bearer “ string within the header.

private static bool TryRetrieveToken(HttpRequestMessage request, out string token)
{
    token = null;
    IEnumerable<string> authorizationHeaders;

    if (!request.Headers.TryGetValues("Authorization", out authorizationHeaders) || authorizationHeaders.Count() > 1)
    {
        return false;
    }

    var bearerToken = authorizationHeaders.ElementAt(0);
    token = bearerToken.StartsWith("Bearer ") ? bearerToken.Substring(7) : bearerToken;
    return true;
}

The next part of the method actually validates the token. This should be fairly self explanatory, I’ve wrapped the method which does the validation in a try catch block so I can set the status code to Unauthorized or InternalServerError dependant on the exception thrown. As you can see I create a token handler and then get the validation parameters. There’s lots of ways you can validate a token, I am using a valid audience string, valid issuer string and a secret token which is very basic. There are other options, please check out the class here. It’s important you select the correct security mechanisms for your own solution, this solution is very basic. Also, please note I am setting both the Thread.CurrentPrincipal and the HttpContext.Current.User, the latter is necessary if hosting in IIS.

	// ....

    try
    {
        ValidateToken(token);
        return base.SendAsync(request, cancellationToken);
    }
    catch (SecurityTokenValidationException)
    {
        statusCode = HttpStatusCode.Unauthorized;
    }
    catch (Exception)
    {
        statusCode = HttpStatusCode.InternalServerError;
    }

    return Task<HttpResponseMessage>.Factory.StartNew(() => new HttpResponseMessage(statusCode));
}

private void ValidateToken(string token)
{
    var tokenHandler = new JwtSecurityTokenHandler();
    var validationParameters = GetValidationParameters();

    SecurityToken validToken;
    IPrincipal principal = tokenHandler.ValidateToken(token, validationParameters, out validToken);

    Thread.CurrentPrincipal = principal;
    HttpContext.Current.User = principal;
}

private TokenValidationParameters GetValidationParameters()
{
    return new TokenValidationParameters
    {
        ValidAudience = Constants.Constants.AllowedAudience,
        ValidIssuer = Constants.Constants.ValidIssuerName,
        IssuerSigningToken = new BinarySecretSecurityToken(Utility.Utility.GetBytes(Constants.Constants.SymmetricKey))
    };
}

To hook this in so it our handler will be executed upon each request we now have to add this to the MessageHandlers collection and we do this from the WebApiConfig class. For this sample you should only be interested in the last line.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.MapHttpAttributeRoutes();

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new {id = RouteParameter.Optional}
            );

        var appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml");
        config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);

        config.MessageHandlers.Add(new JsonWebTokenHandler());
    }
}

This will ensure each incoming request from a client will be processing through our DelegatingHandler and set the Principal. This is all well and good but you also will probably need to be able to assign tokens and give them to clients. Here is a crude implementation of a token provider. Please note: You can do a lot more than this to increase a token’s complexity as and when needed such as using expiration times for tokens.

public class TokenProvider : ITokenProvider
{
    public string GenerateToken(string email)
    {
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(new[]
        {
            new Claim(ClaimTypes.Name, email), 
            new Claim(ClaimTypes.Role, "Admin")
        }),

            AppliesToAddress = Constants.Constants.AllowedAudience,
            TokenIssuerName = Constants.Constants.ValidIssuerName,
            SigningCredentials = new SigningCredentials(new
                InMemorySymmetricSecurityKey(Utility.Utility.GetBytes(Constants.Constants.SymmetricKey)),
                "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256",
                "http://www.w3.org/2001/04/xmlenc#sha256")
        };

        var tokenHandler = new JwtSecurityTokenHandler();
        var token = tokenHandler.CreateToken(tokenDescriptor);
        return tokenHandler.WriteToken(token);
    }
}

Personally, I like to inject a token provider into the LoginController within my WebApi project. Typically my login method looks something like this. I’ve omitted a lot of detail and methods from this. It was just to demonstrate using the TokenProvider on successful login and returning the token back down to the client.

[HttpPost]
[Route("")]
public IHttpActionResult Login([FromBody] LoginRequest credentials)
{
    var loginResponse = new LoginResponse {Authenticated = false};
    var user = GetUser(credentials.Email);

    if (user == null)
        return this.HttpActionResultWithMessage(Constants.Constants.ErrorUserNotValidOrIncorrectCredentials, HttpStatusCode.NotFound);

    if (!TryLogon(user, credentials.Password))
        return this.HttpActionResultWithMessage(Constants.Constants.ErrorUserNotValidOrIncorrectCredentials, HttpStatusCode.BadRequest);

    loginResponse.Token = _tokenProvider.GenerateToken(credentials.Email);
    loginResponse.Authenticated = true;

    return Ok(loginResponse);
}

Once the client is both receiving and sending tokens, you can then decorate methods with [Authorize] attribute and even specify roles providing you’re providing roles when creating Claims within your own TokenProvider. Here’s a sample of some methods which show the varying degrees of authorization.

[HttpGet]
[Authorize(Roles = "SuperAdmin)]
[Route("supersecretmethod")]
public void SuperSecretMethod()
{
   // Only super admins can call this!
}

[HttpGet]
[Authorize(Roles = "Admin")]
[Route("secretmethod")]
public void SecretMethod()
{
    // Only admins can call this!
}

[HttpGet]
[Authorize]
[Route("authmethod")]
public void Auth()
{
    // Only authorized users can call this!
}

[HttpGet]
[Route("unauthmethod")]
public void Unauth()
{
    // Anyone can call this!
}

If anyone has any better way to achieve the same results, please let me know in the comments!



blog comments powered by Disqus