ASP.NET Core JWT bearer authentication: authentication vs authorization explained

ASP.NET Core Interview Q&A November 13, 2026 csharp

Authentication: who you are.

csharp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// ======================================================================
// Q: Authentication vs authorization. How does JWT bearer authentication work in
// ASP.NET Core?
//
// - Authentication: who you are. The result is a ClaimsPrincipal (User).
// - Authorization: what you are allowed to do, using roles, policies and
//   ownership checks (Q36).
// Flow: the client obtains an access token from the Identity Provider (Entra ID,
// Keycloak, Auth0). The API validates the signature with the IdP's public keys
// (JWKS, from /.well-known/openid-configuration), and also checks iss, aud and
// exp. Validation is stateless: no database call is needed per request.
// - A JWT is signed, not encrypted. Anyone can read it (base64), so we never put
//   sensitive data in it.
// - Revocation: a JWT cannot easily be "cancelled". That is why access tokens
//   are short-lived (5-15 minutes) and renewed with refresh tokens using
//   rotation.
// - SPA: no tokens in localStorage, because an XSS attack can steal them. Better
//   is the BFF pattern (Backend-for-Frontend): the server holds the tokens and
//   the browser only has an HttpOnly, Secure, SameSite cookie.
// - Don't write your own authentication server: use an existing IdP or ASP.NET
//   Core Identity.
// ======================================================================

// JWT bearer authentication setup
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(o =>
    {
        o.Authority = builder.Configuration["Auth:Authority"];   // e.g. https://login.microsoftonline.com/{tenant}/v2.0
        o.Audience  = builder.Configuration["Auth:Audience"];    // e.g. api://shop-api
        o.MapInboundClaims = false;                              // keeps the original claim names (sub, roles)
        o.TokenValidationParameters = new()
        {
            ValidateIssuer = true,
            ValidateAudience = true,                             // a token issued for another API is rejected
            ValidateLifetime = true,
            ClockSkew = TimeSpan.FromMinutes(1)                  // default: 5 minutes
        };
    });
// With Entra ID: builder.Services.AddMicrosoftIdentityWebApiAuthentication(builder.Configuration);

app.UseAuthentication();
app.UseAuthorization();