You are currently viewing Securing .NET Core 3 API Using JWT authentication

Securing .NET Core 3 API Using JWT authentication

Now a days, all the functionalities available in your business applications are required to be available everywhere. Everybody wants the application to be available at their finger tips. Your customers can use your applications on any device including (but not limited to) mobile, iPad, laptop.

In order to make your applications work on variety of devices, applications generally designed to use APIs which can cater the required functionalities to web applications, desktop applications or mobile applications. Most of the times these APIs are using JWT Bearer Token Authentication.

That’s why I thought it would be nice idea to compile the required steps in a blog post. In this article we will use ASP .NET Core identity to validation users and then create the JWT tokens. For the sake of keeping it simple, we will not discuss about refresh tokens in this article..

JWT Internals

JWT stands for JSON Web Token.

Every JWT token has three parts separated by dots, which are as below.

  • Header, which contains two fields – first one is signing algorithm used for signing the token and second one is type which is JWT.
  • Payload, contains claims which are required by your application
  • Signature, to create the signature part you have to take the encoded header, the encoded payload, a secret, the algorithm specified in the header, and sign that.

Every part is then Base64 URL encoded, and separated by dots to form the JWT.

Header.Payload.Signature

Signature is used to make sure that the token was not changed along the way.

The output is three Base64-URL strings separated by dots that can be easily passed in HTML and HTTP environments, while being more compact when compared to XML-based standards such as SAML.

If you want to play with JWT and put these concepts into practice, you can use jwt.io Debugger to decode, verify, and generate JWTs.

The Demo Solution

You can download the base solution from my GitHub repository. Follow steps from my previous blog post to create the identity database.

NuGet package references

You will need to add the package reference to NuGet package Microsoft.AspNetCore.Authentication.JwtBearer.

Startup.ConfigurationServices method

The important stuff here is the AddAuthentication call. There you need to specify the AuthenticationScheme and ChallengeScheme.

As you can see, we have enabled validation for issuer, audience and issuer signing key. Below is small explanation of properties which we have sent in TokenValidationParameters.

  • Issuer, is the principal that has issued JWT. If token has different issuer than expected, the validation will fail and caller will receive 401 unauthorized.
  • Audience, is the recipient that JWT is intended for. If token contains different audience than expected, the validation will fail and caller will receive 401 unauthorized.
  • Signing Key, is the key you use for signing the token.
  • ClockSkew, has been set to TimeSpan.Zero. The this represents the time duration after expiry of token, for which the token should be considered valid. I wanted to immediately invalidate the token after expiry time, so I set it to TimeSpan.Zero.

public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
services.AddControllers();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("SqlConnection")));
services.AddIdentity<IdentityUser, IdentityRole>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
// configure strongly typed settings objects
var jwtSection = Configuration.GetSection("JwtBearerTokenSettings");
services.Configure<JwtBearerTokenSettings>(jwtSection);
var jwtBearerTokenSettings = jwtSection.Get<JwtBearerTokenSettings>();
var key = Encoding.ASCII.GetBytes(jwtBearerTokenSettings.SecretKey);
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidIssuer = jwtBearerTokenSettings.Issuer,
ValidateAudience = true,
ValidAudience = jwtBearerTokenSettings.Audience,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
});
}
view raw Startup.cs hosted with ❤ by GitHub

Startup.Configure method

The Authentication middleware is added in Startup.Configure by calling the UseAuthentication extension method on the app’s  IApplicationBuilder.

The call of UseAuthentication registers the middleware which uses the previously registered authentication schemes. Ideally, you should call  UseAuthentication  before any middleware that depends on users being authenticated.

When using endpoint routing, the call to UseAuthentication must go:

  • After UseRouting, so that route information is available for authentication decisions.
  • Before UseEndpoints, so that users are authenticated before accessing the endpoints.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseCors(x => x
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
view raw Startup.cs hosted with ❤ by GitHub

AuthController Code

As you can see, the AuthController code is simple. It has 3 APIs – User Registration, Login and Logout.

In User Registration API, it creates a new identity under the AspNetCore identity database.

The Login API validates user credentials. After successful validation, it creates the token for the user. For demo purpose, we have added only two claims, but you can add more claims as per requirement of your application’s design. You can also create authorization policies to allow user access to some API only if he has specific claim.

The Logout method is pretty empty. Huh.. why ?

[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
private readonly JwtBearerTokenSettings jwtBearerTokenSettings;
private readonly UserManager<IdentityUser> userManager;
public AuthController(IOptions<JwtBearerTokenSettings> jwtTokenOptions, UserManager<IdentityUser> userManager)
{
this.jwtBearerTokenSettings = jwtTokenOptions.Value;
this.userManager = userManager;
}
[HttpPost]
[Route("Register")]
public async Task<IActionResult> Register([FromBody]UserDetails userDetails)
{
if (!ModelState.IsValid || userDetails == null)
{
return new BadRequestObjectResult(new { Message = "User Registration Failed" });
}
var identityUser = new IdentityUser() { UserName = userDetails.UserName, Email = userDetails.Email };
var result = await userManager.CreateAsync(identityUser, userDetails.Password);
if (!result.Succeeded)
{
var dictionary = new ModelStateDictionary();
foreach (IdentityError error in result.Errors)
{
dictionary.AddModelError(error.Code, error.Description);
}
return new BadRequestObjectResult(new { Message = "User Registration Failed", Errors = dictionary });
}
return Ok(new { Message = "User Reigstration Successful" });
}
[HttpPost]
[Route("Login")]
public async Task<IActionResult> Login([FromBody]LoginCredentials credentials)
{
IdentityUser identityUser;
if (!ModelState.IsValid
|| credentials == null
|| (identityUser = await ValidateUser(credentials)) == null)
{
return new BadRequestObjectResult(new { Message = "Login failed" });
}
var token = GenerateToken(identityUser);
return Ok(new { Token = token, Message = "Success" });
}
[HttpPost]
[Route("Logout")]
public async Task<IActionResult> Logout()
{
// Well, What do you want to do here ?
// Wait for token to get expired OR
// Maintain token cache and invalidate the tokens after logout method is called
return Ok(new { Token = token, Message = "Logged Out" });
}
private async Task<IdentityUser> ValidateUser(LoginCredentials credentials)
{
var identityUser = await userManager.FindByNameAsync(credentials.Username);
if (identityUser != null)
{
var result = userManager.PasswordHasher.VerifyHashedPassword(identityUser, identityUser.PasswordHash, credentials.Password);
return result == PasswordVerificationResult.Failed ? null : identityUser;
}
return null;
}
private object GenerateToken(IdentityUser identityUser)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(jwtBearerTokenSettings.SecretKey);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, identityUser.UserName.ToString()),
new Claim(ClaimTypes.Email, identityUser.Email)
}),
Expires = DateTime.UtcNow.AddSeconds(jwtBearerTokenSettings.ExpiryTimeInSeconds),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature),
Audience = jwtBearerTokenSettings.Audience,
Issuer = jwtBearerTokenSettings.Issuer
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
}
view raw AuthController.cs hosted with ❤ by GitHub

How does logout happen ?

I am assuming all of you have a driving license. Every driving license has expiry date. Whenever authority wants to check your driving license, it checks the expiry date and which type of vehicles are allowed to drive using that license.

So if the authority who issued driving license, wants to invalidate someone’s license which has expiry date after years, how will they do this ?

There is no way for them to immediately invalidate the license. They can probably maintain a list of prematurely invalidated driving license. Then next time authority asks you driving license, they will check if this license is not in the invalidated list.

Are you with me so far ?

This description above is perfectly analogous with JWT. Once a user has JWT, there is no way for you to invalidate the token immediately.

So, how will you logout user ?

Image result for one does not simply logout with jwt

You can keep the expiry time to be very short. You can also use refresh tokens to refresh the JWT tokens.

The above approach will not immediately logout user. For immediate logout, you can maintain a cache of invalidated tokens on your server. So every-time a request comes, you can check the cache to see if this token was already invalidated.

Download Completed Solution

You can find the completed solution at this place in my GitHub repository.

Test with Postman

The solution has a JSON file which you can import in postman and it will give you requests for user registration, login, get weather forecast and logout APIs. If you execute them in this order, you should be able to see that all APIs are working.

After calling Login endpoint, you will get a token if login is successful. You will have to use this token while calling weather forecast API.

Under Headers, add new header with name Authorization and in its value add Bearer token-value where token-value is the actual token you copied from the output of login API.

Just for testing purpose, if you call get weather forecast before login, you should see 401 unauthorized response. Also, in case, if the token is expired, the get weather forecast will return 401 unauthorized.

I hope you enjoyed this article. Let me know your thoughts.

Leave a Reply

This Post Has 10 Comments

  1. palemka

    Great article! keep working!

  2. ubong

    it’s a nice article bro

  3. HerringTheCoder

    I think you can omit ModelState.IsValid and request==null checks in controllers, because you are using [ApiController] attribute, which adds automatic validation and a proper response with a list of errors.
    In case there are any validation errors, then your code is never reached, making checks redundant.

    1. Manoj Choudhari

      No. This is incorrect assumption. ApiControllerAttribute is derived from ControllerAttribute class. The Controller attribute class is empty and is derived from Attribute class. The documentation says that these classes are the “default” mechanism through which controller classes can be identified by the framework. These attributes do not perform any validations on the incoming request.

      Reference: Source code of .NET Core at: https://github.com/dotnet/aspnetcore/blob/master/src/Mvc/Mvc.Core/src/ApiControllerAttribute.cs

      1. HerringTheCoder

        Thanks for your response.
        To clarify I was refencing this part of documentation:
        https://docs.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-3.1#model-state

        This paragraph can be summarized as:
        1. Validation takes place (automatically) before controller action no matter the attribute. I was a bit unclear on that point before.
        2. In monolith web apps (e.g. returning Razor pages) code should check if the model is valid (as you’ve shown), but it’s only a check against already established (validated) model state, not a validation itself.
        3. With [ApiAttribute] you get automatic 400 response with a list of validation errors.
        Since we’ve established validation takes place before any controller action, then you will never reach IsValid() check unless the model is already valid, thus making it redundant.

        I’ve also tested this behavior with postman/breakpoints and pre-action validation always takes precedence and returns 400+built-in errors if model is not valid.
        Best Regards

      2. Manoj Choudhari

        Somehow I missed this. Thanks for sharing the info !

  4. Marthin

    How can I use refresh Tokens in ASP.NET Core Authentication?

    1. Manoj Choudhari

      I will try to cover it in the upcoming post. Thanks !

  5. A great collection of posts for configuring a authentication on a .NET Core WebAPi – really useful and with a Github code to fill in any gaps I got the basic configuration stood up in no time.