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.
- 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);.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Health : NancyModule | |
{ | |
public Health() | |
{ | |
Get["/health"] = o => HttpStatusCode.OK; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
}; | |
} | |
} |
The End
That's the basics for getting started with stateless authentication and Nancy.
No comments:
Post a Comment