Introduction
In this article, you will learn how to enable JwtBearer authentication when you are building APIs through NancyFx and learn how to implement the JwtBearer Authentication. This is also an open source project on Github.
Background
When I build some RESTful APIs using NancyFx, I need to verify the request's validity. And, I don't want to use the default authentication type (Basic,Forms and Stateless) provided by Nancy project.
Based on this document API-Security-Checklist, I prefer to use the JwtBearer(using json web token) which we often use in Web API and it also fits me very well.
So, I created an open source project named Nancy.Authentication.JwtBearer to solve this problem on my Github page.
You also can install this package through NuGet. I have uploaded this package to the NuGet. Now, I will show you how to use this package with a easy sample and how to implement the JwtBearer Authentication.
How to Use
First of all, let's create a new empty ASP.NET Core Web application.
Secondly, open the Package Manager Console and execute the below commands one by one.
- Install-Package Microsoft.AspNetCore.Owin -Version 1.1.2
- Install-Package Nancy -Pre
- Install-Package Nancy.Authentication.JwtBearer
Thirdly, in order to enable Nancy framework, we need to modify the method Configure in Startup class, as demonstrated by the following code.
- public class Startup
- {
- public void Configure(IApplicationBuilder app)
- {
- app.UseOwin(x=>x.UseNancy());
- }
- }
Fourthly, create a module class to ensure that our Nancy project can run well.
- public class MainModule : NancyModule
- {
- public MainModule()
- {
- Get("/",_=>
- {
- return "test";
- });
- }
- }
After the previous 4 steps, we can run this project well!
Now, we need to create a bootstrapper so that we can enable the JwtBearer authentication for the sample project.
- public class DemoBootstrapper : DefaultNancyBootstrapper
- {
- protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
- {
- base.ApplicationStartup(container, pipelines);
-
- var keyByteArray = Encoding.ASCII.GetBytes("Y2F0Y2hlciUyMHdvbmclMjBsb3ZlJTIwLm5ldA==");
- var signingKey = new SymmetricSecurityKey(keyByteArray);
-
- var tokenValidationParameters = new TokenValidationParameters
- {
-
- ValidateIssuerSigningKey = true,
- IssuerSigningKey = signingKey,
-
- ValidateIssuer = true,
- ValidIssuer = "http://www.c-sharpcorner.com/members/catcher-wong",
-
- ValidateAudience = true,
- ValidAudience = "Catcher Wong",
-
- ValidateLifetime = true,
- ClockSkew = TimeSpan.Zero
- };
-
- var configuration = new JwtBearerAuthenticationConfiguration
- {
- TokenValidationParameters = tokenValidationParameters
- };
-
-
- pipelines.EnableJwtBearerAuthentication(configuration);
- }
- }
As you can see, I constructed a new instance of JwtBearerAuthenticationConfiguration, and used this instance to enable the JwtBearer authentication.
However, we need to construct an instance of Token Validation Parameters that is in the Microsoft.IdentityModel. Tokens namespace at first! Because this is the most important property of JwtBearerAuthenticationConfiguration.
So far, we have finished the configuration of the authentication. And, we need to verify whether the authentication takes effect.
Create a new module class to use the authentication.
- public class SecurityModule : NancyModule
- {
- public SecurityModule() : base("/demo")
- {
-
- this.RequiresAuthentication();
-
- Get("/",_=>
- {
- return "JwtBearer authentication";
- });
- }
- }
At this time, we accomplished the task!
When we visit this secure route, it will tell us "The requested resource requires user authentication".
Now, let's create a valid JSON Web Token and use this token to send a request using Fiddler.
Here is a method that we can use to create a JSON Web Token:
- private string GetJwt(string client_id)
- {
- var now = DateTime.UtcNow;
-
- var claims = new Claim[]
- {
- new Claim(JwtRegisteredClaimNames.Sub, client_id),
- new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
- new Claim(JwtRegisteredClaimNames.Iat, now.ToUniversalTime().ToString(), ClaimValueTypes.Integer64)
- };
-
-
- var symmetricKeyAsBase64 = "Y2F0Y2hlciUyMHdvbmclMjBsb3ZlJTIwLm5ldA==";
- var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);
- var signingKey = new SymmetricSecurityKey(keyByteArray);
-
- var jwt = new JwtSecurityToken(
- issuer: "http://www.c-sharpcorner.com/members/catcher-wong",
- audience: "Catcher Wong",
- claims: claims,
- notBefore: now,
- expires: now.Add(TimeSpan.FromMinutes(10)),
- signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256));
- var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
-
- var response = new
- {
- access_token = encodedJwt,
- expires_in = (int)TimeSpan.FromMinutes(10).TotalSeconds
- };
-
- return JsonConvert.SerializeObject(response, new JsonSerializerSettings { Formatting = Formatting.Indented });
- }
After executing this request in Fiddler, we may get the below result.
And, here is the token information of this sample.
Note
We need to add this token to the request header(Authorization) , and the value is combine the string Bearer and the token with a space.
Up to here , we have finished the sample and show you how to use this provider.
The next time , I will show you how to implement this provider.
How to Implement
Before I introduce how to implement this provider, I assume that you have already learned about the pipelines of Nancy , which play a important role at the implementation.
By the way, I used the Nancy Style to write the code, which you may find similar to other authentication types in Nancy project.
The code is very easy !
As in the previous sample, we have an entrance to enable the JwtBearer authentication. And, this entrance is an extension method of IPipelines.
-
-
-
-
-
- public static void EnableJwtBearerAuthentication(this IPipelines pipeline, JwtBearerAuthenticationConfiguration configuration)
- {
- JwtBearerAuthentication.Enable(pipeline, configuration);
- }
In the extension method, I called JwtBearerAuthentication's Enable method to finish this work.
Let's take a look at the Enable method.
-
-
-
-
-
- public static void Enable(IPipelines pipelines, JwtBearerAuthenticationConfiguration configuration)
- {
- if (pipelines == null)
- {
- throw new ArgumentNullException("pipelines");
- }
-
- if (configuration == null)
- {
- throw new ArgumentNullException("configuration");
- }
- pipelines.BeforeRequest.AddItemToStartOfPipeline(GetLoadAuthenticationHook(configuration));
- pipelines.AfterRequest.AddItemToEndOfPipeline(GetAuthenticationPromptHook(configuration));
- }
The BeforeRequest and the AfterRequest are the main characters .
Requests will enter the before pipeline first , then you need to handle the requests , and at last the after pipeline will response something for the request.
So we need to verify the token on the before request ,but how can we do that?
The following code shows you how to do it!
- private static Func<NancyContext, Response> GetLoadAuthenticationHook(JwtBearerAuthenticationConfiguration configuration)
- {
- return context =>
- {
- Validate(context,configuration);
- return null;
- };
- }
This method just returns a value whose type is Func<NancyContext, Response> ,and the core of this method is to call the Validate method which is used to handle the token.
Here is the code to handle the token . And the verification is based on JwtSecurityTokenHandler which you can find in the namespace System.IdentityModel.Tokens.Jwt.
- private static void Validate(NancyContext context, JwtBearerAuthenticationConfiguration configuration)
- {
-
- var jwtToken = context.Request.Headers["Authorization"].FirstOrDefault() ?? string.Empty;
-
-
- if (jwtToken.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
- {
- jwtToken = jwtToken.Substring("Bearer ".Length);
- }
- else
- {
- return;
- }
-
-
- if (!string.IsNullOrWhiteSpace(jwtToken))
- {
- try
- {
- SecurityToken validatedToken;
- var tokenHandler = new JwtSecurityTokenHandler();
- var validatedClaims = tokenHandler.ValidateToken(jwtToken, configuration.TokenValidationParameters, out validatedToken);
-
- context.CurrentUser = validatedClaims;
- }
- catch (Exception)
- {
- }
- }
- }
We often use Bearer as the prefix of the value of the header - Authorization , however , we also can use other words to replace the default value which you can specify in your configuration.
And the most important thing is to assign the validated claims to the NancyContext's CurrentUser property.
If the token is not validated , we do nothing , Nancy will return Unauthorized to the client.
And what do we need to do after a request ? We need to deal with the scenario that Nancy returns Unauthorized . Because we need to tell the client the requested resource requires user authentication and which type of authentication the requested resource needs.
- private static Action<NancyContext> GetAuthenticationPromptHook(JwtBearerAuthenticationConfiguration configuration)
- {
- return context =>
- {
- if (context.Response.StatusCode == HttpStatusCode.Unauthorized)
- {
-
- context.Response.WithHeader(JwtBearerDefaults.WWWAuthenticate, configuration.Challenge);
- }
- };
- }
OK! That's the core of how to impement the provider.
Summary
This article introduced how to use a provider to enable the JwtBearer authentication when we use Nancy and showed how to implement this provider.