Thursday, November 17, 2016

NancyFX: Stateless Auth Example

An Update...

Some time ago, I did an example of using JWTs with Nancy. This post is an update to that. It shows a lighter way of consuming JWTs with a Nancy service. The example solution is hosted on Github.

This example uses the following libraries:
  • Nancy
  • Nancy.Hosting.Aspnet
  • Nancy.Authentication.Stateless
  • JWT.
The test project adds the following libraries:
  • FakeItEasy
  • NBuilder
  • FluentAssertions
  • Nancy.Testing
A Word on JWTs

JWTs are one of the token formats that are quite common today. The jwt.io site has a pretty good introductory page on them. The short story is they are a compact way to securely move information between parties (to paraphrase the jwt.io site).

The Bootstrap Class

I'll start with the Nancy Bootstrap class. This class initializes the stateless authentication when the application is started. It gets an instance of the stateless authentication configuration, and enables it with a call to StatelessAuthentication.Enable(pipelines, configuration);.

public class Bootstrap : DefaultNancyBootstrapper
{
protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
{
base.ApplicationStartup(container, pipelines);
ConfigureStatelessAuth(container, pipelines);
}
private static void ConfigureStatelessAuth(TinyIoCContainer container, IPipelines pipelines)
{
var statelessAuthConfigurationFactory = container.Resolve<IStatelessAuthConfigurationFactory>();
var configuration = statelessAuthConfigurationFactory.Create();
StatelessAuthentication.Enable(pipelines, configuration);
}
}
view raw Bootstrap.cs hosted with ❤ by GitHub
Verifying The Token

The StatelessAuthConfigurationFactory returns a configuration used in the previous step. It contains the steps used to verify the JWT is valid. This is done by ensuring the issuer and audience are correct, checking if the token has expired, and if the user is valid.

public class StatelessAuthConfigurationFactory : IStatelessAuthConfigurationFactory
{
private readonly IJwtKeyProvider keyProvider;
private readonly IUserStore userStore;
public StatelessAuthConfigurationFactory(IJwtKeyProvider keyProvider, IUserStore userStore)
{
this.keyProvider = keyProvider;
this.userStore = userStore;
}
public StatelessAuthenticationConfiguration Create()
{
return new StatelessAuthenticationConfiguration(VerifyToken);
}
private IUserIdentity VerifyToken(NancyContext context)
{
var encryptedToken = context.Request.Headers.Authorization;
if (ThereIsNoToken(encryptedToken))
return null;
var jwt = JsonWebToken.DecodeToObject<Jwt>(encryptedToken, keyProvider.Key);
if (TheTokenIsExpired(jwt))
return null;
if (TheAudienceDoesNotMatch(keyProvider, jwt))
return null;
if (TheIssuerDoesNotMatch(keyProvider, jwt))
return null;
if (TheUserIsNotValid(jwt))
return null;
return new ValidUser(jwt);
}
private bool TheUserIsNotValid(Jwt jwt)
{
return !userStore.IsUserValid(jwt.Name);
}
private static bool TheIssuerDoesNotMatch(IJwtKeyProvider provider, Jwt jwt)
{
return !provider.Issuer.Equals(jwt.Issuer);
}
private static bool TheAudienceDoesNotMatch(IJwtKeyProvider provider, Jwt jwt)
{
return !provider.Audience.Equals(jwt.Audience);
}
private static bool ThereIsNoToken(string encryptedToken)
{
return string.IsNullOrWhiteSpace(encryptedToken);
}
private static bool TheTokenIsExpired(Jwt jwt)
{
return jwt.Expiration < DateTime.UtcNow;
}
}
Securing the Endpoint

The last bit part of the process is securing the endpoint(s). Nancy has a number of extension methods to help with this. The Health module will respond to any call with an OK status. It's not a great way to report the health of a service, but it demonstrates the idea.

public class Health : NancyModule
{
public Health()
{
Get["/health"] = o => HttpStatusCode.OK;
}
}
view raw Health.cs hosted with ❤ by GitHub
The Secure module demonstrates a few ways to use the extension methods. What's really cool is the requirements can be placed at the module level as well as the endpoint level. There are two endpoints in the example: /secure and /needsclaim. Both endpoints require the call use SSL and be made with a valid JWT. The /needsclaim endpoint further requires the authenticated user be an administrator.

public class Secure : NancyModule
{
public Secure()
{
this.RequiresHttps();
this.RequiresAuthentication();
Get["/secure"] = o => HttpStatusCode.OK;
Get["/needsclaim"] = o =>
{
this.RequiresClaims(Claim.Admin);
return HttpStatusCode.OK;
};
}
}
view raw Secure.cs hosted with ❤ by GitHub

The End

That's the basics for getting started with stateless authentication and Nancy.

No comments:

Post a Comment