OAuth 2.0 and OIDC for Microservices

Learn how OAuth 2.0 and OpenID Connect enable delegated authorization and federated identity in microservices architectures.

published: reading time: 39 min read author: GeekWorkBench

OAuth 2.0 and OIDC for Microservices

Modern microservice architectures rarely keep authentication simple. A monolith might validate a username and password directly. Microservices face a harder problem: how do you validate identity when a request has already traveled through three services, an API gateway, and a message queue? OAuth 2.0 and OpenID Connect solve this by externalizing authentication and distributing identity verification across service boundaries.

Forget adding a login form. The real problem is building a system where services trust incoming requests, users can grant third-party apps access without sharing passwords, and your architecture scales without turning every microservice into an authentication service.

Introduction

ScenarioUse OAuth 2.0 / OIDCNotes
User authentication for web or mobile appsYesAuthorization Code flow with PKCE is standard
Service-to-service calls without shared secretsYesClient Credentials flow works well
Third-party application access to user dataYesOAuth’s delegated authorization shines here
Machine-to-machine with static API keysConsiderMay be simpler if you already have key infrastructure
Microservices behind an API gatewayYesCentralized token validation at the gateway
Long-lived background job authenticationConsiderRefresh token rotation adds complexity
Stateless serverless functionsYesJWT validation works well for cold starts
Real-time bidirectional communication (WebSocket)CautionToken validation on connect, re-auth on reconnect

Trade-offs

AspectAPI Keys / Basic AuthOAuth 2.0 / OIDC
Identity verificationNone or weakStrong (via IdP verification)
Delegated authorizationNot supportedFirst-class support
Token lifetimeUsually long-livedShort-lived with refresh
RevocationImmediate if key rotatedDelayed (until token expires)
Implementation complexityLowHigher
Standards complianceNoneRFC-compliant
User identity in tokensNot availableStandard in OIDC ID tokens
Multi-factor auth supportExternal to systemBuilt into IdP flows

When NOT to Use OAuth 2.0 / OIDC

  • Simple internal services behind strict network segmentation: If network access is tightly controlled and all clients are trusted, overhead may not justify the benefit
  • High-throughput, latency-critical paths where IdP is a single point of failure: JWT self-validation removes this risk; if you cannot tolerate IdP latency, use reference tokens
  • Legacy systems that cannot participate in token flows: Integration work may exceed benefit
  • Public APIs where you do not control the client: OAuth flows can be gamed; rate limiting and API keys may be more practical

What OAuth 2.0 Actually Does

OAuth 2.0 is an authorization framework, not an authentication protocol. That distinction matters. OAuth 2.0 lets a user grant a client application access to their resources on a resource server, without the client ever seeing the user’s credentials.

The classic example is a mobile app accessing your Google Drive files. You do not give the app your Google password. Instead, Google issues the app a token with limited permissions that you approved. The app presents that token to Google Drive, which validates it and serves the files.

The four roles in OAuth 2.0:

  • Resource owner: The user who owns the data
  • Client: The application requesting access
  • Authorization server: The system that authenticates the user and issues tokens
  • Resource server: The API that holds the protected resources

Authorization Code Flow

The Authorization Code flow is the standard for server-side web apps and SPAs with a backend. It involves more steps, but it keeps tokens away from browsers where they could be intercepted.

sequenceDiagram
    participant User
    participant Client as Client App
    participant Auth as Authorization Server
    participant Resource as Resource Server

    User->>Client: Click "Login with Google"
    Client->>User: Redirect to Authorization Server
    User->>Auth: Enter credentials, approve access
    Auth->>Client: Redirect with authorization code
    Client->>Auth: Exchange code for tokens
    Auth->>Client: Return access token + refresh token
    Client->>Resource: Request with access token
    Resource->>Client: Protected resource

The flow works like this: the client redirects the user to the authorization server with a client_id, redirect_uri, and requested scopes. The user authenticates and approves the request. The authorization server redirects back to the client with a short-lived authorization code. The client exchanges this code for tokens by calling the token endpoint directly, passing the code and a client_secret. The authorization server validates the code and returns an access token and optionally a refresh token.

The authorization code never appears in browser logs or server-side access logs. The token exchange happens on a secure channel between client and authorization server.

Client Credentials Flow

Client Credentials flow handles machine-to-machine communication where there is no user. A service needs to call another service’s API, and both services have their own credentials with the authorization server.

sequenceDiagram
    participant ServiceA as Service A
    participant Auth as Authorization Server
    participant ServiceB as Service B

    ServiceA->>Auth: Request token with client_id + client_secret
    Auth->>ServiceA: Return access token
    ServiceA->>ServiceB: Request with access token
    ServiceB->>ServiceA: Protected resource

In this flow, Service A authenticates directly with the authorization server using its own credentials (client_id and client_secret). There is no user interaction. The authorization server returns a token that Service A uses to call Service B. Service B validates the token and serves the request.

This is how microservices talk to each other without sharing passwords or API keys. Each service has its own identity, and the authorization server tracks who can call what.

Refresh Token Flow

Access tokens are short-lived. JWTs typically expire in 5 to 15 minutes. Refresh tokens live longer and let clients obtain new access tokens without re-authenticating the user.

sequenceDiagram
    participant Client
    participant Auth as Authorization Server

    Client->>Auth: Exchange refresh token for new access token
    Auth->>Client: Return new access token + new refresh token

The client sends the refresh token to the authorization server. If the refresh token is valid and not revoked, the server issues a new access token and optionally a new refresh token. The old refresh token is invalidated.

This rotation prevents replay attacks where a stolen refresh token becomes useless after use. Most production systems rotate refresh tokens on every use.

OpenID Connect: Adding Identity to OAuth 2.0

OAuth 2.0 tells you whether a request is authorized. It does not tell you who the user is. OpenID Connect (OIDC) adds an identity layer on top of OAuth 2.0, standardized as OIDC in 2014.

OIDC introduces the ID token, a JWT that contains claims about the user. Where an OAuth 2.0 access token is opaque to the client, an ID token is self-contained and verifiable.

ID Token vs Access Token

Here is the key distinction.

An access token is a bearer token that grants access to a resource server. The resource server validates the token and decides whether to serve the request. The client cannot read the access token; it is opaque to the client.

An ID token is a signed JWT that contains user information. The client can read the ID token directly. It proves the user authenticated with the authorization server and contains claims like their email, name, and unique identifier.

sequenceDiagram
    participant User
    participant Client
    participant Auth as Auth Server

    User->>Client: Initiates login
    Client->>Auth: Authentication request
    Auth->>User: Login prompt
    User->>Auth: Credentials
    Auth->>Client: ID token + Access token
    Client->>Client: Decode and read ID token
    Note over Client: User info: sub, email, name
    Client->>Auth: Use access token for API calls

For web applications, OIDC flows are similar to OAuth 2.0 flows but with an additional scope (openid) that signals the request is for identity information. The authorization server returns both an ID token and an access token.

Standard OIDC Scopes and Claims

OIDC defines standard scopes that map to sets of claims:

ScopeClaims
openidsub (user identifier)
profilename, family_name, given_name, preferred_username, picture
emailemail, email_verified
addressaddress
phonephone_number, phone_number_verified

The sub claim is the unique identifier for the user at the authorization server. It is the only required claim. Applications typically use the sub as the primary key for user records in their own databases.

JWT Tokens and Claims

JSON Web Tokens (JWTs) are the dominant token format for both access tokens and ID tokens in modern systems. A JWT is a base64-encoded, signed JSON object.

A JWT has three parts:

  • Header: Algorithm and token type
  • Payload: Claims (the data)
  • Signature: Verifies the token was issued by the expected party
// Decoded JWT payload example
{
  "iss": "https://auth.example.com",
  "sub": "user_12345",
  "aud": ["api.example.com", "mobile-app"],
  "exp": 1710930000,
  "iat": 1710926400,
  "scope": "read:profile read:orders",
  "email": "user@example.com",
  "name": "Jane Developer"
}

The claims above include the issuer (iss), subject (sub), audience (aud), expiration time (exp), issued-at time (iat), scopes, and user information. The signature allows any party with the public key to verify the token was issued by the expected authorization server.

Why JWTs Work Well for Microservices

JWTs are self-contained. A service can validate a JWT without calling back to the authorization server for every request. The signature proves authenticity. The claims are visible to the service. The expiration time is enforced locally.

In microservices, network calls have latency. If every service has to call an authorization server to validate a token, you have created a bottleneck. JWT validation is local and fast.

Token Validation in Microservices

Validating a JWT is straightforward but has several steps that must all pass.

Signature Validation

The token was signed by the authorization server. To verify this, you need the public key corresponding to the private key that signed the token.

In practice, authorization servers publish their public keys at a well-known URL called the JWKS endpoint. Services cache these keys and refresh them periodically. When a token arrives, the service finds the matching key by the key ID (kid) in the token header and verifies the signature.

import { createRemoteJWKSet, jwtVerify } from "jose";

const JWKS = createRemoteJWKSet(
  new URL("https://auth.example.com/.well-known/jwks.json"),
);

async function validateToken(token) {
  const { payload } = await jwtVerify(token, JWKS, {
    issuer: "https://auth.example.com",
    audience: "api.example.com",
  });
  return payload;
}

Expiration and Time Validation

Tokens carry an exp claim. Your service must reject tokens where exp is in the past. Clock skew between services can cause valid tokens to appear expired, so most systems allow a small tolerance (usually 30 to 60 seconds) when validating expiration.

Audience Validation

The aud claim specifies who the token is intended for. A token issued for mobile-app should not grant access to api.example.com. Your service must verify the audience claim matches its own identifier.

Scope and Permission Validation

Tokens may contain a scope or scp claim listing what the token allows. Before performing an action, your service should verify the token includes the required scope.

function requireScope(payload, requiredScope) {
  const scopes = (payload.scope || "").split(" ");
  if (!scopes.includes(requiredScope)) {
    throw new Error(`Missing required scope: ${requiredScope}`);
  }
}

SSO Patterns in Microservices

SSO means you log in once and then access multiple services without being asked for credentials again. OAuth 2.0 and OIDC make this work by sharing authentication through a common IdP.

How SSO Works with OIDC

The IdP holds the user’s session. When you redirect to the IdP from any client app, it checks for an existing session cookie first:

sequenceDiagram
    participant User
    participant ClientA as Client App A
    participant ClientB as Client App B
    participant IdP as Identity Provider

    User->>ClientA: Access App A (no session)
    ClientA->>User: Redirect to IdP login
    User->>IdP: Authenticate
    IdP->>User: Set session cookie
    IdP->>ClientA: Redirect with code
    ClientA->>IdP: Exchange code for tokens
    IdP->>ClientA: ID token + Access token
    ClientA->>User: Logged in to App A

    User->>ClientB: Access App B (no session)
    ClientB->>User: Redirect to IdP
    User->>IdP: Already authenticated (cookie)
    IdP->>User: Redirect with code (no login)
    ClientB->>IdP: Exchange code for tokens
    IdP->>ClientB: ID token + Access token
    ClientB->>User: Logged in to App B

That session cookie is what makes the second login disappear. The IdP sees it and skips the prompt.

Session State Considerations

You can manage session state a few different ways:

IdP-managed sessions: The IdP owns the session completely. RPs use short-lived tokens and do not track their own user sessions. This is the standard OAuth/OIDC approach.

Shared session store: Applications keep local sessions but subscribe to logout events through a shared store or the IdP’s back-channel logout endpoint. This gets you faster local session invalidation than waiting for tokens to expire.

// IdP-managed session validation
async function validateUserSession(token) {
  // Token validation is local (JWT) or via introspection (reference token)
  const payload = await validateToken(token);

  // Check if token is still valid (not revoked, not expired)
  // IdP session state is NOT checked here - that's the IdP's job
  return payload;
}

Browsers restrict how cookies work across sites, which directly impacts OAuth redirects:

  • SameSite=Strict: Cookies only go to the same site that set them. OAuth redirects break because the IdP redirect crosses site boundaries.
  • SameSite=Lax: Cookies travel on top-level navigations from other sites. Works for most OAuth flows but silently fails for iframe-based silent auth.
  • SameSite=None; Secure: Cookies work everywhere. Required for cross-site OAuth but introduces CSRF risk.

Chrome 80 and later default to Lax. SPAs work around this by keeping redirect URIs on the same origin, or by using the storage access API for cross-origin token storage.

Identity Platform SSO Features

The major platforms layer additional features on top of the basic protocol:

Session management: Auth0, Okta, and Keycloak give administrators consoles to view active sessions, force logout, set lifetime policies, and distinguish passive from active authentication requirements.

Application integration: IdPs track metadata (redirect URIs, logout URIs, PKCE requirements) for every registered client and enforce consistent security policies across all of them.

Multi-IdP support: Large enterprises sometimes need to authenticate against multiple IdPs simultaneously (corporate IdP plus social providers, for instance). Platforms like Auth0 handle this at the connection level rather than the application level.

Federated Identity Providers

Most organizations do not build their own authorization servers. They use identity providers (IdPs) that implement OAuth 2.0 and OIDC.

Keycloak

Keycloak is an open-source identity and access management solution. You deploy it as a service, define realms (tenant separation), clients, and users. It supports standard protocols and integrates with LDAP and Active Directory.

For microservice architectures, Keycloak can act as the authorization server, issuing tokens for your services and enforcing realm-level policies.

Auth0

Auth0 is a managed identity platform. It handles the infrastructure, handles edge cases like brute force protection and credential stuffing detection, and provides SDKs for every platform. You configure connections to social identity providers (Google, GitHub) and enterprise providers (SAML, OIDC).

Auth0 abstracts the complexity of multi-factor authentication, password policies, and credential storage.

Okta

Okta is an enterprise identity platform with similar capabilities. It focuses on workforce identity (employees accessing corporate applications) but also supports customer identity scenarios.

All three work with microservices architectures. The choice depends on whether you want self-hosted or managed, budget, and enterprise integration requirements.

Scope-Based Authorization

OAuth 2.0 scopes are coarse-grained permission labels. read:orders means you can read orders. The resource server decides what that means.

In practice, scope validation happens at two levels. The API gateway validates scopes before forwarding requests to services. Individual services validate scopes for fine-grained operations.

GET /api/orders/12345
Authorization: Bearer eyJhbGc...
// Service-level scope check
app.get("/api/orders/:id", async (req, res) => {
  const token = req.token;

  // Gateway already validated this scope exists
  if (!token.scope.includes("read:orders")) {
    return res.status(403).json({ error: "Forbidden" });
  }

  // Service enforces that user owns the order
  const order = await db.getOrder(req.params.id);
  if (order.userId !== token.sub) {
    return res.status(403).json({ error: "Forbidden" });
  }

  res.json(order);
});

The gateway handles the coarse check. The service handles the fine-grained check. This separation keeps the gateway simple while allowing services to enforce their own business rules.

API Gateway Integration

The API gateway is the natural place to validate tokens before they reach your services. This centralizes authentication logic and keeps services focused on business logic.

graph TD
    A[Client] --> B[API Gateway]
    B --> C{Token Valid?}
    C -->|No| D[401 Unauthorized]
    C -->|Yes| E{Scope OK?}
    E -->|No| F[403 Forbidden]
    E -->|Yes| G[Route to Service]
    G --> H[Order Service]
    G --> I[Product Service]
    H --> J[Return Response]
    I --> J

When a request arrives, the gateway extracts the bearer token, validates it (signature, expiration, audience), checks the required scope, and either rejects the request or forwards it with the token claims attached as headers.

// Gateway middleware
async function authenticate(req, res, next) {
  const token = req.headers.authorization?.replace("Bearer ", "");

  if (!token) {
    return res.status(401).json({ error: "Missing token" });
  }

  try {
    const payload = await validateToken(token);
    req.user = payload;
    next();
  } catch (error) {
    return res.status(401).json({ error: "Invalid token" });
  }
}

Services behind the gateway receive requests with a verified user context. They trust the gateway because the gateway is inside the network boundary and the token has already been validated.

See API Gateway for a comprehensive overview of gateway patterns including authentication, rate limiting, and failure handling.

Machine-to-Machine Authentication

Microservices talking to each other need identity too. The Client Credentials flow handles this.

Each service has its own client_id and client_secret. To call another service, it authenticates with the authorization server, receives a token, and uses that token for the call.

// Service-to-service token request
async function getServiceToken() {
  const response = await fetch("https://auth.example.com/oauth/token", {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      grant_type: "client_credentials",
      client_id: process.env.SERVICE_CLIENT_ID,
      client_secret: process.env.SERVICE_CLIENT_SECRET,
      scope: "read:inventory write:orders",
    }),
  });

  const { access_token, expires_in } = await response.json();
  return { token: access_token, expiresIn: expires_in };
}

// Cached token with refresh before expiry
const tokenCache = { token: null, expiresAt: 0 };

async function getValidToken() {
  if (Date.now() >= tokenCache.expiresAt - 60000) {
    const { token, expiresIn } = await getServiceToken();
    tokenCache = { token, expiresAt: Date.now() + expiresIn * 1000 };
  }
  return tokenCache.token;
}

Services should cache tokens and refresh them before expiry. Calling the authorization server on every request adds latency and load. A short buffer before expiration prevents race conditions where a token expires mid-request.

Security Considerations

Token-based authentication introduces security concerns that password-based systems do not have.

Token storage: Access tokens in browser localStorage are vulnerable to XSS attacks. HttpOnly cookies are safer for browser-based applications. Service-to-service tokens should be stored in memory or secure vaults.

Token revocation: JWTs cannot be revoked before expiration. If a token is stolen, it remains valid until it expires. This is why short expiration times matter. For high-security applications, consider using reference tokens (opaque tokens that require a validation call) instead of JWTs.

Scope creep: Request only the scopes you need. A compromised token with admin:* is far more dangerous than one with read:profile.

Client secrets: Treat client secrets like passwords. Rotate them regularly. Do not commit them to source control. Use secret management systems for production deployments.

For more on securing your infrastructure, see Rate Limiting for protecting APIs from abuse and Service Mesh for mutual TLS between services.

Token Security Analysis: Common Attack Vectors

Understanding how tokens get compromised changes how you think about design decisions.

Authorization Code Interception: In poorly configured systems, authorization codes can be intercepted via browser history, referrer headers, or server logs. Always use PKCE for public clients and ensure code_verifier complexity meets security requirements.

Token Replay Attacks: Refresh tokens intercepted in transit can be replayed to obtain new access tokens. Mitigation includes token rotation, binding refresh tokens to device fingerprints, and monitoring for impossible travel patterns at the IdP level.

Scope Manipulation: Malicious clients may attempt to escalate privileges by modifying the scope parameter in token exchange requests. Always validate that granted scopes match what was originally requested, and reject responses containing scopes you did not ask for.

Client Impersonation: Without proper client authentication, a malicious actor could craft token requests that appear to come from your registered application. Use client_secret for confidential clients, mTLS certificates for service-to-service flows, and client_id binding in token responses.

MFA Implementation Patterns via OIDC

OIDC lets you layer additional authentication factors without building MFA logic into your application code.

Major identity providers (Keycloak, Auth0, Okta) support MFA configuration at the authorization server level. When MFA is enabled, the IdP enforces it before issuing tokens — your services just receive the result.

The amr (Authentication Methods References) claim in ID tokens tells you which factors were used:

// Example ID token payload with MFA information
{
  "sub": "user_12345",
  "amr": ["pwd", "otp"],  // Password + One-Time Password (TOTP)
  "auth_time": 1710930000,
  "acr": "http://schemas.openid.net/claims/acr/values/authenticator"
}

Step-up Authentication: For sensitive operations, services can require tokens with specific acr (Authentication Context Class Reference) values indicating higher assurance. Users who authenticated with only password get a lower-privilege token; those with MFA get tokens that can access sensitive endpoints.

Patterns for MFA Integration:

  • Configure MFA policies at the IdP (not in your application code)
  • Use amr claims to audit MFA usage in your services
  • Implement step-up authentication for sensitive operations by validating acr values
  • For APIs called by automated systems, use mTLS client certificates instead of MFA (MFA is designed for human users)

Common Pitfalls / Anti-Patterns

Treating access tokens as ID tokens: Access tokens are opaque to clients. Do not try to decode them for user information unless you control the authorization server and know the format.

Skipping audience validation: A token meant for one API should not work on another. Always validate the audience claim.

Long-lived tokens: Access tokens should be short (5 to 15 minutes). Refresh tokens handle session persistence. Long-lived access tokens are an unacceptable risk if leaked.

No token refresh handling: Clients must handle token expiration gracefully. Unhandled expiration mid-session produces confusing 401 errors for users.

Verifying only the signature: Signature validation alone is insufficient. Check expiration, audience, and issuer too.

Production Failure Scenarios

Failure Scenarios and Mitigations

Scenario: Authorization Server Unavailable

Symptoms: All requests start returning 401/403. Services cannot obtain tokens. Users cannot log in.

Diagnosis:

# Check authorization server health
curl -s https://auth.example.com/.well-known/openid-configuration | jq '.issuer'

# Check token endpoint availability
curl -s -X POST https://auth.example.com/oauth/token -d "grant_type=client_credentials" -d "client_id=test"

# Check JWKS endpoint
curl -s https://auth.example.com/.well-known/jwks.json | jq '.keys | length'

Mitigation:

  1. If using JWTs with local validation, services should continue working until their cached JWKS expires
  2. If using reference tokens requiring validation calls, switch to allowlist mode temporarily
  3. Scale the authorization server if it is overloaded
  4. Check network policies and DNS resolution

Prevention:

  • Run authorization server with high availability (multiple replicas)
  • Use JWTs with local validation to eliminate single point of failure
  • Cache JWKS keys with appropriate TTL (typically 1-24 hours)
  • Monitor authorization server health and set alerts

Scenario: Client Credentials Leaked

Symptoms: Unauthorized usage detected in logs. Unexpected API calls from unknown sources. Unusual patterns in access logs.

Diagnosis:

# Review token issuance logs
# Look for tokens issued to your client_id at unusual times or from unexpected IPs

# Check token introspection for recent tokens
curl -s -X POST https://auth.example.com/oauth/introspect \
  -d "token=<suspicious_token>" -d "client_id=<your_client_id>"

# Audit client usage
# Check which scopes were requested vs what your application normally uses

Mitigation:

  1. Immediately revoke the client secret: rotate credentials in IdP admin console
  2. Invalidate all existing tokens for that client (if IdP supports bulk revocation)
  3. Audit which resources were accessed with the compromised credentials
  4. Review logs for data exfiltration or unauthorized actions
  5. If tokens are short-lived, you may need to wait for expiry rather than revoke

Prevention:

  • Store client secrets in Vault or a secrets manager, never in environment variables or code
  • Use mTLS for service-to-service communication instead of client credentials where possible
  • Set alerts for unusual token issuance patterns
  • Implement IP allowlisting for client credential flows if IdP supports it

Scenario: JWT Validation Bypass via Algorithm Confusion

Symptoms: Security audit finds endpoints accepting tokens with “none” algorithm. Vulnerability scanners detect algorithm confusion attacks.

Diagnosis:

# Test your token validation with a tampered token
# RS256 token sent to HS256 endpoint can leak secret if misconfigured

# Check which algorithms your validation library accepts
# Most should only accept the expected algorithm (RS256, ES256, etc.)

Mitigation:

  1. Update token validation to explicitly specify and check the expected algorithm
  2. Reject tokens with “none” algorithm
  3. Do not accept different key types than expected (e.g., symmetric keys for asymmetric algorithms)
  4. Rotate signing keys if compromise is suspected

Prevention:

  • Always specify expected algorithm explicitly in validation code
  • Use a validation library that rejects algorithm confusion attacks
  • Include algorithm in your token validation checks alongside signature verification
  • Run security scans against your token validation endpoints

Scenario: Refresh Token Leak

Symptoms: Users report being logged out unexpectedly. Concurrent session anomalies. Access from unexpected locations.

Diagnosis:

# Check IdP for refresh token usage logs
# Look for refresh tokens being used from multiple IPs simultaneously

# Check active sessions for affected users
# In Keycloak: ./kcadm.sh get sessions <user-id>
# In Auth0: Check breach detection dashboard

Mitigation:

  1. If IdP supports per-user token revocation, revoke all refresh tokens for affected users
  2. Force re-authentication for affected users
  3. If using Auth0 or similar managed IdP, enable anomaly detection to auto-revoke on suspicious activity
  4. Notify affected users of the security event

Prevention:

  • Use refresh token rotation (new refresh token on each use) to limit exposure
  • Store refresh tokens in HttpOnly cookies, not localStorage
  • Implement refresh token binding (token-bound references)
  • Enable IdP anomaly detection (impossible travel, new device detection)

Observability Hooks

Metrics to Capture

MetricWhat It Tells YouAlert Threshold
token_issuance_totalToken issuance rate by grant typeUnexpected grant type volume
token_validation_failure_totalValidation failures by reason>1% failure rate
token_validation_duration_secondsToken validation latencyp99 > 100ms
jwks_cache_miss_totalJWKS fetches from IdP>10% miss rate indicates cache misconfiguration
active_sessions_totalCurrently active refresh tokensSudden drop indicates revocation event
oauth_error_totalOAuth errors by error codeAny increase in invalid_grant

Logs to Collect

From API Gateway (structured logging):

{
  "event": "token_validated",
  "trace_id": "abc123",
  "client_id": "my-service",
  "grant_type": "client_credentials",
  "scopes": ["read:orders", "write:inventory"],
  "validation_result": "success|failure",
  "failure_reason": "expired|invalid_signature|wrong_audience",
  "auth_server": "keycloak",
  "duration_ms": 5
}
{
  "event": "token_issued",
  "client_id": "my-service",
  "grant_type": "authorization_code",
  "user_id": "user_12345",
  "scopes": ["openid", "profile", "email"],
  "token_type": "access|refresh|id",
  "expires_in": 3600,
  "auth_server": "keycloak"
}

Key log fields: client_id, user_id (if applicable), grant_type, scopes, validation result, failure reason, auth server, duration.

Traces to Capture

Enable tracing in API gateway and authorization server. Key span attributes:

  • oauth.client_id: Client identifier
  • oauth.grant_type: authorization_code, client_credentials, refresh_token
  • oauth.scopes: Array of requested scopes
  • oauth.validation.result: success, failure
  • oauth.failure.reason: expired, invalid_signature, invalid_audience, insufficient_scope

Dashboards to Build

  1. OAuth/OIDC Health: Token issuance rate, validation success/failure ratio, error breakdown
  2. Token Lifecycle: Average token lifetime, refresh rate, revocation events
  3. Client Activity: Token usage by client, scope distribution, unusual client behavior
  4. Authorization Server: Request latency, error rate, JWKS cache hit ratio

Alerting Rules

# Token validation failures
- alert: TokenValidationFailures
  expr: rate(token_validation_failure_total[5m]) > 0.01
  labels:
    severity: warning
  annotations:
    summary: "Token validation failure rate above 1%"

# Authorization server down
- alert: AuthorizationServerDown
  expr: up{job="auth-server"} == 0
  labels:
    severity: critical
  annotations:
    summary: "Authorization server is unavailable"

# JWKS cache misses
- alert: JWKSMissRateHigh
  expr: rate(jwks_cache_miss_total[5m]) / rate(jwks_cache_request_total[5m]) > 0.1
  labels:
    severity: warning
  annotations:
    summary: "JWKS cache miss rate above 10%"

# Unusual token issuance
- alert: UnusualTokenIssuance
  expr: rate(token_issuance_total{grant_type="client_credentials"}[15m]) > 10 * avg(rate(token_issuance_total{grant_type="client_credentials"}[1h]))
  labels:
    severity: warning
  annotations:
    summary: "Unusual spike in client credential token issuance"

Quick Recap

  • OAuth 2.0 is an authorization framework; OIDC adds identity on top with standardized user claims in ID tokens
  • Authorization Code flow with PKCE is the standard for user-facing apps; Client Credentials flow is for service-to-service
  • JWT validation must check signature, expiration, audience, and issuer; skipping any of these creates security gaps
  • JWKS caching eliminates authorization server as a per-request bottleneck; cache keys appropriately (1-24h typical)
  • API gateway centralizes token validation so services can trust incoming user context without re-validation
  • Authorization server unavailability is a single point of failure; use JWT self-validation to make services resilient
  • Algorithm confusion attacks (RS256 vs HS256) are a real vulnerability; always specify expected algorithm explicitly
  • Refresh token rotation limits exposure if a token is stolen; enable rotation and store tokens in HttpOnly cookies
  • Federated identity providers (Keycloak, Auth0, Okta) handle the complexity so you do not build auth yourself

Interview Questions

1. Explain the difference between OAuth 2.0 and OpenID Connect. When would you use one over the other?

Expected answer points:

  • OAuth 2.0 is an authorization framework for delegated access; OIDC is an identity layer on top of OAuth 2.0
  • OAuth 2.0 answers "what can this client access?" while OIDC answers "who is the user?"
  • OIDC adds ID tokens (signed JWTs) containing user claims like email, name, and sub identifier
  • Use OAuth 2.0 when you only need authorization (e.g., service-to-service calls with Client Credentials)
  • Use OIDC when you need user identity information for your application (user-facing apps needing authentication)
  • Both can coexist; an OIDC flow returns both access tokens and ID tokens
2. What is PKCE and why is it important for public clients like SPAs and mobile apps?

Expected answer points:

  • PKCE (Proof Key for Code Exchange) adds a cryptographic verifier to prevent authorization code interception attacks
  • Public clients cannot safely store client secrets, making them vulnerable to code interception
  • PKCE works by having the client generate a random code_verifier, send its hash (code_challenge) with the auth request, and prove possession of the verifier when exchanging the code
  • An intercepted authorization code alone cannot be exchanged without knowing the original code_verifier
  • RFC 7636 standardizes PKCE and it is now recommended for ALL OAuth flows, not just public clients
3. Walk through the JWT validation steps a microservice must perform before trusting a token.

Expected answer points:

  • Extract the token from the Authorization header and parse the JWT structure (header, payload, signature)
  • Signature validation: fetch public keys from the IdP's JWKS endpoint, match by kid, verify cryptographic signature using the matching key
  • Expiration check (exp claim): reject tokens where exp is in the past, accounting for clock skew tolerance (30-60 seconds)
  • Issuer validation (iss claim): verify the token was issued by your expected authorization server
  • Audience validation (aud claim): confirm the token is intended for your service, not another API
  • Optional but recommended: scope validation against required permissions before performing actions
4. What are the trade-offs between JWTs and reference tokens in microservices authentication?

Expected answer points:

  • JWTs are self-contained: validation is local, no network call needed, better latency and availability
  • Reference tokens are opaque: require a call to the authorization server for validation, adding latency and a dependency
  • JWT drawback: cannot be revoked before expiration; if compromised, you must wait for expiry
  • Reference token advantage: can be revoked immediately, useful for high-security scenarios
  • Hybrid approach: short-lived JWTs for normal operations, reference tokens for high-privilege actions requiring immediate revocation capability
  • For most microservices, JWTs with short expiration (5-15 minutes) balance performance and security adequately
5. Describe the security implications of storing access tokens in browser localStorage versus HttpOnly cookies.

Expected answer points:

  • localStorage is accessible via JavaScript, making it vulnerable to XSS attacks that can exfiltrate tokens
  • HttpOnly cookies are inaccessible to JavaScript, protecting tokens from XSS-based theft
  • HttpOnly cookies are also protected against CSRF by using `SameSite` attribute and CSRF tokens
  • localStorage persists across browser sessions until explicitly cleared; cookie expiration is controlled
  • For browser-based applications, HttpOnly cookies with Secure flag (HTTPS-only) is the recommended approach
  • Refresh tokens should always use HttpOnly cookies; access tokens can use memory (not stored) for highest security
6. How does refresh token rotation work and what security benefits does it provide?

Expected answer points:

  • On each refresh token exchange, the IdP issues a new access token AND a new refresh token
  • The old refresh token is invalidated immediately after the exchange
  • If a stolen refresh token is used: the legitimate refresh also occurs, causing a token mismatch the IdP can detect
  • Detection triggers revocation of all refresh tokens for that user session, limiting exposure
  • Rotation limits the window of opportunity: a stolen refresh token becomes useless after its first use by the attacker
  • Must implement proper token storage (HttpOnly cookies) and handle the case where rotation fails (token reuse detection)
7. What is an algorithm confusion attack in JWT validation and how do you prevent it?

Expected answer points:

  • An attacker crafts a token signed with a symmetric algorithm (e.g., HS256) using the public key as the secret
  • If the server accepts both asymmetric algorithms (RS256) and symmetric (HS256), the attacker can forge tokens
  • The server's public key (meant for RS256 verification) becomes the HMAC secret for HS256 validation
  • Prevention: explicitly specify the expected algorithm in validation code (e.g., only accept "RS256")
  • Reject tokens with "none" algorithm entirely
  • Use a validation library that defaults to rejecting algorithm confusion rather than accepting it
  • Rotate signing keys immediately if compromise is suspected
8. When would you choose Client Credentials flow over Authorization Code flow? Give concrete examples.

Expected answer points:

  • Client Credentials is for service-to-service (machine-to-machine) communication where no user is involved
  • Authorization Code is for user-facing applications where a user grants access to their resources
  • Client Credentials example: a background job service calling an inventory API to update stock levels
  • Client Credentials example: microservice A calling microservice B's internal API
  • Authorization Code example: a web app accessing Google Drive on behalf of a logged-in user
  • Client Credentials cannot be used when you need to know which user is making the request; it only identifies the client
9. Explain the role of JWKS in token validation and how caching affects system reliability.

Expected answer points:

  • JWKS (JSON Web Key Set) is a JSON document published by the IdP at a well-known endpoint containing public keys
  • Services use these public keys to verify JWT signatures locally without calling the IdP
  • Each key has a "kid" (key ID) that appears in token headers, allowing services to select the correct key
  • Caching: services cache JWKS documents and refresh periodically (TTL typically 1-24 hours) to avoid per-request fetches
  • Benefit: token validation remains fast and the IdP is not a bottleneck or single point of failure
  • Risk: if a signing key rotates and cache is too long, validation failures occur until cache expires
  • Recommendation: set appropriate TTL, have fallback fresh fetch on validation failure, monitor cache hit rates
10. How would you design a token refresh strategy that handles concurrent requests gracefully?

Expected answer points:

  • Problem: multiple concurrent requests all see an expired access token and attempt to refresh simultaneously
  • Solution 1: token cache with expiry timestamp; check if refresh is needed before using the token
  • Solution 2: use a mutex or lock at the process level so only one refresh happens at a time
  • Solution 3: refresh early (before actual expiry with a buffer, e.g., expiry - 60 seconds) to avoid concurrent expiration
  • Implement graceful degradation: if refresh fails and token is not yet expired, allow the request to proceed with the existing token
  • Centralize token management in a service/utility rather than having each component implement its own refresh logic
  • Handle refresh failures with circuit breaker pattern to prevent thundering herd on IdP
11. What is the purpose of the nonce claim in OIDC and how does it prevent replay attacks?

Expected answer points:

  • Nonce is a random string included in the authentication request that gets returned in the ID token
  • The client generates a nonce, includes it in the auth request (as a hash in code flow with PKCE, or directly in implicit/hybrid flows)
  • The authorization server includes the nonce hash in the ID token
  • The client verifies the nonce in the returned ID token matches what it sent
  • If an attacker intercepts the ID token and tries to replay it, the nonce will not match or already be used
  • Nonce binding prevents attackers from intercepting and replaying ID tokens to impersonate users
12. Explain the difference between the Authorization Code flow with PKCE versus the standard Authorization Code flow. When is PKCE required?

Expected answer points:

  • Standard Authorization Code flow uses a client_secret to authenticate when exchanging the code for tokens
  • PKCE (Proof Key for Code Exchange) replaces the static client_secret with a dynamic code_verifier
  • PKCE flow: client generates random code_verifier, sends SHA256 hash (code_challenge) with auth request, sends original verifier when exchanging code
  • PKCE protects against authorization code interception attacks where an attacker steals the code from a redirect URI
  • PKCE is REQUIRED for public clients (SPAs, mobile apps) that cannot safely store client_secret
  • PKCE is now RECOMMENDED for all OAuth flows including confidential clients as defense in depth
13. How does the `acr` (Authentication Context Class Reference) claim differ from the `amr` (Authentication Methods References) claim in OIDC?

Expected answer points:

  • acr indicates the authentication context class, representing the LEVEL of assurance (e.g., phising-resistant, MFA present)
  • amr indicates the specific AUTHENTICATION METHOD used (e.g., password, OTP, biometric)
  • acr values are often defined by the IdP or standards like NIST AAL levels; they signal whether step-up authentication occurred
  • amr values are defined by OIDC spec (pwd, otp, swk,hwk, etc.) and tell you WHAT factors were used
  • Use acr for authorization decisions (does this token meet our assurance requirements?)
  • Use amr for audit/compliance (log what authentication methods were used for this session)
14. What is silent authentication in OIDC and when would you use it?

Expected answer points:

  • Silent authentication uses a stored refresh token to obtain new tokens without user interaction (no login prompt)
  • The IdP checks for an existing session cookie or silent refresh grant type
  • If session is valid and not expired, new tokens are returned without user interaction
  • Use cases: keeping a web app session alive, preloading user data on page load
  • Benefits: better UX (no login interruption), reduces authentication load on IdP
  • Caveats: requires refresh token storage, may be blocked by browsers with strict cookie policies (ITP, Firefox Total Cookie Protection)
15. How would you implement logout in an OAuth 2.0 / OIDC system? Describe the steps involved in single logout.

Expected answer points:

  • Client-side logout: clear local tokens, session cookies, and any stored state
  • RP-initiated logout: call the IdP's logout endpoint to invalidate the IdP session
  • IdP can then trigger Single Logout (SLO) by calling logout endpoints on other RPs that were accessed in the same session
  • Token revocation: call the authorization server's token revocation endpoint to invalidate refresh tokens
  • Front-channel logout: IdP redirects to RP logout URLs with iframes (less secure, cache issues)
  • Back-channel logout: IdP makes direct POST calls to RP logout endpoints (more secure, requires RPs to expose endpoints)
  • Session invalidation: clear local sessions across all services, not just the originating one
16. What are the security implications of using the `state` parameter in OAuth 2.0 authorization requests?

Expected answer points:

  • State is an opaque random string the client generates and includes in the initial authorization request
  • The authorization server returns state unchanged in the redirect, allowing the client to verify the redirect came from the expected request
  • State prevents Cross-Site Request Forgery (CSRF) attacks: an attacker could trick a user into authorizing an authorization code grant with the attacker's client_id
  • Without state validation, the attacker could intercept the authorization code and exchange it before the legitimate user
  • State should be: cryptographically random, stored server-side in session, validated on redirect before code exchange
  • State is REQUIRED for OAuth 2.0 security; never process an authorization response without validating state
17. Describe how to implement fine-grained authorization using OAuth 2.0 scopes beyond simple coarse-grained access control.

Expected answer points:

  • Coarse-grained: `read:orders` grants read access to all orders
  • Fine-grained: combine scopes with attributes (e.g., `read:orders:own` or resource-based policies)
  • Resource indicators (RFC 8707) allow specifying which resource API the token is for when calling multiple APIs
  • Scope hierarchy: define scope groups like `orders:read` that implies `orders:read:basic`
  • Entitlement systems: externalize authorization decisions to a policy engine (Open Policy Agent) that evaluates token claims against resource attributes
  • Example: `read:orders` scope + order.userId == token.sub claim = authorized to read specific order
  • Avoid scope explosion: use resource prefixes, group related permissions, consider ABAC for complex scenarios
18. How does the `auth_time` claim in OIDC ID tokens help with session management and security?

Expected answer points:

  • auth_time records when the user last actively authenticated with the IdP (seconds since Unix epoch)
  • Clients use auth_time to detect if a new authentication has occurred since the last ID token was issued
  • If auth_time is newer than expected, the client may need to re-authenticate for sensitive operations
  • auth_time enables "passive authentication" refresh: check if auth_time has changed to know if session is still valid
  • For step-up authentication scenarios, clients can check if auth_time is recent enough for the required assurance level
  • Note: auth_time tells when authentication happened, not when the current ID token was issued (iat claim)
19. What is token binding and how does it help mitigate token theft attacks?

Expected answer points:

  • Token binding ties tokens to a cryptographic key or device that the client proves possession of
  • Access token binding: bind token to TLS channel, client certificate, or asymmetric key pair
  • If a token is stolen and used from a different context (different IP, device, TLS fingerprint), the binding check fails
  • For browser-based apps: token binding to TLS channel state (Channel ID, Certificate Bound Access Tokens)
  • Refresh token rotation already provides some binding via device fingerprinting, but explicit binding is stronger
  • Trade-offs: adds complexity, requires IdP support, may break in certain network configurations
20. Explain how OAuth 2.0 and OIDC integrate with a service mesh for zero-trust networking. How do mTLS and token validation work together?

Expected answer points:

  • Service mesh (Linkerd, Istio) provides mTLS for cryptographically authenticating which service is making the request
  • mTLS alone identifies the SERVICE identity but does not carry USER identity across service-to-service calls
  • OIDC tokens carry user identity: which user initiated the request, what scopes they were granted
  • Combined model: mTLS handles service-to-service authentication, OIDC tokens propagate user context
  • For external requests: API gateway validates OIDC token (who is the user?) and attaches identity headers
  • For internal requests: sidecar proxy validates mTLS certificates (which service is calling?)
  • Services can trust the combined identity: "service X called us on behalf of user Y" with cryptographic proof for both
  • Zero-trust means: verify both service identity (mTLS) AND user identity (OIDC) on every hop, regardless of network position

Further Reading

For understanding how centralized validation impacts system latency and reliability, see Distributed Caching for strategies to cache validation results effectively.

For a broader view of how microservices handle inter-service communication, the Microservices Roadmap covers architecture patterns, communication styles, and operational concerns.

When designing APIs that handle authentication and authorization, follow the guidelines in RESTful API Design to ensure consistent, secure interfaces.

Conclusion

OAuth 2.0 and OpenID Connect form the foundation of modern delegated authorization and federated identity in microservices architectures. OAuth 2.0 solves the problem of granting limited access to resources without sharing credentials, while OIDC extends it to add identity layer capabilities on top of OAuth 2.0.

Key takeaways: always use short-lived access tokens backed by refresh token rotation, validate tokens at the resource server with cryptographic signature verification rather than relying solely on opaque token introspection, and prefer explicit algorithm specification in JWT validation to prevent algorithm confusion attacks. When designing authorization scopes, follow the principle of least privilege and avoid overly broad scopes that grant more access than necessary.

For microservices communicating across service mesh or API gateway boundaries, mTLS provides cryptographic service-level authentication, while OAuth 2.0/OIDC handles user and application identity. These two layers work together to enable a zero-trust security model where every request is authenticated and authorized regardless of network position.

Category

Related Posts

mTLS: Mutual TLS for Service-to-Service Authentication

Learn how mutual TLS secures communication between microservices, how to implement it, and how service meshes simplify mTLS management.

#microservices #mtls #security

Secrets Management: Vault, Kubernetes Secrets, and Env Vars

Learn how to securely manage secrets, API keys, and credentials across microservices using HashiCorp Vault, Kubernetes Secrets, and best practices.

#microservices #secrets-management #security

Service Identity: SPIFFE and Workload Identity in Microservices

Understand how SPIFFE provides cryptographic identity for microservices workloads and how to implement workload identity at scale.

#microservices #service-identity #spiffe