Patrón Federated Identity
Delega la autenticación a proveedores de identidad externos. Un patrón para integrar OAuth2, OIDC, SAML y SSO entre múltiples servicios y organizaciones.
Visión General
El patrón Federated Identity delega la autenticación a proveedores de identidad externos (IdPs) en lugar de gestionar credenciales localmente. Los usuarios inician sesión a través de un tercero de confianza (Google, GitHub, Azure AD, Okta), y la aplicación recibe un token que puede verificar. Esto elimina el almacenamiento de contraseñas, habilita single sign-on (SSO) y permite autenticación cross-organization.
Cuándo Usar
Usar el patrón Federated Identity cuando:
- No quieres almacenar ni gestionar contraseñas de usuarios
- Los usuarios ya tienen cuentas con Google, GitHub, Microsoft u Okta
- Múltiples aplicaciones necesitan single sign-on across una organización
- Necesitas autenticar usuarios de organizaciones partner sin crear cuentas locales
- Requisitos de compliance exigen gestión centralizada de identidad (SOC2, HIPAA)
Solución
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()
# Verificar 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
Explicación
El patrón Federated Identity separa la autenticación de la aplicación:
- Identity Provider (IdP): Google, GitHub, Azure AD, Okta.
- Relying Party (RP): Tu aplicación. Confía en el IdP, recibe tokens, nunca ve contraseñas.
- Protocolos: OAuth2 (autorización), OIDC (capa de autenticación sobre OAuth2), SAML (enterprise SSO).
- Token Flow: Usuario → IdP (login) → Authorization Code → App intercambia code por tokens → App verifica ID token → Usuario autenticado.
- Single Sign-On (SSO): Una vez autenticado con el IdP, el usuario puede acceder a múltiples RPs sin re-ingresar credenciales.
Variantes
| Variante | Protocolo | Caso de Uso |
|---|---|---|
| OAuth2 Authorization Code | OAuth2 | Web apps con intercambio de token server-side |
| OIDC | OIDC (OAuth2 + ID tokens) | Web y mobile apps modernas |
| SAML 2.0 | SAML | Enterprise SSO, sistemas legacy |
| Client Credentials | OAuth2 | Autenticación service-to-service |
| Device Code | OAuth2 | TV, IoT, CLI devices sin browser |
Pautas
- Usar OIDC para nuevas aplicaciones — provee ID tokens estandarizados con claims de usuario
- Almacenar solo referencias de tokens, no contraseñas — el IdP es dueño del almacenamiento de credenciales
- Validar tokens en cada petición — verificar firma, expiración, issuer, audience
- Usar PKCE para clientes públicos (SPAs, mobile) para prevenir interceptación de authorization code
- Implementar refresh de tokens — los access tokens expiran; usar refresh tokens para mantener sesiones
- Mapear roles del IdP a roles locales — no depender de nombres de roles específicos del IdP en lógica de negocio
- Manejar outage del IdP gracefulmente — cachear sesiones de usuario, proveer modo degradado si es posible
- Usar discovery endpoints — los IdPs publican configuración en
/.well-known/openid-configuration
Errores Comunes
- Almacenar contraseñas localmente junto con federated identity — derrota el propósito
- No validar firmas de tokens — permite tokens forjados
- Ignorar expiración de tokens — tokens stale otorgan acceso después de revocación
- Hardcodear endpoints del IdP — usar discovery documents para flexibilidad
- No manejar outage del IdP — los usuarios no pueden log in si el IdP está down y no hay fallback
- Mezclar scopes de OAuth2 — solicitar solo lo necesario (openid, email, profile)
- No implementar logout — los usuarios se quedan logueados across apps incluso después de logout explícito
- Confiar en claims no verificados — siempre verificar issuer y audience antes de usar claims
Lectura Adicional
- Documentación oficial: consulta la referencia actualizada del framework o herramienta utilizada.
- Guías relacionadas: explora las guías de identity y pattern para profundizar.
- Patrones complementarios: revisa los patrones de diseño aplicables a tu stack tecnológico.
- Postmortems públicos: estudia incidentes reales de equipos que enfrentaron problemas similares en producción.
Notas de Producción
- Despliega gradualmente usando canary o blue-green para detectar regresiones temprano.
- Configura alertas para errores, latencia p99 y tasa de fallos antes de habilitar en producción.
- Documenta el rollback en el runbook; prueba el procedimiento en staging al menos una vez por trimestre.
- Revisa logs estructurados con correlation IDs para trazar requests end-to-end en incidentes.
Puntos Clave
- Aplica patrón federated identity cuando necesites una solución práctica para tu caso de uso.
- Monitorea el rendimiento después de implementar; mide latencia, errores y uso de recursos antes y después.
- Revisa la sección de Troubleshooting ante errores comunes; la mayoría tienen causa raíz documentada con solución.
- Mantén dependencias actualizadas y ejecuta tests en CI para prevenir regresiones en producción.
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.
Errores Comunes en Producción
- Aplicar el patrón donde no se necesita abstracción, agregando complejidad accidental.
- Dejar que el patrón se filtre en módulos no relacionados y confundir los límites de responsabilidad.
- Sobre-ingeniería en la primera implementación en lugar de comenzar simple y medir el dolor.
- Saltar los tests de contrato, de modo que las refactorizaciones rompan consumidores en silencio.
- Ignorar modos de fallo que el patrón no cubre.
- Usar el patrón como opción por defecto en lugar de elegir la herramienta adecuada para la escala actual.
- Olvidar documentar cuándo dejar de usar el patrón y qué lo reemplaza.
- Carecer de observabilidad sobre rendimiento y propagación de errores del patrón.
Preguntas frecuentes
¿Es este patrón adecuado para proyectos pequeños?
Para proyectos pequeños con pocos componentes, este patrón puede añadir complejidad innecesaria. Empieza simple e introduce el patrón cuando sientas el problema que resuelve.
¿Cómo se compara este patrón con alternativas?
Cada patrón hace diferentes trade-offs. Revisa la tabla de variantes arriba y considera tus restricciones específicas: tamaño del equipo, requisitos de rendimiento y planes de escalado.
¿Puedo aplicar este patrón parcialmente?
Sí. Muchos equipos adoptan patrones incrementalmente. Empieza con la idea central y añade sofisticación según sea necesario. El patrón es una guía, no un blueprint estricto.
Recursos Relacionados
Patrón Ambassador: Offloadeá Cross-Cutting Concerns a un
Cómo offloadar cross-cutting concerns a un proxy ambassador. Cubre connection pooling, retry logic, circuit breaking, monitoring, y TLS termination para client services.
PatternPatron de Enrutamiento de Gateway
Enruta solicitudes a multiples servicios backend a traves de un unico punto de entrada que gestiona preocupaciones transversales.
PatternPatrón Back-Pressure
Previene que sistemas upstream abrumen a consumidores downstream propagando señales de control de flujo hacia atrás a través del pipeline, asegurando throughput estable bajo carga.
PatternPatrón Multi-Tenant Data Isolation
Aisla datos de tenants en infraestructura compartida usando row-level security, schema-per-tenant o database-per-tenant. Un patrón para aplicaciones SaaS.
PatternPatrón Voucher
Valida claims y delega acceso usando vouchers firmados sin exponer datos sensibles. Un patrón de seguridad para autorización basada en tokens entre servicios.
RecipeEscapar Entidades HTML
Cómo escapar entidades HTML para prevenir ataques XSS en Python, Java y JavaScript.