Authentication and authorization in ASP.NET Core
Protect endpoints with JWT bearer tokens or cookies, ASP.NET Core Identity, roles and policies.
13 min read
You will learn
- Configure authentication schemes (JWT bearer, cookies)
- Protect endpoints with RequireAuthorization and [Authorize]
- Write role-, claim- and policy-based authorization rules
- Read the current user's claims
Concepts first
You learned the theory in Track 7: authentication establishes who the user is (sessions with cookies, or bearer tokens like JWTs; possibly via OpenID Connect), authorization decides what they may do. ASP.NET Core implements both as middleware plus a few attributes and extension methods.
Authentication: schemes
An authentication scheme knows how to read identity from a request. Register one (or several) and the middleware:
// An API accepting JWT access tokens (from your own issuer or an identity provider)
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://login.example.com"; // issuer: keys are discovered automatically
options.Audience = "shop-api"; // tokens must be meant for this API
});
builder.Services.AddAuthorization();
app.UseAuthentication();
app.UseAuthorization();On every request, the JWT handler validates the token's signature, issuer, audience and expiry, and turns its claims into HttpContext.User.
For server-rendered websites, use cookies (AddCookie()), usually together with ASP.NET Core Identity — a complete user system (registration, password hashing, lockout, 2FA, email confirmation) stored via EF Core:
builder.Services.AddIdentity<AppUser, IdentityRole>()
.AddEntityFrameworkStores<ShopDb>();Tip
For APIs used by SPAs and mobile apps, prefer an external identity provider (Microsoft Entra ID, Auth0, Keycloak, Duende IdentityServer) issuing tokens via OpenID Connect. Your API then only validates tokens — it never handles passwords.
Protecting endpoints
app.MapGet("/products", GetProducts); // public
app.MapPost("/orders", PlaceOrder).RequireAuthorization(); // any signed-in user
app.MapDelete("/products/{id}", DeleteProduct)
.RequireAuthorization(p => p.RequireRole("Admin")); // inline rule
var admin = app.MapGroup("/admin").RequireAuthorization("AdminOnly"); // whole groupWith controllers:
[Authorize] // the whole controller requires sign-in
public class OrdersController : ControllerBase
{
[AllowAnonymous] [HttpGet("public-stats")] public IActionResult Stats() => ...;
[Authorize(Roles = "Admin")] [HttpDelete("{id}")] public IActionResult Delete(int id) => ...;
}Outcomes: no valid identity → 401 (challenge); identity but rule fails → 403 (forbid).
Policies
Define rules once, by name:
builder.Services.AddAuthorizationBuilder()
.AddPolicy("AdminOnly", p => p.RequireRole("Admin"))
.AddPolicy("CanManageCatalog", p => p.RequireAssertion(ctx =>
ctx.User.IsInRole("Admin") || ctx.User.HasClaim("department", "catalog")))
.AddPolicy("Adult", p => p.Requirements.Add(new MinimumAgeRequirement(18))); // custom handlerCustom requirements are implemented by an AuthorizationHandler<TRequirement> — perfect for rules like "minimum age" or "subscription active".
Resource-based authorization
"Can edit this order?" depends on the order. Check it in the handler with IAuthorizationService:
app.MapPut("/orders/{id}", async (int id, ClaimsPrincipal user, IAuthorizationService auth, ShopDb db) =>
{
var order = await db.Orders.FindAsync(id);
if (order is null) return Results.NotFound();
var result = await auth.AuthorizeAsync(user, order, "IsOrderOwner");
return result.Succeeded ? Results.Ok(/* update */) : Results.Forbid();
}).RequireAuthorization();Reading the current user
app.MapGet("/me", (ClaimsPrincipal user) => new
{
Id = user.FindFirstValue(ClaimTypes.NameIdentifier), // the "sub" claim
Name = user.Identity?.Name,
IsAdmin = user.IsInRole("Admin"),
Claims = user.Claims.Select(c => new { c.Type, c.Value }),
}).RequireAuthorization();To see what a token contains, paste it into the JWT decoder from Track 7 — those claims are exactly what ends up in ClaimsPrincipal.
Development tips
dotnet user-jwts create --role Admingenerates a valid local JWT for testing protected endpoints.- Keep signing keys and client secrets in user secrets / a vault, never in
appsettings.json.
Key takeaways
- Register authentication schemes (
AddJwtBearer,AddCookie, Identity, OIDC) plusUseAuthentication/UseAuthorization. - Protect endpoints with
RequireAuthorization()/[Authorize]; open specific ones with[AllowAnonymous]. - Centralize rules in named policies; use resource-based checks for per-object permissions.
- The user is a
ClaimsPrincipal(HttpContext.User) built from the token's or cookie's claims.
Quiz
4 questions · 3 correct to pass1.An endpoint has RequireAuthorization() and the request has no token. What's the response?
2.Where does ASP.NET Core put the authenticated user's identity?
3.What's the advantage of named policies over checking roles inline everywhere?
4.What does AddJwtBearer validate on each request by default?
To complete this lesson
- ○ Pass the quiz (3/4 correct)