Patrones de Implementación BFF
Patrones de Implementación BFF
Section titled “Patrones de Implementación BFF”Este documento materializa el Token Handler Pattern (presentado en SSO y Token Handler Pattern) en implementaciones tangibles dentro de un ecosistema Quarkus.
Para las decisiones de plataforma que justifican este stack, véase Decisiones Tecnológicas.
1. Agente OAuth2 (SSO Handshake)
Section titled “1. Agente OAuth2 (SSO Handshake)”El “OAuth2 Agent” es responsable de gobernar el flujo de redirecciones seguras con el Identity Provider (IdP) de Salesforce siguiendo el estándar Authorization Code + PKCE (RFC 7636).
Controlador: JAX-RS WebResource
Section titled “Controlador: JAX-RS WebResource”Dos simples interfaces @Path gestionan el punto de entrada y salida:
@Path("/bff/auth")public class OAuth2AgentResource {
@GET @Path("/login") public Response initiateLogin() { // Genera el desafío PKCE (code_verifier + code_challenge) // Redirige al URL de Autorización del IdP (HTTP 302) return Response.status(302).location(idpUrl).build(); }
@GET @Path("/callback") public Response handleCallback(@QueryParam("code") String code) { // Valida el Authorization Code y extrae el Payload // Delega el Exchange al Session Store (cifrado + persistencia)
String sessionId = UUID.randomUUID().toString(); // Construcción Estricta de la Cookie NewCookie sessionCookie = new NewCookie.Builder("session_id") .value(sessionId) .path("/") .secure(true) // Requiere HTTPS .httpOnly(true) // Bloquea lectura desde JavaScript en el SPA .sameSite(NewCookie.SameSite.LAX) // Protección CSRF base .build();
return Response.status(302).cookie(sessionCookie).location(spaUrl).build(); }}Referencia:
NewCookieAPI · SameSite cookies (MDN).
Encriptación y Estado (Session Store)
Section titled “Encriptación y Estado (Session Store)”Los tokens crudos jamás deben llegar (o “sangrar”) hacia el frontend. Siguiendo el rigor del estándar:
- El
OAuth2Agentextrae elaccess_tokenyrefresh_tokendel IdP. - Una clave simétrica interna (gestionada vía GCP Secret Manager o HashiCorp Vault) encripta este payload empleando algoritmo AES-GCM (autenticado y seguro frente a manipulación).
- El paquete encriptado se vuelca en la Caché Distribuida (ej. Cloud Firestore o Cloud SQL / PostgreSQL) y se asocia al string aleatorio
sessionId. - El
sessionIdviaja transparentemente e inyectado al navegador como cabeceraSet-Cookie(HttpOnly; Secure).
La elección del Session Store (Firestore vs. RDBMS) se describe en Gestión de Estado (Sesión).
2. OAuth2 Proxy / API Proxy (Intercepción de Peticiones)
Section titled “2. OAuth2 Proxy / API Proxy (Intercepción de Peticiones)”El interceptor (Proxy) captura las peticiones nativas en JSON del Frontend, les inyecta el verdadero token re-hidratado y las lanza contra el Servidor de Recursos (Salesforce API). Véase la secuencia completa en Resolución de Peticiones y Token Exchange.
Constructo Fundamental: Filtros REST y Clientes Declarativos
Section titled “Constructo Fundamental: Filtros REST y Clientes Declarativos”Debido a que el cometido central del BFF es ser un túnel de paso directo (leer cookie, desencriptar subyacente, re-enviar), el uso de ContainerRequestFilter y @RegisterRestClient de JAX-RS / Quarkus resulta ser extremadamente más rápido y purista arquitectónicamente que embutir cada petición a través de un enrutador completo de Apache Camel.
// 1. Filtro Pre-Matching para interceptar tráfico SPA hacia APIs@Provider@PreMatchingpublic class SessionDecryptionFilter implements ContainerRequestFilter {
@Override public void filter(ContainerRequestContext ctx) { // 1. Extraer la cookie "session_id" Cookie sessionCookie = ctx.getCookies().get("session_id"); if (sessionCookie == null) throw new UnauthorizedException();
// 2. Recuperar el paquete cifrado de la Caché Distribuida y desencriptarlo String plainAccessToken = decryptToken(cacheStore.get(sessionCookie.getValue()));
// 3. Mutar la petición inyectando explícitamente el Bearer Token ctx.getHeaders().putSingle("Authorization", "Bearer " + plainAccessToken); }}
// 2. Cliente REST Declarativo encapsulando la API subyacente@RegisterRestClient(configKey = "backend-api")public interface BackendProxyClient {
@GET @Path("/{path: .*}") Response forwardRequest(@PathParam("path") String path, @HeaderParam("Authorization") String token);}Referencias:
@PreMatching· Quarkus REST Client Guide · Bearer Token (RFC 6750).