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:
The best article. thank you.