Minimal APIs in ASP.NET Core 6.0 - Part2

June 22, 2021 by Anuraj

AspNetCore DotNetCore

This article will discuss about minimal APIs in ASP.NET Core 6.0 - How to implement Authentication and some C# 10 features which will help to minimize the code. Few days back I wrote a post of Minimal APIs in ASP.NET Core 6.0. I received one comment, asking about how to implement authentication in Minimal APIs. So I thought I will write another blog post on how to implement authentication. Like I responded to the comment authentication implementation is in similar way as we are implementing now.

Here is the code for implementing Token authentication in Minimal API.

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("TodoDbConnection")));
builder.Services.AddAuthorization();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters()
    {
        ValidateActor = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ValidIssuer = builder.Configuration["Issuer"],
        ValidAudience = builder.Configuration["Audience"],
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["SigningKey"]))
    };
});

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

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

app.MapGet("/", () => "Hello World.");

Next you can implement the Token endpoint like this.

async Task GetToken(HttpContext http)
{
    var dbContext = http.RequestServices.GetService<TodoDbContext>();
    var inputUser = await http.Request.ReadFromJsonAsync<User>();
    if (!string.IsNullOrEmpty(inputUser.Username) &&
        !string.IsNullOrEmpty(inputUser.Password))
    {
        var loggedInUser = await dbContext.Users
            .FirstOrDefaultAsync(user => user.Username == inputUser.Username
            && user.Password == inputUser.Password);
        if (loggedInUser == null)
        {
            http.Response.StatusCode = 401;
            return;
        }

        var claims = new[]
        {
            new Claim(JwtRegisteredClaimNames.Sub, inputUser.Username),
            new Claim(JwtRegisteredClaimNames.Name, inputUser.Username),
            new Claim(JwtRegisteredClaimNames.Email, loggedInUser.Email)
        };

        var token = new JwtSecurityToken
        (
            issuer: builder.Configuration["Issuer"],
            audience: builder.Configuration["Audience"],
            claims: claims,
            expires: DateTime.UtcNow.AddDays(60),
            notBefore: DateTime.UtcNow,
            signingCredentials: new SigningCredentials(
                new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["SigningKey"])),
                SecurityAlgorithms.HmacSha256)
        );

        await http.Response.WriteAsJsonAsync(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
        return;
    }

    http.Response.StatusCode = 400;
}

And finally map the endpoints

app.MapPost("/token", GetToken);
app.MapGet("/todoitems", GetTodoItems);

app.Run();

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
async Task GetTodoItems(HttpContext http)
{
    var user = http.User;
    var dbContext = http.RequestServices.GetService<TodoDbContext>();
    var todoItems = await dbContext.TodoItems.Where(todoItem => todoItem.User 
        == user.Identity.Name).ToListAsync();
    await http.Response.WriteAsJsonAsync(todoItems);
}

If you’re using Visual Studio 2022 Preview 1, you can use C# 10 features by modifying Project file. You can checkout this blog post on how to do it. Once you enable C# 10 features you can move all the using statements to a separate file explicit cast to (Func<string>) will no longer be necessary, and you can add attributes to lambda expressions and lambda parameters. So you don’t need to create separate methods. You can do implement the code in the Map delegate. Here is the C# 10 code.

app.MapGet("/",() => "Hello World!");
app.MapGet("/todoitems", [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] async (http) =>
{
    var user = http.User;
    var dbContext = http.RequestServices.GetService<TodoDbContext>();
    var todoItems = await dbContext.TodoItems.Where(todoItem => todoItem.User == user.Identity.Name).ToListAsync();
    await http.Response.WriteAsJsonAsync(todoItems);
});

app.Run();

Here is the User model class.

public class User
{
    public int Id { get; set; }
    [Required]
    public string Username { get; set; }
    [Required]
    public string Password { get; set; }
    public DateTime CreatedOn { get; set; } = DateTime.UtcNow;
    public string Email { get; set; }
}

Here is few links related to this.

  1. Learn more about the parameter binding capabilities from David Fowler
  2. Minimal API Playground from Damian Edwards

Happy Programming :)

Copyright © 2024 Anuraj. Blog content licensed under the Creative Commons CC BY 2.5 | Unless otherwise stated or granted, code samples licensed under the MIT license. This is a personal blog. The opinions expressed here represent my own and not those of my employer. Powered by Jekyll. Hosted with ❤ by GitHub