Minimal API Workshop
Module 3
Identity, Tokens & Authorization
Identity · JWT · policies · resource authz · hardening
1:00 – 2:15 PM
By the end you can
- Add ASP.NET Core Identity with token endpoints
- Protect endpoints with JWT bearer authentication
- Express permissions as policies, not scattered role checks
- Harden against the most common API attacks
Authentication ≠ Authorization
Who are you? vs. May you do this?
Order is not negotiable
app.UseAuthentication(); // validate token -> ClaimsPrincipal
app.UseAuthorization(); // evaluate policies against it
app.MapProductEndpoints(); // endpoints come after both
// no principal -> 401 Unauthorized
// principal, denied -> 403 Forbidden
Two ways to get a token
MapIdentityApi<T>()
- Register / login / refresh for free
- Great default
- Returns a bearer token
Issue your own JWT
- Control the claims
- Add
role+customer_id - Authz without a DB hit
Validating the token
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(o => o.TokenValidationParameters = new()
{
ValidIssuer = jwt.Issuer, ValidAudience = jwt.Audience,
IssuerSigningKey = new SymmetricSecurityKey(keyBytes),
ClockSkew = TimeSpan.FromSeconds(30),
});
Signing key lives in user secrets / env — never appsettings.json. HS256 needs ≥ 32 bytes.
Authorization as policy
builder.Services.AddAuthorizationBuilder()
.AddPolicy("Staff", p => p.RequireRole("Staff"))
.AddPolicy("OwnsOrder", p => p.AddRequirements(new OwnsOrderRequirement()));
var staff = products.MapGroup("/").RequireAuthorization("Staff");
staff.MapPost("/", CreateProduct); // and PUT, DELETE
Policy on the group → new endpoints are protected by default. Fail safe.
Resource-based authorization
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext ctx, OwnsOrderRequirement req, Order order)
{
if (ctx.User.IsInRole("Staff") ||
ctx.User.FindFirstValue("customer_id") == order.CustomerId.ToString())
ctx.Succeed(req);
return Task.CompletedTask;
}
Return 404 (not 403) for another customer's order — a 403 confirms it exists.
Never trust the body for identity
- Read the customer from the token, not the request
user.FindFirstValue("customer_id")- A
customerIdin the body = order as someone else - This is OWASP API1 — the most common API vuln
Hardening quick hits
| Concern | What you add |
|---|---|
| Brute force on login | Rate limiter, fixed window on /api/auth/* |
| Abuse elsewhere | Global sliding-window limiter |
| Cross-origin calls | CORS with an explicit origin list |
| Transport | HTTPS redirection + HSTS |
| Response headers | nosniff, Referrer-Policy, remove Server |
Lab
Lab 3 · Customers and Baristas
- Issue JWTs with role and
customer_idclaims - Lock products to Staff; orders to the authenticated customer
- Verify: 401 · 403 · 404-not-403 · 429 all behave
Ends at checkpoint-3