👋Hi, I'm Waqas — a Software Architect and Technical Consultant specializing in .NET, Azure, microservices, and API-first system design..
I help companies build reliable, maintainable, and high-performance backend platforms that scale.
OAuth2, OpenID Connect, JWT validation, and securing .NET APIs and SPAs.
May 24, 2025 · Waqas Ahmad
Read the article
Introduction
This guidance is relevant when the topic of this article applies to your system or design choices; it breaks down when constraints or context differ. I’ve applied it in real projects and refined the takeaways over time (as of 2026).
OAuth2 and OpenID Connect (OIDC) are the standards for modern auth: OAuth2 handles authorisation (apps accessing resources on behalf of users), OIDC adds authentication (who the user is). This article covers OAuth2 and OIDC in depth—flows, JWT validation in ASP.NET Core, securing APIs and SPAs, and common pitfalls. For architects and tech leads, getting flows and token validation right (Authority, Audience, PKCE for SPAs) is essential to avoid security gaps.
For a deeper overview of this topic, explore the full .NET Architecture guide.
System scale: APIs and SPAs (or native apps) that need to authenticate users or call APIs on behalf of users or services. Applies when you’re adding or refining auth (OAuth2/OIDC) in .NET.
Team size: Backend and front-end; someone must own token validation, scopes, and secure storage. Works when the team can configure an identity provider (Azure AD, Auth0, etc.) and validate JWTs correctly.
Time / budget pressure: Fits when you have an IdP and can use Authorization Code + PKCE for SPAs; breaks down when you try to invent your own auth or store secrets in the client.
Technical constraints:ASP.NET Core; OAuth2/OIDC; JWT validation; Azure AD, Auth0, or similar IdP. Assumes you use HTTPS and don’t log or expose tokens.
Non-goals: This article does not optimise for legacy or custom auth schemes; it focuses on OAuth2 and OpenID Connect in .NET with standard flows.
What is OAuth2?
OAuth2 is an authorisation framework. It lets a client application obtain limited access to a resource (API) on behalf of a user, without the user sharing their password.
JWT (JSON Web Token) is the common format for access and ID tokens. ASP.NET Core validates JWTs using the Microsoft.AspNetCore.Authentication.JwtBearer package.
// BFF pattern: proxy API calls, attach token server-side
app.MapGet("/api/orders", async (HttpContext context, IHttpClientFactory factory) =>
{
var accessToken = await context.GetTokenAsync("access_token");
var client = factory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
returnawait client.GetFromJsonAsync<Order[]>("https://api.example.com/orders");
});
Enterprise best practices
1. Use Azure AD or established IdP. Do not roll your own authentication.
2. Validate all claims. Issuer, audience, expiration, signature. Do not skip validation.
3. Use PKCE for all public clients. Required for SPAs and mobile apps.
4. Store refresh tokens securely. HttpOnly cookie or secure server-side storage.
5. Implement token revocation. For sensitive actions, check token validity against a revocation list.
6. Use short-lived access tokens. 5-60 minutes. Refresh tokens extend the session.
7. Monitor for anomalies. Log authentication events; alert on unusual patterns.
8. Rotate client secrets. Regularly rotate secrets for confidential clients.
Common issues
Issue
Cause
Fix
401 Unauthorized
Wrong audience or issuer
Match Authority and Audience exactly
Token expired
Lifetime exceeded
Use refresh token; reduce access token lifetime
Invalid signature
Wrong signing key
Ensure JWKS endpoint is correct
Missing scope
Token lacks required scope
Client must request scope; API must validate
CORS errors
Cross-origin blocked
Configure CORS for your SPA domain
Token in URL
Query string leaked in logs
Use Authorization header only
You can also explore more patterns in the .NET Architecture resource page.
Summary
OAuth2 handles authorisation; OpenID Connect adds authentication—use Authorization Code + PKCE for user login and Client Credentials for machine-to-machine, and validate JWTs with correct Authority, Audience, and scopes. Skipping audience or scope checks or storing tokens insecurely (e.g. localStorage for SPAs) creates security gaps; use httpOnly cookies or backend session where possible and never log tokens. Next, configure your IdP and ASP.NET Core with AddAuthentication(JwtBearer), set Authority and Audience, then add PKCE for SPAs and secure token storage.
Position & Rationale
I use Authorization Code + PKCE for SPAs and native apps—no client secret in the client; the IdP issues tokens after user consent. I use Client Credentials for server-to-server or background jobs when there’s no user. I validate JWTs with the correct Authority (issuer), Audience, and signing keys (JWKS); I never skip audience or scope checks. I store tokens in memory or secure storage (e.g. httpOnly cookie for web); I never put access or refresh tokens in localStorage for SPAs if I can avoid it (cookie or backend session is safer). I avoid implicit flow and resource-owner password flow for new apps; they’re deprecated or insecure. I don’t log tokens or put them in URLs.
Trade-Offs & Failure Modes
OAuth2/OIDC adds dependency on an IdP and correct validation; you gain standard, delegatable auth. PKCE adds a step in the flow; you gain security for public clients. JWT validation must be strict (issuer, audience, scope, expiry); wrong config leads to broken auth or over-privilege. Failure modes: wrong audience or scope validation (API accepts tokens meant for another app); tokens in URL or logs (leak); client secret in SPA (never do that); skipping PKCE for public clients.
What Most Guides Miss
Most guides show “add JWT bearer” but don’t stress audience validation—if you don’t validate audience, any token from the same IdP can call your API. Scope validation is often omitted; the API must check that the token has the scope for the operation. Token storage in SPAs (localStorage vs cookie vs backend session) has security trade-offs; many tutorials use localStorage without mentioning refresh token rotation and XSS. Refresh token handling (where to store, when to rotate) is underplayed.
Decision Framework
If user login (SPA or native) → Authorization Code + PKCE; no client secret in the client.
If server-to-server or daemon → Client Credentials; store client secret server-side only.
For API → Validate JWT: Authority, Audience, scopes; use JWKS from the IdP.
For tokens → Store in memory or secure cookie; never in URL or logs; use HTTPS.
For SPAs → Prefer backend session or secure cookie for tokens; if using localStorage, understand XSS and refresh rotation.
Key Takeaways
OAuth2 = authorisation; OIDC = authentication (ID token, user info). Use Authorization Code + PKCE for SPAs/native; Client Credentials for machine-to-machine.
Validate JWT: issuer, audience, scopes, expiry; use JWKS. Never skip audience.
Store tokens securely; never in URL or logs; never client secret in the client.
Avoid implicit and resource-owner password flows for new apps.
When I Would Use This Again — and When I Wouldn’t
I’d use OAuth2/OIDC again for any API or SPA that needs user or service auth—standard flows and JWT validation with a proper IdP. I’d use PKCE for every public client. I wouldn’t implement my own token issuance or store client secrets in the front end. I also wouldn’t deploy without validating audience and scopes on the API; otherwise tokens from other apps can be accepted.
Frequently Asked Questions
Frequently Asked Questions
What is OAuth2?
OAuth2 is an authorisation framework that lets apps access resources on behalf of users without sharing passwords. It issues access tokens.
What is OpenID Connect?
OIDC extends OAuth2 for authentication. It adds ID tokens containing user identity claims (sub, name, email).
What is JWT?
JWT (JSON Web Token) is a signed token format containing claims. Validated by the API using the issuer’s public key.
What is the difference between access token and ID token?
Access token authorises API calls. ID token contains user identity—use it in your app, not for API calls.
When should I use Authorization Code vs Client Credentials?
Authorization Code for user login (SPA, mobile, web). Client Credentials for service-to-service (no user).
What is PKCE?
PKCE (Proof Key for Code Exchange) secures the Authorization Code flow for public clients that cannot store a secret.
Where should I store tokens in a SPA?
In memory or httpOnly cookie. Never in localStorage (vulnerable to XSS).
How do I validate scopes in .NET?
Use policy-based authorization: options.AddPolicy("ReadAccess", policy => policy.RequireClaim("scp", "api.read")).
What is a refresh token?
A long-lived token used to obtain new access tokens without re-prompting the user.
How do I handle token expiration?
Use refresh tokens to get new access tokens silently. Implement token refresh in your client.
What is the BFF pattern?
Backend For Frontend keeps tokens server-side. The SPA uses a session cookie; the BFF proxies API calls with the token.
How do I revoke tokens?
Implement a token revocation endpoint or use short-lived tokens. For critical actions, check a revocation list.
What Authority should I use for Azure AD?
https://login.microsoftonline.com/{tenant}/v2.0 for v2.0 endpoint. Use your tenant ID or “common” for multi-tenant.
What is the difference between issuer and audience?
Issuer is who issued the token (the IdP). Audience is who the token is intended for (your API).
How do I debug JWT issues?
Decode the token at jwt.io (never in production with real tokens). Check issuer, audience, expiration, and scopes.
Related Guides & Resources
Explore the matching guide, related services, and more articles.