.Net 6 : Jwt Authentication in Minimal Web Api

In the previous post, we delved into Jwt Authentication implementation in the .Net Core 5. In this post, we will create a Minimal Web API (introduced in .Net 6) and implement Jwt Authentication in it.

Minimal Web API allows developers to build low ceremony web apis without the overhead of ceremonial code in traditional Asp.Net core MVC solution. The traditional Web API core adds so much ceremonial code to your application that it could confuse the beginners.Starting with .Net 6 Preview 4, you can build minimal API’s which are devoid of any ceremonial code – write only what you want !!

You can create a minimal Web Api solution using the following command

dotnet new web

This would provide you the basic skeleton for the project. The code is build on the Top Level Programs feature introduced in C# 9, which allows you to declare and execute programs without the need for writing ceremonial code like class and namespace or main.

So let us write our minimal Web Api for implementing Jwt Authentication now.

var builder = WebApplication.CreateBuilder(args);
await using var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

app.MapGet("/", (Func<string>)(() => "This a demo for JWT Authentication using Minimalist Web API"));
await app.RunAsync();

A minimal code Web Api could look like the above. Isn’t that clean ?

Dependency Injection

To add authentication, we would add couple of services which would help us on the way. First we will add a ITokenService which would provide us the token. We will also add IUserRepositoryService which would allow us to mock the users for sake of example (let us not complicate things with a Db here).

But first we need to add a nuget package which would allow us to use Dependency injection – Microsoft.Extensions.DependencyInjection.

At this point, we will make use of another .Net 6 feature – Global using. This would allow us to move all our using statements to a single location and allow further clean up code. Read on Global using here.

Add a new file called “Usings.cs” and move all your using statements.

global using System;
global using System.ComponentModel.DataAnnotations;
global using Microsoft.AspNetCore.Builder;
global using Microsoft.AspNetCore.Http;
global using Microsoft.Extensions.Hosting;
global using System.Collections.Generic;
global using System.Linq;
global using Microsoft.Extensions.DependencyInjection;
global using System.Security.Claims;
global using System.Text;
global using Microsoft.IdentityModel.Tokens;
global using System.IdentityModel.Tokens.Jwt;
global using Microsoft.AspNetCore.Authentication.JwtBearer;
global using Microsoft.AspNetCore.Authorization;

Alright back to the task in our hand. With the package of Dependency Injection added, we can now register our services.

builder.Services.AddSingleton<ITokenService>(new TokenService());
builder.Services.AddSingleton<IUserRepositoryService>(new UserRepositoryService());

For sake of example, we will use the same implementation of TokenService and UserRepositoryService as we had used in the earlier post on Jwt authentication.



public interface IUserRepositoryService
{
    UserDto GetUser(UserModel userModel);
}
public class UserRepositoryService : IUserRepositoryService
{
    private List<UserDto> _users => new ()
    {
        new ("Anu Viswan","anu"),
        new ("Jia Anu","jia"),
        new ("Naina Anu","naina"),
        new ("Sreena Anu","sreena"),
    };
    public UserDto GetUser(UserModel userModel)
    {
        return _users.FirstOrDefault(x => string.Equals(x.UserName,userModel.UserName) && string.Equals(x.Password, userModel.Password));
    }
}

public interface ITokenService
{
    string BuildToken(string key, string issuer, UserDto user);
}
public class TokenService : ITokenService
{
    private TimeSpan ExpiryDuration = new TimeSpan(0, 30, 0);
    public string BuildToken(string key, string issuer, UserDto user)
    {
        var claims = new[]
        {
                new Claim(ClaimTypes.Name, user.UserName),
                new Claim(ClaimTypes.NameIdentifier,
                Guid.NewGuid().ToString())
             };

        var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
        var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);
        var tokenDescriptor = new JwtSecurityToken(issuer, issuer, claims,
            expires: DateTime.Now.Add(ExpiryDuration), signingCredentials: credentials);
        return new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);
    }
}

We will also use two POCO’s which would help us transfer data.

public record UserDto(string UserName,string Password);

public record UserModel
{
    [Required]
    public string UserName { get; set; }

    [Required]
    public string Password { get; set; }
}

Note that we are using another .Net 5 feature here – record.

Add Middleware

The next step would be to configure and add the middleware for supporting Authentication and Authorization.

builder.Services.AddAuthorization();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(opt =>
{
    opt.TokenValidationParameters = new ()
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ValidIssuer = builder.Configuration["Jwt:Issuer"],
        ValidAudience = builder.Configuration["Jwt:Issuer"],
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
    };
});

We will use the WebApplicationBuilder instance to configure our middleware and use the the WebApplication instance to plugin our middleware.

app.UseAuthentication();
app.UseAuthorization();

Actions

Now we are all set to write our Login and Protected Action. We will first write our Login Action.

app.MapGet("/login", [AllowAnonymous] async (HttpContext http,ITokenService tokenService,IUserRepositoryService userRepositoryService) => {
    var userModel = await http.Request.ReadFromJsonAsync<UserModel>();
    var userDto = userRepositoryService.GetUser(userModel);
    if (userDto == null)
    {
        http.Response.StatusCode = 401;
        return;
    }

    var token = tokenService.BuildToken(builder.Configuration["Jwt:Key"], builder.Configuration["Jwt:Issuer"], userDto);
    await http.Response.WriteAsJsonAsync(new { token = token });
    return;
});

Notice we have also made use of Dependency Injection to use our services, which had registered in the previous steps. We then use the UserRepositoryService and TokenService to validate and generate token for our User.

Another important point to note here is how we used the [AllowAnonymous] attribute inline.

The protected action doesn’t do much. We have kept it as simple as possible for sake of the example.

app.MapGet("/doaction", (Func<string>)([Authorize]() => "Action Succeeded"));

That’s all you need to do to implement Jwt Authentication using Minimal API. The whole implementation looks like the following.


var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<ITokenService>(new TokenService());
builder.Services.AddSingleton<IUserRepositoryService>(new UserRepositoryService());
builder.Services.AddAuthorization();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(opt =>
{
    opt.TokenValidationParameters = new ()
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ValidIssuer = builder.Configuration["Jwt:Issuer"],
        ValidAudience = builder.Configuration["Jwt:Issuer"],
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
    };
});

await using var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

app.MapGet("/", (Func<string>)(() => "This a demo for JWT Authentication using Minimalist Web API"));

app.MapGet("/login", [AllowAnonymous] async (HttpContext http,ITokenService tokenService,IUserRepositoryService userRepositoryService) => {
    var userModel = await http.Request.ReadFromJsonAsync<UserModel>();
    var userDto = userRepositoryService.GetUser(userModel);
    if (userDto == null)
    {
        http.Response.StatusCode = 401;
        return;
    }

    var token = tokenService.BuildToken(builder.Configuration["Jwt:Key"], builder.Configuration["Jwt:Issuer"], userDto);
    await http.Response.WriteAsJsonAsync(new { token = token });
    return;
});

app.MapGet("/doaction", (Func<string>)([Authorize]() => "Action Succeeded"));

await app.RunAsync();

public record UserDto(string UserName,string Password);

public record UserModel
{
    [Required]
    public string UserName { get; set; }

    [Required]
    public string Password { get; set; }
}

public interface IUserRepositoryService
{
    UserDto GetUser(UserModel userModel);
}
public class UserRepositoryService : IUserRepositoryService
{
    private List<UserDto> _users => new ()
    {
        new ("Anu Viswan","anu"),
        new ("Jia Anu","jia"),
        new ("Naina Anu","naina"),
        new ("Sreena Anu","sreena"),
    };
    public UserDto GetUser(UserModel userModel)
    {
        return _users.FirstOrDefault(x => string.Equals(x.UserName,userModel.UserName) && string.Equals(x.Password, userModel.Password));
    }
}

public interface ITokenService
{
    string BuildToken(string key, string issuer, UserDto user);
}
public class TokenService : ITokenService
{
    private TimeSpan ExpiryDuration = new TimeSpan(0, 30, 0);
    public string BuildToken(string key, string issuer, UserDto user)
    {
        var claims = new[]
        {
                new Claim(ClaimTypes.Name, user.UserName),
                new Claim(ClaimTypes.NameIdentifier,
                Guid.NewGuid().ToString())
             };

        var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
        var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);
        var tokenDescriptor = new JwtSecurityToken(issuer, issuer, claims,
            expires: DateTime.Now.Add(ExpiryDuration), signingCredentials: credentials);
        return new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);
    }
}


You can find the code given in the example in my Github.

That’s all for now, Happy weekend !!

Advertisement

5 thoughts on “.Net 6 : Jwt Authentication in Minimal Web Api

    1. Hi,
      If you want to validate your login credentials against an API using Postman, you could do a post request as you would normally do, say with Json Body. Once you receive the response, make sure you copy the Token received for using in next request for authorization.

      You could use the Authorization Tab from postman for posting the authorization credentials. Make sure that you have “Type” selected as “BearerToken” and have the required token (recieved in step 1) filled in Token textbox. This would ensure your token is passed along with your second requested api after it was authenticated and token recieved.

      Like

  1. Great tutorial, concise and straight to the point. Everything works fine but when I copy the token into jwt.io, it reports that the signature is invalid. I tried pasting the secret into the text box provided in the VERIFY SIGNATURE section but still no luck.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s