Minimal APIs – Authentication with JWT

By | 26/07/2023

In this post, we will see how to implement JWT (JSON Web Token) authentication in Minimal APIs.
We are going to use the same project that we created in the post “Minimal APIs – Minimal APIs with .NET Core“, only changing the type of Dog.Id from Guid to int.
These are all the classes:

[DOGS.CS]

namespace MinimalAPI.Model;

public class Dog
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Breed { get; set; }
    public string Color { get; set; }   
}


[DATACONTEXT.CS]

using Microsoft.EntityFrameworkCore;

namespace MinimalAPI.Model;

public class DataContext: DbContext
{
    public DataContext(DbContextOptions<DataContext> options)
    : base(options) { }

    public DbSet<Dog> Dogs => Set<Dog>();
}


[IDOGCOMMANDS.CS]

using MinimalAPI.Model;

namespace MinimalAPI.Commands;

public interface IDogCommands
{
    Task<bool> AddDog(Dog dog);

    Task<List<Dog>> GetAllDogs();

    Task<Dog> GetDogById(int id);

    Task<bool> UpdateDog(Dog dog, int id);

    Task<bool> DeleteDog(int id);

    Task Save();
}


[DOGCOMMANDS.CS]

using Microsoft.EntityFrameworkCore;
using MinimalAPI.Model;

namespace MinimalAPI.Commands;

public class DogCommands:IDogCommands
{
    private readonly DataContext _dataContext;

    public DogCommands(DataContext dataContext)
    {
        _dataContext = dataContext;
    }

    public async Task<bool> AddDog(Dog dog)
    {
        try
        {
            await _dataContext.Dogs.AddAsync(dog);
            return true;
        }
        catch (Exception)
        {
            return false;
        }
    }

    public async Task<List<Dog>> GetAllDogs()
    {
        return await _dataContext.Dogs.AsNoTracking().ToListAsync();
    }

    public async Task<Dog> GetDogById(int id)
    {
        return await _dataContext.Dogs.FirstOrDefaultAsync(d => d.Id == id);
    }

    public async Task<bool> UpdateDog(Dog dog, int id)
    {
        var dogInput = await _dataContext.Dogs.FirstOrDefaultAsync(d => d.Id == id);

        if(dogInput == null)
        {
            return false;
        }

        dogInput.Name = dog.Name;
        dogInput.Color = dog.Color;
        dogInput.Breed = dog.Breed;

        await _dataContext.SaveChangesAsync();

        return true;
    }

    public async Task<bool> DeleteDog(int id)
    {
        var dogInput = await _dataContext.Dogs.FirstOrDefaultAsync(d => d.Id == id);

        if (dogInput == null)
        {
            return false;
        }

        _dataContext.Dogs.Remove(dogInput);
        return true;
    }

    public async Task Save()
    {
        await _dataContext.SaveChangesAsync();
    }
}


[PROGRAM.CS]

using Microsoft.EntityFrameworkCore;
using MinimalAPI.Commands;
using MinimalAPI.Model;

var builder = WebApplication.CreateBuilder(args);
// definition of DataContext
builder.Services.AddDbContext<DataContext>(opt => opt.UseInMemoryDatabase("DbDog"));
// definition of Dependency Injection
builder.Services.AddScoped<IDogCommands, DogCommands>();

// Add services to the container.
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

// Definition Get Method
app.MapGet("/dog", async (IDogCommands commands) =>
    await commands.GetAllDogs());

// Definition Get{Id} Method
app.MapGet("/dog/{id}", async (int id, IDogCommands commands) =>
{
    var dog = await commands.GetDogById(id);

    if (dog == null) return Results.NotFound();

    return Results.Ok(dog);
});

// Definition Post Method
app.MapPost("/dog", async (Dog dog, IDogCommands commands) =>
{
    await commands.AddDog(dog);
    await commands.Save();

    return Results.Ok();
});

// Definition Put Method
app.MapPut("/dog/{id}", async (int id, Dog dog, IDogCommands commands) =>
{
    var updateOk = await commands.UpdateDog(dog, id);

    if (!updateOk) return Results.NotFound();

    return Results.NoContent();
});

// Definition Delete Method
app.MapDelete("/dog/{id}", async (int id, IDogCommands commands) =>
{
    var deleteOk = await commands.DeleteDog(id);
    if (deleteOk)
    {
        await commands.Save();
        return Results.Ok();
    }

    return Results.NotFound();
});

app.Run();



If we run the application, the following will be the result:


Using a tool like Insomnia, we can check that everything works fine:

adding a new dog:


selecting all dogs:


selecting a specific dog:


updating a dog:


deleting a dog:


Now, we will modify the code in order to implement the authentication with JWT.
First of all, we add two NuGet packages using the commands:

Install-Package Microsoft.AspNetCore.Authentication.JwtBearer

Install-Package System.IdentityModel.Tokens.Jwt


Then, we will add the code to implement JWT.
We start to create a class called AuthenticationExtensions, used to add JWT Bearer Authentication:
[AUTHENTICATIONEXTENSIONS.CS]

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;

namespace MinimalAPI.Extensions;

public static class AuthenticationExtensions
{
    public static IServiceCollection AddJwtAuthentication(this IServiceCollection services, string jwtSecretKey)
    {
        // Add JWT Bearer Authentication
        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                // Configure token validation parameters
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    // Validate the signature of the token
                    ValidateIssuerSigningKey = true,
                    // Set the secret key used to validate the token's signature
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtSecretKey)),
                    // Skip issuer validation (optional)
                    ValidateIssuer = false,
                    // Skip audience validation (optional)
                    ValidateAudience = false,
                    // Validate the token's expiration time
                    ValidateLifetime = true,
                    // Set the tolerance for validating the token's expiration time
                    ClockSkew = TimeSpan.Zero,
                    // Require the token to have an expiration time
                    RequireExpirationTime = true,
                    LifetimeValidator = (before, expires, token, parameters) =>
                    {
                        var tokenLifetimeMinutes = (expires - before)?.TotalMinutes;
                        return tokenLifetimeMinutes <= 10; // Set the maximum token lifetime to 10 minutes
                    }
                };
            });

        return services;
    }
}


Then, we add a class called TokenGenerator, used to generate a Token:
[TOKENGENERATOR.CS]

using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

namespace MinimalAPI.Extensions;

public static class TokenGenerator
{
    // Generates a JWT token with the specified secret key and token expiry time
    public static string GenerateToken(string jwtSecretKey, int tokenExpiryMinutes)
    {
        var tokenHandler = new JwtSecurityTokenHandler();
        var key = Encoding.ASCII.GetBytes(jwtSecretKey);

        // Configure the token descriptor
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(new List<Claim>
                {
                    new Claim(ClaimTypes.Name, "token_user") // Set the claim with the name of the token user
                }),
            Expires = DateTime.UtcNow.AddMinutes(tokenExpiryMinutes), // Set the token expiration time
            SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) // Set the signing credentials for the token
        };

        // Create the JWT token based on the token descriptor
        var token = tokenHandler.CreateToken(tokenDescriptor);

        // Write the JWT token as a string
        var generatedToken = tokenHandler.WriteToken(token);

        return generatedToken;
    }

    // Generates a JWT token endpoint result for use in an API controller
    public static IActionResult GenerateTokenEndpoint(string jwtSecretKey, int tokenExpiryMinutes)
    {
        // Generate a JWT token using the provided secret key and token expiry time
        var token = GenerateToken(jwtSecretKey, tokenExpiryMinutes);

        // Return the generated token as an OK response
        return new OkObjectResult(token);
    }
}


Finally, we modify Program.cs to implement JWT:
[PROGRAM.CS]

using Microsoft.EntityFrameworkCore;
using MinimalAPI.Commands;
using MinimalAPI.Extensions;
using MinimalAPI.Model;

var builder = WebApplication.CreateBuilder(args);

// Defined the DataContext for the application and configure it to use an in-memory database
builder.Services.AddDbContext<DataContext>(opt => opt.UseInMemoryDatabase("DbDog"));

// Defined the dependency injection for the IDogCommands interface
builder.Services.AddScoped<IDogCommands, DogCommands>();

// Added services to the container
// Enabled API Explorer for generating OpenAPI documentation
builder.Services.AddEndpointsApiExplorer(); 
 // Added Swagger generation for the API documentation
builder.Services.AddSwaggerGen();
// Added authorization services for JWT authentication
builder.Services.AddAuthorization(); 

var jwtSecretKey = "password123casdsadsaiodiasdsadas";
var tokenExpiryMinutes = 10;

// Added JWT Authentication using the provided secret key
builder.Services.AddJwtAuthentication(jwtSecretKey);

var app = builder.Build();

// Enabled authentication and HTTPS redirection
// Enabled authentication middleware for JWT authentication
app.UseAuthentication(); 
// Redirects HTTP requests to HTTPS
app.UseHttpsRedirection(); 
// Enabled authorization middleware for JWT authentication
app.UseAuthorization(); 

// Configure the HTTP request pipeline
if (app.Environment.IsDevelopment())
{
     // Added Swagger UI to the pipeline for API documentation in development mode
    app.UseSwagger(); 
    // Configured Swagger UI endpoint for API documentation in development mode
    app.UseSwaggerUI();
}

// Definition Get Method
// Added authentication requirement for this endpoint
app.MapGet("/dog", async (IDogCommands commands) =>
    await commands.GetAllDogs()).RequireAuthorization(); 

// Definition Get{Id} Method
// Added authentication requirement for this endpoint
app.MapGet("/dog/{id}", async (int id, IDogCommands commands) =>
{
    var dog = await commands.GetDogById(id);

    if (dog == null) return Results.NotFound();

    return Results.Ok(dog);
}).RequireAuthorization(); 

// Definition Post Method
// Added authentication requirement for this endpoint
app.MapPost("/dog", async (Dog dog, IDogCommands commands) =>
{
    await commands.AddDog(dog);
    await commands.Save();

    return Results.Ok();
}).RequireAuthorization(); 

// Definition Put Method
// Added authentication requirement for this endpoint
app.MapPut("/dog/{id}", async (int id, Dog dog, IDogCommands commands) =>
{
    var updateOk = await commands.UpdateDog(dog, id);

    if (!updateOk) return Results.NotFound();

    return Results.NoContent();
}).RequireAuthorization(); 

// Definition Delete Method
// Added authentication  requirement for this endpoint
app.MapDelete("/dog/{id}", async (int id, IDogCommands commands) =>
{
    var deleteOk = await commands.DeleteDog(id);
    if (deleteOk)
    {
        await commands.Save();
        return Results.Ok();
    }

    return Results.NotFound();
}).RequireAuthorization();

// POST /token to generate JWT token
// Allowed anonymous access to the "/token" endpoint for generating the JWT token
app.MapPost("/token", (HttpContext context) =>
{
    // Generate JWT token
    var generatedToken = TokenGenerator.GenerateToken(jwtSecretKey, tokenExpiryMinutes);
    return TokenGenerator.GenerateTokenEndpoint(jwtSecretKey, tokenExpiryMinutes);
}).AllowAnonymous(); 
app.Run();


We have done and now, if we run the application and we call an endpoint without using the token for the authentication, we will receive an error:


Instead, if we request a token and then we use it for the authentication, we will be able to use the endpoints:



One thought on “Minimal APIs – Authentication with JWT

Leave a Reply

Your email address will not be published. Required fields are marked *