An HTTP API that can be called without proving who is calling is a public API, even if you “didn’t document it.” Authentication answers who; authorization answers what they may do. In ASP.NET Core Minimal APIs both are middleware + metadata on endpoints, not a custom if (token == "secret").
Pick a scheme and stay boring
- Cookie + session (or cookie + ASP.NET Core Identity) for first-party browsers.
HttpOnly,Secure,SameSite. CSRF tokens if the cookie is sent automatically. - Bearer JWT for service-to-service or SPA + API when you accept token theft as a design constraint. Validate issuer, audience, lifetime, signing key. Do not roll your own JWT parser.
- Mutual TLS or signed requests for high-value machine clients.
Do not put long-lived JWTs in localStorage and call it security. Do not log Authorization headers.
Wire-up shape
builder.Services.AddAuthentication().AddJwtBearer();
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
var orders = app.MapGroup("/orders").RequireAuthorization("orders.read");
orders.MapGet("/{id:guid}", GetOrder);
RequireAuthorization() with no policy still demands an authenticated user. Policies encode roles/claims (“must be in tenant X”). Resource-based checks (this order belongs to that user) happen in the handler, not only in a global role.
Pitfalls
- Authentication without authorization: any logged-in user can hit
/admin. - Failing open when the identity service is down.
- Using GET with a token in the query string (logs, Referer).
- Confusing 401 (no/invalid credentials) with 403 (credentials fine, not allowed).
See OWASP Top Ten A07 Authentication Failures and A01 Broken Access Control.