Authentication with JWT and cookies

ASP.NET Core · Advanced · 15 min

Almost every API must know who is calling before deciding what they may do.

Authentication answers "who is this?". It's provided by schemes registered with builder.Services.AddAuthentication(...) and run by app.UseAuthentication() (WebApplication adds it automatically when auth services are registered). A successful scheme produces a ClaimsPrincipal available as HttpContext.User or a ClaimsPrincipal handler parameter.

Common schemes:

  • JWT bearer (Microsoft.AspNetCore.Authentication.JwtBearer) for APIs: clients send Authorization: Bearer <token>. With an identity provider (Entra ID, Auth0, Keycloak), set Authority and Audience; the middleware downloads signing keys from the provider's OpenID metadata and validates signature, issuer, audience and lifetime.
  • Cookies for server-rendered web apps; usually combined with OpenID Connect to sign in through an external provider.
  • ASP.NET Core Identity and its API endpoints (MapIdentityApi) when you store users yourself.

A claim is a key/value statement about the user (sub, email, role). Read them with user.FindFirst("sub")?.Value or user.Identity?.Name. Avoid inventing your own token formats or crypto.

Authentication alone doesn't block anything: an anonymous request simply has an unauthenticated user. Blocking is the job of authorization (next lesson), which returns 401 (challenge: not authenticated) or 403 (forbid: authenticated but not allowed).

For local development, dotnet user-jwts create issues test tokens that the JwtBearer handler accepts.

Example

// dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
using System.Security.Claims;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication().AddJwtBearer(o =>
{
    o.Authority = "https://login.example.com/";   // OIDC issuer: keys via metadata
    o.Audience = "todo-api";                      // must match the token's aud
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/public", () => "anyone");
app.MapGet("/me", (ClaimsPrincipal user) => new
{
    user.Identity!.Name,
    Sub = user.FindFirstValue(ClaimTypes.NameIdentifier),
    Roles = user.FindAll(ClaimTypes.Role).Select(c => c.Value),
}).RequireAuthorization();                        // 401 without a valid token

app.Run();
// curl /me                                   -> 401
// curl -H "Authorization: Bearer <jwt>" /me  -> {"name":"ada","sub":"42","roles":["admin"]}

Remember

  • Authentication schemes turn credentials into a ClaimsPrincipal
  • JwtBearer validates signature, issuer, audience and expiry
  • Use cookies + OIDC for browser apps, bearer tokens for APIs
  • 401 means not authenticated, 403 means not allowed

Watch out for

  • Disabling token validation parameters (issuer, audience, lifetime) to 'make it work'.

Check yourself

  1. An endpoint requires authorization and the request has no token. What status is returned?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: learn.microsoft.com/en-us/aspnet/core/security/authentication/