#csharp #dotnet #backend

Day 23: Authentication (JWTs)

Welcome to Day 23. Security is vital. You don’t want just anyone fetching secure /api/bank-accounts endpoints.

REST APIs are Stateless. Unlike old MVC websites using Session Cookies, REST services don’t remember who you are between requests. Every single request must carry proof of identity.

We use JSON Web Tokens (JWT) for this.

How JWT Works

  1. Client sends /login with username and password.
  2. Server verifies against the database. If correct, the server generates a cryptographically signed string (the JWT) and sends it back to the client.
  3. The client saves this token (in memory or localStorage).
  4. On every subsequent request, the client attaches Authorization: Bearer <token> in the HTTP Header.
  5. The API intercepts the header, cryptographically verifies the signature using a Secret Key, and logs them in!

Setting Up ASP.NET Core JWT Auth

First, install the package:

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

In Program.cs, we must register the Authentication Services and define the Secret Key validation rules.

using Microsoft.IdentityModel.Tokens;
using System.Text;

var builder = WebApplication.CreateBuilder(args);

// The Super Secret Key! (In production, NEVER hardcode this. Put it in Azure KeyVault or env vars)
var secureKey = "this_is_a_very_long_secret_key_used_for_signing_tokens!";

builder.Services.AddAuthentication("Bearer")
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secureKey)),
            ValidateIssuer = false,
            ValidateAudience = false
        };
    });

// Also enable Authorization middleware
builder.Services.AddAuthorization();

var app = builder.Build();

// MIDDLEWARE (Order is CRITICAL!)
app.UseAuthentication();
app.UseAuthorization();

Protecting Endpoints [Authorize]

Now our app validates tags. How do we actually restrict an endpoint?

By using the [Authorize] attribute above a controller action, or the .RequireAuthorization() extension in Minimal APIs.

// Unprotected!
app.MapGet("/public-data", () => "Anyone can see this");

// Protected! The middleware will reject access with a 401 Unauthorized if there is no valid JWT.
app.MapGet("/secure-data", () => "Only authenticated users see this!")
   .RequireAuthorization(); 

Challenge for Day 23

Review jwt.io to see how tokens are formed out of Base64 encoded JSON. Set up UseAuthentication() and UseAuthorization() in a project and verify you get a 401 Unauthorized when trying to access .RequireAuthorization() endpoints without a token!

Tomorrow: Authorization (Role-Based Permissions).