APIs are the gateway to your application’s data and capabilities — and with that comes responsibility. Two standards dominate modern API security: JSON Web Tokens (JWT) for stateless authentication and OAuth 2.0 for delegated authorization. Used together (or independently when appropriate), they enable scalable, secure identity and access control for ASP.NET Core Web APIs.
This guide explains the concepts, how they fit together, and practical best practices for production-ready API security — no code required.
1. Authentication vs Authorization — the quick distinction
- Authentication answers “Who are you?” — confirming identity. JWT is commonly used here.
- Authorization answers “What can you do?” — controlling access to resources. OAuth 2.0 handles delegated authorization and scopes.
Understanding both is essential: authentication provides a token that proves identity; authorization decides which operations that identity may perform.
2. JWT — what it is and when to use it
JSON Web Token (JWT) is a compact, URL-safe token format that encodes claims (user id, roles, expiry, etc.) and is digitally signed.
Why JWT:
- Stateless: server doesn’t need to store session state — ideal for horizontally scaled services.
- Compact & portable: easy to send in HTTP headers.
- Interoperable: supported across languages and platforms.
When to prefer JWT:
- Microservices or distributed APIs where keeping server-side session state is undesirable.
- Single Page Apps, mobile clients, or services that need short-lived, verifiable tokens.
- When you want the token to carry claims usable by downstream services without extra lookups.
Caveats:
- Tokens are self-contained; if you put too much sensitive data in them, you risk exposure.
- You must plan for token revocation and rotation since a JWT is valid until it expires (unless you implement revocation patterns).
3. JWT anatomy and security considerations (high level)
A JWT typically has three parts: header, payload (claims), and signature. Important considerations:
- Signing: Tokens must be signed — either with a symmetric key (HMAC) or an asymmetric pair (RSA/ECDSA). Asymmetric keys provide stronger security and easier rotation across services.
- Claims: Keep them minimal. Include only what consumers need (e.g., subject, roles, issued/expiry times, audience).
- Expiry (exp claim): Use short-lived access tokens (minutes). This reduces the risk if a token is leaked.
- Refresh tokens: Use refresh tokens to obtain new access tokens without forcing frequent logins. Store refresh tokens securely (usually server-side or in a secure, limited-scope storage).
- Audience and issuer: Validate aud (who the token is for) and iss (who issued it) to prevent token replay across different systems.
- Transport: Always serve tokens over HTTPS to prevent interception.
- Storage on clients: Avoid storing access tokens in insecure places (e.g., local storage in browsers) unless mitigations are in place; consider secure, HttpOnly cookies or browser-specific best practices and protections (e.g., same-site cookies, CSRF protections).
- No sensitive secrets in payload: Never include passwords or secrets in JWT claims — they are base64-encoded, not encrypted, unless you use encrypted JWT (JWE), which is less common.
4. OAuth 2.0 — the right tool for delegated access
OAuth 2.0 is an authorization framework designed for delegating limited access to protected resources. It defines roles and flows:
- Roles: Resource Owner (user), Client (app), Authorization Server (auth provider), Resource Server (API).
- Common flows:
OAuth 2.0 gives you:
- Scopes: fine-grained permissions (e.g., read:orders, write:orders).
- Delegation: allows third-party apps to act on behalf of users without sharing credentials.
- Standardized token lifecycle: access tokens (short-lived) + refresh tokens.
Use OAuth when:
- Third-party apps or services need delegated access.
- You want a standardized way to issue tokens, manage scopes, consent screens, and revocation.
5. How JWT and OAuth 2.0 work together
A common pattern is:
- Authorization Server (OAuth provider) authenticates user and issues an access token (often a JWT) and a refresh token.
- Client presents the access token to the Resource Server (your ASP.NET Core API).
- Resource Server validates the token and checks claims / scopes to authorize the request.
This separation lets you centralize authentication and authorization decisions (on the Authorization Server) while keeping Resource Servers focused on validating tokens and enforcing policies.
6. Token validation and authorization policies in ASP.NET Core (conceptual)
When your API receives a request with a token, it should:
- Validate signature: ensure token was issued by a trusted authority.
- Validate exp: reject expired tokens.
- Validate audience/issuer: confirm token is meant for your API.
- Verify scopes/claims: ensure the token has required permissions (scopes) or roles.
- Enforce fine-grained policies: map claims to authorization requirements (e.g., role-based access, resource ownership checks, claim-based rules).
Authorization policies let you express rules like:
- “Only users with the admin role can access this endpoint.”
- “Users can read their own orders but not others’.”
Policies should be declarative and applied consistently across endpoints.
7. Refresh tokens, rotation, and revocation
Short-lived access tokens reduce risk, but users still need a smooth experience. Use refresh tokens to obtain new access tokens. Best practices:
- Rotate refresh tokens: issue a new refresh token each time one is used; invalidate previous one. This reduces risk from stolen refresh tokens.
- Protect refresh tokens: treat them as highly sensitive credentials. Store them securely and limit their lifetime and scope.
- Revoke on suspicious activity: support token revocation (via blacklist or introspection endpoint) for compromised credentials or logout events.
Token revocation is harder with pure stateless JWTs; typical solutions:
- Maintain a revocation list/blacklist (short-lived entries).
- Use a token introspection endpoint on the Authorization Server.
- Use short access token lifetimes and rely on refresh token controls.
8. Additional hardening and operational best practices
Secure transport and headers
- Always use HTTPS.
- Apply security headers (CSP, HSTS, X-Frame-Options) as appropriate.
CORS and CSRF
- Configure CORS strictly to allow only required origins.
- If you use cookies for tokens, defend against CSRF with anti-forgery measures.
Secret & key management
- Use a secure secrets store (Key Vault, AWS Secrets Manager) for signing keys and client secrets.
- Rotate keys regularly and support key identifiers (kid) so older tokens can still be validated after rotation.
Rate limiting and throttling
- Apply rate limits to protect APIs from abuse and DDoS.
- Combine with IP-based protections and WAF if needed.
Logging, auditing & observability
- Log authentication/authorization events (success/failure) without logging raw tokens.
- Capture metrics: token validation failures, expired token rates, failed logins.
- Use centralized monitoring and alarms for suspicious spikes.
Least privilege & minimal scopes
- Issue tokens with the smallest scope necessary for the operation.
- Enforce least privilege in APIs and resource access.
Defense-in-depth
- Combine multiple layers: transport security, token validation, authorization policies, input validation, and monitoring.
9. Common mistakes & how to avoid them
- Long-lived access tokens — increase exposure window. Use short-lived access tokens and refresh tokens with controls.
- Storing tokens insecurely on clients — use platform-specific secure storage.
- Failing to validate aud or iss — allows tokens intended for other resources to be used.
- Embedding sensitive data in JWT claims — anyone with token can read claims unless encrypted.
- No key rotation plan — makes compromise long-lasting.
- Mixing authentication and authorization logic on the server — keep roles, claims, and policies clear and documented.
10. Deployment & production checklist (quick)
- Enforce HTTPS site-wide.
- Short-lived access tokens + secure refresh tokens.
- Validate signature, expiry, audience, and issuer on every request.
- Store signing keys in a secure vault and rotate keys regularly.
- Implement authorization policies for role/scope checks.
- Use PKCE for public clients (mobile/SPAs).
- Rate limit endpoints and enable global throttling.
- Add logging/monitoring and alerting for auth anomalies.
- Document token usage, scopes, and token lifecycles for your consumers.
- Test token revocation, expiry behavior, and edge cases before production.
11. Final thoughts
Securing ASP.NET Core Web APIs with JWT and OAuth 2.0 gives you a modern, scalable, and interoperable security architecture — when implemented thoughtfully. The keys to success are:
- Treat tokens as sensitive credentials.
- Use short-lived access tokens and secure refresh token strategies.
- Centralize authentication with an Authorization Server and enforce fine-grained authorization in your APIs.
- Protect keys, rotate them, and monitor auth events continuously.
When done right, JWT + OAuth 2.0 enables secure, decoupled systems that scale with confidence while offering a smooth user experience.