Minimal APIs: Authorization

By | 22/11/2023

In this post, we will see how to implement authorization in Minimal APIs.
We will use the same project that we created in the post “Minimal APIs – Minimal APIs with .NET Core” in which, we then added Authentication in this other post “Minimal APIs: Authentication with JWT“.
In detail, we want to restrict the Insert, Delete and Update methods to the Admin role while, the Reader role, could only use the Select method.

Let’s start to create a “mock User store” class defined as follows:
[MOCKUSERSTORE.CS]

namespace MinimalAPI;
public static class MockUserStore
{
    public static Dictionary<string, string> Users = new Dictionary<string, string>
    {
        // we define two users: admin and reader
        { "admin", "adminpass" },
        { "reader", "readerpass" }
    };
}


Then, we modify the TokenGenerator class adding the ‘role’ in the GeneratedToken method:
[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, string role)
    {
        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
                    new Claim(ClaimTypes.Role, role)
                }),
            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
    // We added the role as parameter
    public static IActionResult GenerateTokenEndpoint(string jwtSecretKey, int tokenExpiryMinutes, string role)
    {
        // Generate a JWT token using the provided secret key and token expiry time
        var token = GenerateToken(jwtSecretKey, tokenExpiryMinutes, role);
        // Return the generated token as an OK response
        return new OkObjectResult(token);
    }
}


Finally, in Program file, we add role-based authorization to the endpoints and then, we modify the endpoint “/token” adding “username” and “password” in the query-string:
[PROGRAM.CS]

using Microsoft.AspNetCore.Authorization;
using Microsoft.EntityFrameworkCore;
using MinimalAPI;
using MinimalAPI.Commands;
using MinimalAPI.Extensions;
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();
builder.Services.AddAuthorization();
var jwtSecretKey = "password123casdsadsaiodiasdsadas";
var tokenExpiryMinutes = 10;
// Add JWT Authentication
builder.Services.AddJwtAuthentication(jwtSecretKey);
var app = builder.Build();
// Use Authentication and HTTPS redirection
app.UseAuthentication();
app.UseHttpsRedirection();
app.UseAuthorization();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
// Definition Get Method
app.MapGet("/dog", async (IDogCommands commands) =>
    await commands.GetAllDogs()).RequireAuthorization();
// 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);
}).RequireAuthorization();
// Definition Post Method
app.MapPost("/dog", async (Dog dog, IDogCommands commands) =>
{
    await commands.AddDog(dog);
    await commands.Save();
    return Results.Ok();
}).RequireAuthorization(new AuthorizeAttribute { Roles = "Admin" });
// 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();
}).RequireAuthorization(new AuthorizeAttribute { Roles = "Admin" });
// 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();
}).RequireAuthorization(new AuthorizeAttribute { Roles = "Admin" });
// POST /token to generate JWT token
app.MapPost("/token", async (HttpContext context) =>
{
    // Obviously, this authentication way is NOT valid
    // in a real project where, we'd get these from a POST body,
    // and we'd need to ensure everything is securely encrypted.
    var username = context.Request.Query["username"];
    var password = context.Request.Query["password"];
    
    // Check user/password with out MockUserStore
    if (!MockUserStore.Users.ContainsKey(username) || MockUserStore.Users[username] != password)
        return Results.BadRequest("Invalid credentials");
    
    // Determine role based on username
    // Again, this is a simplification. In a real project, roles would be fetched from a more complex user profile. 
    var role = username == "admin" ? "Admin" : "Reader";
    // Generate JWT token
    var generatedToken = TokenGenerator.GenerateToken(jwtSecretKey, tokenExpiryMinutes, role);
    return Results.Ok(generatedToken);
}).AllowAnonymous();
app.Run();


We have done and now, if we run the application, the following will be the results:

Invalid Credential


Valid Credential


HTTP 403 Forbidden when we try to use a method for which we don’t have permission


HTTP 200 OK when we try to use a method for which we have permission



Leave a Reply

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