ASP.NET Core cookbook · Intermediate
Protect APIs with JWT bearer tokens and policies
The API must accept access tokens from an identity provider and enforce scopes and roles.
Approach
Configure AddJwtBearer with the provider's authority and audience, define named policies for scopes and roles,
and require authentication by default with a fallback policy.
Solution
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = builder.Configuration["Auth:Authority"]; // e.g. https://login.example.com/
options.Audience = builder.Configuration["Auth:Audience"]; // e.g. api://orders
options.MapInboundClaims = false; // keep "sub", "scope" claim names
options.TokenValidationParameters.ClockSkew = TimeSpan.FromSeconds(30);
});
builder.Services.AddAuthorizationBuilder()
.SetFallbackPolicy(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build())
.AddPolicy("orders:read", p => p.RequireAssertion(ctx => HasScope(ctx.User, "orders:read")))
.AddPolicy("orders:write", p => p.RequireAssertion(ctx => HasScope(ctx.User, "orders:write")))
.AddPolicy("admin", p => p.RequireClaim("roles", "admin"));
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/health", () => "ok").AllowAnonymous();
app.MapGet("/api/orders", () => Array.Empty<object>()).RequireAuthorization("orders:read");
app.MapPost("/api/orders", () => Results.Created()).RequireAuthorization("orders:write");
app.MapDelete("/api/orders/{id}", (string id) => Results.NoContent()).RequireAuthorization("admin");
app.Run();
static bool HasScope(System.Security.Claims.ClaimsPrincipal user, string scope) =>
user.FindAll("scope").SelectMany(c => c.Value.Split(' ')).Contains(scope);
Pitfalls
- Skipping audience validation, so tokens issued for other APIs are accepted.
- Treating ID tokens as access tokens.
- Checking ownership only with roles; resource-level checks still need
IAuthorizationService.
Variations
- For browser apps, a backend-for-frontend with cookie auth avoids tokens in JavaScript.
Microsoft.Identity.Webadds Entra ID specific helpers.
Further reading: Configure JWT bearer authentication
More ASP.NET Core recipes
- Accept file uploads with size and type limits
- Add OpenTelemetry traces and metrics
- Apply EF Core migrations safely during deployment
- Bind and validate options at startup
- Cache responses with output caching and evict by tag
- Call external APIs with typed HttpClients and resilience
- Configure CORS for a browser front end
- Expose liveness and readiness health checks