Federated Identity Pattern
Delegate authentication to external identity providers. A pattern for integrating OAuth2, OIDC, SAML, and SSO across multiple services and organizations.
Overview
The Federated Identity Pattern delegates authentication to external identity providers (IdPs) instead of managing credentials locally. Users log in through a trusted third party (Google, GitHub, Azure AD, Okta), and the application receives a token it can verify. This eliminates password storage, enables single sign-on (SSO), and allows cross-organization authentication.
When to Use
Use the Federated Identity Pattern when:
- You do not want to store or manage user passwords
- Users already have accounts with Google, GitHub, Microsoft, or Okta
- Multiple applications need single sign-on across an organization
- You need to authenticate users from partner organizations without creating local accounts
- Compliance requirements mandate centralized identity management (SOC2, HIPAA)
Solution
Python (FastAPI + OAuth2/OIDC)
from fastapi import FastAPI, HTTPException, Depends
from fastapi.responses import RedirectResponse
import httpx
import jwt
import time
app = FastAPI()
GOOGLE_CLIENT_ID = "your-client-id"
GOOGLE_CLIENT_SECRET = "your-client-secret"
GOOGLE_REDIRECT_URI = "http://localhost:8000/auth/callback"
GOOGLE_DISCOVERY = "https://accounts.google.com/.well-known/openid-configuration"
async def get_google_discovery():
async with httpx.AsyncClient() as client:
resp = await client.get(GOOGLE_DISCOVERY)
return resp.json()
@app.get("/auth/login")
async def login():
discovery = await get_google_discovery()
auth_url = (
f"{discovery['authorization_endpoint']}"
f"?client_id={GOOGLE_CLIENT_ID}"
f"&redirect_uri={GOOGLE_REDIRECT_URI}"
f"&response_type=code"
f"&scope=openid email profile"
)
return RedirectResponse(auth_url)
@app.get("/auth/callback")
async def callback(code: str):
discovery = await get_google_discovery()
async with httpx.AsyncClient() as client:
token_resp = await client.post(
discovery["token_endpoint"],
data={
"code": code,
"client_id": GOOGLE_CLIENT_ID,
"client_secret": GOOGLE_CLIENT_SECRET,
"redirect_uri": GOOGLE_REDIRECT_URI,
"grant_type": "authorization_code",
},
)
tokens = token_resp.json()
# Verify ID token
id_token = tokens.get("id_token")
decoded = jwt.decode(id_token, options={"verify_signature": False})
return {
"user": {
"email": decoded["email"],
"name": decoded["name"],
"sub": decoded["sub"],
},
"access_token": tokens["access_token"],
}
@app.get("/protected")
async def protected(authorization: str = Depends(extract_user)):
return {"message": f"Hello {authorization['email']}"}
async def extract_user(authorization: str = None):
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Missing token")
token = authorization.split(" ")[1]
try:
decoded = jwt.decode(token, options={"verify_signature": False})
if decoded["exp"] < time.time():
raise HTTPException(status_code=401, detail="Token expired")
return decoded
except jwt.PyJWTError:
raise HTTPException(status_code=401, detail="Invalid token")
JavaScript (Express + OIDC)
const express = require("express");
const { auth } = require("express-openid-connect");
const app = express();
app.use(
auth({
issuerBaseURL: "https://accounts.google.com",
baseURL: "http://localhost:3000",
clientID: "your-client-id",
secret: "your-secret-key",
authRequired: false,
auth0Logout: true,
routes: {
login: "/auth/login",
callback: "/auth/callback",
logout: "/auth/logout",
},
})
);
app.get("/", (req, res) => {
if (req.oidc.isAuthenticated()) {
res.json({
user: req.oidc.user,
token: req.oidc.accessToken,
});
} else {
res.json({ message: "Not authenticated. Visit /auth/login" });
}
});
app.get("/profile", (req, res) => {
if (!req.oidc.isAuthenticated()) {
return res.status(401).json({ error: "Unauthorized" });
}
res.json({ user: req.oidc.user });
});
app.listen(3000);
Java (Spring Security + OAuth2)
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.client.oidc.web.logout.OidcClientInitiatedLogoutSuccessHandler;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
public class FederatedIdentityApp {
@GetMapping("/user")
public String user(@AuthenticationPrincipal OidcUser principal) {
if (principal == null) return "Not authenticated";
return "Hello, " + principal.getFullName() + " (" + principal.getEmail() + ")";
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http,
ClientRegistrationRepository repo) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/user").authenticated()
.anyRequest().permitAll()
)
.oauth2Login(oauth -> {})
.logout(logout -> logout
.logoutSuccessHandler(
new OidcClientInitiatedLogoutSuccessHandler(repo)
)
);
return http.build();
}
public static void main(String[] args) {
SpringApplication.run(FederatedIdentityApp.class, args);
}
}
// application.yml:
// spring.security.oauth2.client.registration.google.client-id=xxx
// spring.security.oauth2.client.registration.google.client-secret=xxx
Explanation
The Federated Identity Pattern separates authentication from the application:
- Identity Provider (IdP): Google, GitHub, Azure AD, Okta. Stores credentials, handles login flows.
- Relying Party (RP): Your application. Trusts the IdP, receives tokens, never sees passwords.
- Protocols: OAuth2 (authorization), OIDC (authentication layer on OAuth2), SAML (enterprise SSO).
- Token Flow: User → IdP (login) → Authorization Code → App exchanges code for tokens → App verifies ID token → User is authenticated.
- Single Sign-On (SSO): Once authenticated with the IdP, the user can access multiple RPs without re-entering credentials.
Variants
| Variant | Protocol | Use Case |
|---|---|---|
| OAuth2 Authorization Code | OAuth2 | Web apps with server-side token exchange |
| OIDC | OIDC (OAuth2 + ID tokens) | Modern web and mobile apps |
| SAML 2.0 | SAML | Enterprise SSO, legacy systems |
| Client Credentials | OAuth2 | Service-to-service authentication |
| Device Code | OAuth2 | TV, IoT, CLI devices without browser |
What Works
- Use OIDC for new applications — it provides standardized ID tokens with user claims
- Store only token references, not passwords — the IdP owns credential storage
- Validate tokens on every request — check signature, expiry, issuer, audience
- Use PKCE for public clients (SPAs, mobile) to prevent authorization code interception
- Implement token refresh — access tokens expire; use refresh tokens to maintain sessions
- Map IdP roles to local roles — do not rely on IdP-specific role names in business logic
- Handle IdP outage gracefully — cache user sessions, provide degraded mode if possible
- Use discovery endpoints — IdPs publish configuration at
/.well-known/openid-configuration
Common Mistakes
- Storing passwords locally alongside federated identity — defeats the purpose
- Not validating token signatures — allows forged tokens
- Ignoring token expiry — stale tokens grant access after revocation
- Hardcoding IdP endpoints — use discovery documents for flexibility
- Not handling IdP outage — users cannot log in if the IdP is down and no fallback exists
- Mixing OAuth2 scopes — request only what you need (openid, email, profile)
- Not implementing logout — users stay logged in across apps even after explicit logout
- Trusting unverified claims — always verify the issuer and audience before using claims
Troubleshooting
- Login works for some users but not others: check identity provider configuration, user claims, and role mappings. Look for case sensitivity in identifiers.
- Token expires too quickly: verify token lifetime, refresh logic, and clock skew. Short tokens with secure refresh are preferred.
- Session is not shared across subdomains: set the cookie domain and SameSite policy correctly.
- Brute force attempts increase: implement rate limiting, account lockout, and CAPTCHA.
- OIDC flow fails with invalid_state: ensure the state parameter is stored, transmitted, and validated in the same user session.
Further Reading
- Official documentation: check the current reference for the framework or tool used.
- Related guides: explore the identity and pattern guides for deeper coverage.
- Complementary patterns: review design patterns applicable to your technology stack.
- Public postmortems: study real incidents from teams that faced similar production issues.
Production Notes
- Deploy gradually using canary or blue-green to catch regressions early.
- Configure alerts for error rate, p99 latency, and failure rate before enabling in production.
- Document the rollback in the runbook; test the procedure in staging at least once per quarter.
- Review structured logs with correlation IDs to trace requests end-to-end during incidents.
Key Takeaways
- Apply federated identity pattern when you need a practical solution for your use case.
- Monitor performance after implementation; measure latency, errors, and resource usage before and after.
- Check the Troubleshooting section for common failures; most have documented root causes with fixes.
- Keep dependencies updated and run tests in CI to prevent production regressions.
Common Production Pitfalls
- Applying the pattern where no abstraction is needed, adding accidental complexity.
- Letting the pattern leak into unrelated modules and blur ownership boundaries.
- Over-engineering the first implementation instead of starting simple and measuring pain.
- Skipping contract tests, so refactors silently break consumers.
- Ignoring failure modes that the pattern does not cover.
- Using the pattern as a default instead of choosing the right tool for the current scale.
- Forgetting to document when to stop using the pattern and what replaces it.
- Missing observability around the pattern’s performance and error propagation.
Frequently Asked Questions
What is the difference between OAuth2 and OIDC?
OAuth2 is an authorization framework — it grants access to resources. OIDC is an authentication layer built on top of OAuth2 — it proves who the user is via ID tokens. Use OIDC when you need authentication, OAuth2 when you need delegated access.
Should I use SAML or OIDC?
Use OIDC for new applications — it is simpler, JSON-based, and works well with mobile and SPAs. Use SAML for enterprise integrations where the IdP only supports SAML (Azure AD, ADFS, legacy systems).
How do I handle multiple identity providers?
Implement a multi-IdP strategy. Let users choose their provider at login. Map all providers to a local user record using the sub claim as the unique identifier. Libraries like Passport.js (Node) or Spring Security (Java) support multiple providers natively.
Related Resources
Ambassador: Offload Cross-Cutting Concerns to a Proxy
How to offload cross-cutting concerns to a proxy ambassador. Covers connection pooling, retry logic, circuit breaking, monitoring, and TLS termination for client services.
PatternGateway Routing Pattern
Route requests to multiple backend services through a single entry point that handles cross-cutting concerns.
PatternBack-Pressure Pattern
Prevent upstream systems from overwhelming downstream consumers by propagating flow-control signals backward through the pipeline, ensuring stable throughput under load.
PatternMulti-Tenant Data Isolation Pattern
Isolate tenant data in shared infrastructure using row-level security, schema-per-tenant, or database-per-tenant strategies. A pattern for SaaS applications.
PatternVoucher Pattern
Validate claims and delegate access using signed vouchers without exposing sensitive data. A security pattern for token-based authorization between services.
RecipeEscape HTML Entities
How to escape HTML entities to prevent XSS attacks in Python, Java, and JavaScript.