Securing APIs with OAuth2 and OpenID Connect in .NET
OAuth2 handles delegated access; OpenID Connect layers identity on top. Together they power Google Sign-In, Microsoft 365, and every “Login with X” button. Here’s how to wire them into .NET like a pro.
Real-Life Analogy: Airport Boarding Pass + Passport
A boarding pass (OAuth2 access token) proves the airline authorised you onto flight 123. A passport (OIDC ID token) proves who you are to immigration. Same journey, different checkpoints.
Choosing a Flow
Flow | Use Case | Notes |
---|---|---|
Auth Code + PKCE | SPAs / mobile | No client secret in browser. |
Client Credentials | Machine-to-machine | Service calls backend. |
Device Code | Smart-TV / CLI | Enter code on phone. |
Adding Authentication
builder.Services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", o =>
{
o.Authority = "https://login.microsoftonline.com/{tenantId}/v2.0";
o.TokenValidationParameters.ValidAudience = "api://my-api-id";
});
Adding Authorization Policies
services.AddAuthorization(o =>
{
o.AddPolicy("CanWriteOrders", p =>
p.RequireClaim("scope","orders.write"));
});
Protecting Endpoints
[Authorize(Policy = "CanWriteOrders")]
[HttpPost("orders")]
public IActionResult Create(OrderDto dto) { ... }
Exchanging Auth Code for Tokens
(SPA with MSAL)
const msal = new PublicClientApplication(cfg);
await msal.loginPopup({ scopes: ["api://my-api-id/orders.read"] });
Refreshing Access Tokens
Most OIDC providers return a refresh_token
for confidential clients.
SPAs use silent refresh with hidden iframe or token-renew endpoint.
Best-Practice Checklist
- Always use PKCE even for confidential clients—prevents auth-code interception.
- Validate
nonce
on ID tokens. - Never treat an access token as identity; use ID token or
/userinfo
. - Rotate client secrets annually.
Final Thoughts
OAuth2 grants permission; OpenID Connect proves identity. Master the flows, keep scopes tight, and your API will admit only ticketed, passport-holding travellers.