Featured

Secure your minimal api .net 6 using JWT

Last modified: March 02, 2022

In previous article , we learned how we build Minimal APIs in .NET 6 and this article, we will show how we can protect our APIs using JWT (JSON Web Token). Please follow and code that article first before following this article as we are going to use code from there.

Table of Content

  1. Appsettings.json
  2. Models
  3. Interfaces
  4. Services
  5. Program.cs
  6. Test the application

Edit project.csproj to install two nuget packages.

<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.2" /> <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.16.0" />

1. appsettings.json

"Jwt": { "Key": "ertwet3245sgf2342werwergww4352345", "Issuer": "https://localhost:7085/", "Audience": "https://localhost:7085/" }

Model

Create a folder called ViewModels and create a class file name UserViewModel that hold username and password.

public class UserViewModel { public int Id { get; set; } public string Username { get; set; } public string Password { get; set; } public string Roles { get; set; } }

3. Interfaces

Create a folder called Interfaces and create a interface file name IUserService as shown below

public interface IUserService { (bool, string?) IsLoginValid(UserViewModel user); }

In the same folder, create another interface file name ITokenService as shown below.

public interface ITokenService { string GenerateToken(UserViewModel user, string key, string issuer, string audience); }

4. Services

Create a folder called Services and create a class file name UserService. The function IsLoginValid return more then one output aka Tuple.

public class UserService : IUserService { private List<UserViewModel> _users() { return new List<UserViewModel> { new UserViewModel{Username = "admin", Password="password" }, new UserViewModel {Username = "admin1", Password = "password1", Roles = "administrator"} }; } public (bool, string?) IsLoginValid(UserViewModel user) { var _user = _users().Where(w => w.Username.Equals(user.Username) && w.Password.Equals(user.Password)); return (_user.Any(), _user.Select(s=> s.Roles).FirstOrDefault()) ; } }

Create another class file called TokenService and saved it to Services folder. The token will valid for 30 minutes. We store userId and User role in the clam, hence pass it to the token which is generated in this method.

public class TokenService : ITokenService { public string GenerateToken(UserViewModel user, string secretKey, string issuer, string audience) { var handler = new JwtSecurityTokenHandler(); var key = Encoding.ASCII.GetBytes(secretKey); var descriptor = new SecurityTokenDescriptor { Issuer = issuer, Audience = audience, Subject = new ClaimsIdentity(new[] { new Claim("id", user.Id.ToString()), new Claim(ClaimTypes.Role, user.Roles) }), Expires = DateTime.UtcNow.AddMinutes(30), SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) }; var token = handler.CreateToken(descriptor); return handler.WriteToken(token); } }

5. Program.cs

Now we need to register our dependencies in WebApplicationBuilder Services

builder.Services.AddSingleton<TokenService>(new TokenService()); builder.Services.AddSingleton<IUserService>(new UserService());

We need to configure AddAuthentication service to same parameters as we use to generate JWT token in TokenService code. This is to check if token sent to API for valid.

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

In the login method, it take in login information and check it to IsLoginValid method(userService). If login valid (Item1 value) and it pass role to generate the token

app.MapPost("/login", [AllowAnonymous] async ([FromBodyAttribute]UserViewModel userModel, IUserService userService, TokenService tokenService, HttpResponse response) => { var user = userService.IsLoginValid(userModel); if (user.Item1) //login match { userModel.Roles = user.Item2 ?? "guest";// map the users role, if no role then assign as guest var token = tokenService.GenerateToken(userModel, builder.Configuration["Jwt:Key"], builder.Configuration["Jwt:Issuer"], builder.Configuration["Jwt:Audience"]); await response.WriteAsJsonAsync(new { token = token }); } return; });

In this method, a user need to pass valid token that can authenticate

app.MapGet("/auth", [Authorize] () => { return Results.Ok("This endpoint requires authorization"); });

OR use "RequireAuthorization" instead

app.MapGet("/auth", () => "This endpoint requires authorization") .RequireAuthorization();

We create a policy called "AdminsOnly" that has administrator role

builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly", b => b.RequireRole("administrator", "true")));

In this method, a user need to pass valid token that can authenticate and meet AdminsOnly policy.

app.MapGet("/admin", [Authorize("AdminsOnly")] () => "The admin endpoint is for admins only.");

OR

app.MapGet("/admin2", () => "The admin endpoint is for admins only.") .RequireAuthorization("AdminsOnly");

6. Test the application

  • Login to generate token

postmam

  • Pass the token to authenticate "auth" endpoint

postmam

  • Login as admin and pass its token to authenticate "admin" endpoint

postmam