Un límite OAuth dual para las pasarelas MCP: protegiendo la identidad del usuario
Las pasarelas MCP no deberían agrupar todas las llamadas en una sola cuenta de servicio. La validación entrante, junto con un intercambio OAuth en nombre del usuario, mantiene la identidad de cada usuario intacta.
Fuentes: Solving the Identity Termination Problem in MCP Gateway Architectures
Una pasarela que se encuentra entre un agente controlado por MCP y los sistemas empresariales debe contar con dos límites OAuth, de modo que la persona que realizó la solicitud permanezca identificable en cada sistema al que accede el agente.
El fallo
Veamos un fallo típico. Un empleado solicita a un agente, a través de una pasarela MCP, que presente un ticket en nombre de un compañero de trabajo. La pasarela verifica al empleado en el proveedor de identidades corporativas, valida el token de portador y transmite la solicitud a la API ITSM, la cual crea el ticket. Un mes después, un auditor abre el almacén de tickets y observa que en todo lo que presentó el agente figura un mismo nombre: la identidad de la aplicación de la pasarela. Las personas que realmente realizaron las solicitudes no aparecen en los registros.
El mismo defecto se repite en todos los sistemas posteriores al gateway. El sistema de recursos humanos atribuye cada solicitud de tiempo libre a la cuenta de servicio del gateway, el registro de activos atribuye cada transferencia de dispositivo a ella, y el flujo de trabajo de adquisiciones la lista como aprobadora en cada solicitud. La identidad termina en el gateway, y nada más allá puede recuperarla.
Los gateways MCP se han convertido en el puente por defecto entre los agentes LLM y los sistemas empresariales de registro, pero la mayoría de las implementaciones en producción dejan una pregunta sin resolver: ¿de quién es la identidad que autoriza cada llamada posterior? Dos patrones antiétnicos dominan la situación.
Identidad terminada. Después de verificar quién llama, la pasarela se comunica con cada backend como un principal de servicio compartido. Todo lo que haga con el sistema de TI, el sistema de recursos humanos y el de adquisiciones se atribuye a su cuenta de servicio y no a la persona que realiza la solicitud. Las trazas de auditoría se vuelven inútiles y el principio de menor privilegio se desmorona. Un comando malintencionado puede ejercer todo el alcance de la pasarela contra cualquier backend al que pueda acceder.
Confíe en el cliente. La pasarela acepta cualquier token de portador que llegue y lo reenvía sin modificaciones a las APIs posteriores, sin verificar el emisor, el público objetivo ni el alcance. El robo de tokens en cualquier punto del camino del agente autoriza entonces silenciosamente llamadas arbitrarias a los sistemas posteriores.
Ninguno de ellos es aceptable para la IA agente empresarial, especialmente bajo la función de gobernanza del Marco de Gestión de Riesgos de IA de NIST, que exige que se mantenga la responsabilidad individual en todas las acciones automatizadas en sistemas regulados.
La propuesta
El diseño cuenta con dos límites OAuth. En el extremo del cliente, el límite de entrada extrae y verifica un token de acceso delegado, es decir, un token portador OAuth 2.0 (RFC 6749) emitido al usuario que realiza la solicitud para el público al que está dirigido el gateway. El límite de salida realiza un intercambio en nombre del servicio (siguiendo el espíritu del intercambio de tokens de RFC 8693) o, cuando la cadena de confianza lo permite, una reenvío delegado directo, y genera un token para el público interno que sigue llevando la identidad del usuario original.
La autorización de la herramienta se decide en el límite, a partir de las afirmaciones de tokens analizados, y no está integrada dentro de las implementaciones de la herramienta. Eso hace que la gestión de la identidad sea responsabilidad del gateway, en lugar de algo que cada herramienta o backend deba recordar.
El patrón: OAuth de límite delegado
En resumen: el gateway maneja dos límites OAuth en lugar de uno, y la identidad del usuario es el contrato que se transmite entre ellos.
El límite de entrada. La extracción del token portador, la validación del emisor y del público, así como el análisis de las afirmaciones, tienen lugar en una sola capa de la pasarela, generalmente en el middleware de solicitudes o en un inyector de dependencias al estilo FastMCP, que se ejecuta antes de cada función de la herramienta. Aquí se responde a la pregunta “¿quién está llamando y tiene permiso para usar esta herramienta?”, y ninguna implementación de herramienta puede omitir este paso. La asignación de alcances a herramientas es declarativa: cada herramienta expuesta indica los alcances OAuth que necesita, y el límite se niega a ejecutarla cuando el token no los contiene. Esta negativa constituye un fallo de autorización estructurado, y no un error 5xx generado por una verificación de permisos realizada en profundidad en la pila que consumió recursos computacionales en el proceso.
El límite de salida. La llamada descendente se autentica mediante un token que indica el nombre del usuario original, nunca mediante la identidad de servicio del propio gateway. Existen dos mecanismos para lograrlo. Un intercambio en nombre de otro (Microsoft Entra OBO, un intercambio de pool de identidades de AWS Cognito o un intercambio STS personalizado) emite un token nuevo dirigido a la API descendente, manteniendo al llamante humano como su sujeto. El reenvío directo, reservado para los servicios descendentes cubiertos por un acuerdo de confianza explícito con el gateway, transmite el token entrante sin cambios. Es el intercambio el que hace que la propagación sea verificable y no simplemente algo esperado.
Entre medias. Los tokens se consideran un estado que dura solo una solicitud. Nada se almacena en caché entre solicitudes, nada se mantiene en una sesión, y no se comparte ningún estado mutable. Cada llamada a una herramienta MCP lee nuevamente el token de los encabezados en tiempo real y lo olvida una vez que finaliza la solicitud.
Implementación
Entrada: extracción del token delegado
El límite de entrada es un middleware ligero que se ejecuta antes que cada función de herramienta. En un servidor MCP de Python construido sobre un framework de estilo FastMCP, se depende de una inyección de CurrentHeaders para obtener el token de portador mediante una pequeña utilidad:
from typing import Optional
def extract_bearer_token(headers: dict) -> str:
auth = headers.get("authorization") or headers.get("Authorization")
if not auth or not auth.startswith("Bearer "):
raise AuthError("missing or malformed bearer token")
return auth[len("Bearer "):]
A continuación se realiza la validación de ese token frente al emisor y al público que espera la pasarela, utilizando las claves de firma publicadas por el proveedor de identidad (JWKS):
from jose import jwt
def validate_inbound_token(token: str, jwks: dict, audience: str, issuer: str) -> dict:
claims = jwt.decode(
token, jwks,
audience=audience,
issuer=issuer,
options={"require_exp": True, "require_iat": True},
)
return claims # sub, scp/scope, exp, etc.
La validación debe realizarse en el propio proceso. Externalizar la validación de JWT a un proxy posterior puede funcionar en topologías de confianza limitada, pero elimina la capacidad de la pasarela para imponer la correspondencia entre el ámbito y la herramienta antes de que se ejecuten los efectos secundarios de dicha herramienta.
Salida: en nombre del intercambio
El límite de salida intercambia el token de usuario de entrada por uno dirigido al público posterior. Ese intercambio es lo que diferencia este patrón de un reenvío simple, ya que la API posterior nunca ve directamente el token de entrada. La pasarela llama al endpoint de tokens del proveedor de identidad con una solicitud urn:ietf:params:oauth:grant-type:jwt-bearer (Entra) o una solicitud assume-role-with-web-identity (AWS), pasando el token de entrada como la afirmación del sujeto. El proveedor devuelve un nuevo token para la API posterior cuyo claim sub sigue siendo el usuario original.
def exchange_obo(inbound_token: str, downstream_audience: str) -> str:
response = requests.post(
f"{IDP_TOKEN_ENDPOINT}",
data={
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"client_id": GATEWAY_CLIENT_ID,
"client_secret": GATEWAY_CLIENT_SECRET,
"assertion": inbound_token,
"scope": f"{downstream_audience}/.default",
"requested_token_use": "on_behalf_of",
},
timeout=5,
)
response.raise_for_status()
return response.json()["access_token"]
El token intercambiado se adjunta luego a la llamada HTTP saliente:
def call_downstream(downstream_token: str, path: str) -> dict:
return requests.get(
f"{DOWNSTREAM_BASE_URL}{path}",
headers={"Authorization": f"Bearer {downstream_token}"},
timeout=10,
).json()
Mapeo de alcance y propagación de identidad
Las funciones de la herramienta declaran sus alcances requeridos al registrarse, y el límite los aplica antes de que se ejecute el cuerpo de la herramienta:
@tool(required_scopes=["ticketing.write"])
def open_ticket(headers: CurrentHeaders, summary: str, body: str) -> dict:
token = extract_bearer_token(headers)
claims = validate_inbound_token(token, jwks, audience=GATEWAY_AUD, issuer=ISSUER)
enforce_scopes(claims, required=["ticketing.write"]) # central, not tool-local
downstream_token = exchange_obo(token, downstream_audience=TICKETING_AUD)
return call_downstream(downstream_token, path="/tickets")
Dado que el mapeo de alcances es declarativo, se puede inspeccionar. Cualquiera que audite la pasarela dispone de todas las herramientas y sus alcances requeridos únicamente a partir del registro, sin necesidad de leer el código de la herramienta.
El límite de entrada es el mismo en todos los proveedores de nube. Solo difiere el cableado de intercambio de salida, y las dos implementaciones siguientes comparten una estructura general idéntica.
En AWS: API Gateway, Lambda y Cognito
En AWS, el límite de entrada puede ser un autorizador Lambda personalizado en Amazon API Gateway. Este verifica el token contra un grupo de usuarios de Amazon Cognito y devuelve un documento de política junto con un mapa de contexto de las afirmaciones analizadas, de modo que el Lambda del servidor MCP extrae las afirmaciones de event.requestContext.authorizer en lugar de verificarlas nuevamente. En el lado de salida, sts:AssumeRoleWithWebIdentity toma el token de entrada (o uno emitido por un grupo de identidades de Cognito) y genera credenciales temporales de AWS vinculadas a la identidad federada del usuario. Cualquier punto final de API Gateway que esté detrás de él, o una API externa que confíe en los JWT de Cognito, podrá ver al usuario en la afirmación sub.
Un intercambio mínimo entre Cognito y STS en el Lambda del gateway se ve así:
import boto3
def aws_obo(inbound_token: str, role_arn: str) -> dict:
sts = boto3.client("sts")
creds = sts.assume_role_with_web_identity(
RoleArn=role_arn,
RoleSessionName="mcp-gateway-obo",
WebIdentityToken=inbound_token,
DurationSeconds=900,
)
return creds["Credentials"]
La política de confianza del rol asumido designa al grupo de usuarios de Cognito como entidad federada y también verifica la afirmación aud del token entrante, lo que significa que solo un token emitido para esta pasarela puede asumir el rol.
En Azure: el flujo Entra on-behalf-of
En Azure, el control de frontera de entrada verifica el token recibido en relación con el registro de la aplicación Entra de la pasarela, utilizando la URI de ID de aplicación de la pasarela como audiencia. Para el salto de salida, se ejecuta un intercambio Entra on-behalf-of dirigido al registro de aplicación propio de la API descendente:
import msal
def entra_obo(inbound_token: str, downstream_scope: str) -> str:
app = msal.ConfidentialClientApplication(
client_id=GATEWAY_APP_ID,
client_credential=GATEWAY_CLIENT_SECRET,
authority=f"https://login.microsoftonline.com/{TENANT_ID}",
)
result = app.acquire_token_on_behalf_of(
user_assertion=inbound_token,
scopes=[downstream_scope],
)
if "access_token" not in result:
raise AuthError(result.get("error_description", "obo exchange failed"))
return result["access_token"]
La API de downstream debe aceptar tokens cuyo público sea su propio registro de aplicación Entra. Su lógica de autorización lee el atributo oid (ID del objeto) o upn (nombre principal del usuario) para atribuir la llamada al usuario original. La estructura del patrón es idéntica, y solo cambia la conexión con el proveedor de identidad.
Vinculación con el registro y la evaluación
La identidad propagada resulta más útil cuando se registra. Si la pasarela registra al usuario que realiza la solicitud en cada llamada a una herramienta, una auditoría puede relacionar una acción de un agente con una persona específica. El mismo campo puede incluirse en los registros de evaluación, vinculando las ejecuciones de pruebas con las personas cuyo comportamiento está respondiendo el sistema. Considere la identidad extraída por este patrón como la primera columna de cada registro de auditoría y evaluación que genere el sistema.
Cuándo aplica este patrón y cuándo no
Aplica cuando:
- La pasarela se utiliza en sistemas donde la identidad individual es importante para la autorización, auditoría o cumplimiento, como la gestión de servicios de TI, recursos humanos, finanzas y flujos de trabajo regulados.
- El proveedor de identidades empresariales admite un estándar de intercambio: Entra OBO, Cognito con identidad web STS, o el intercambio de tokens de Auth0 y Okta.
- Las API posteriores se encuentran dentro del mismo límite de confianza que la pasarela y pueden configurarse para aceptar tokens de audiencia de usuario.
- El despliegue está sujeto a normativas como NIST AI RMF, SOX, HIPAA o similares que exigen responsabilidad a nivel de usuario por las acciones automatizadas.
No es adecuado cuando:
- Cada llamada posterior es claramente no específica del usuario, por ejemplo, datos de referencia de solo lectura sin efectos secundarios atribuibles a un usuario.
- Un proxy descendente de confianza ya aplica semánticas completamente delegadas para la pasarela, lo que hace que el trabajo de la propia pasarela sea redundante. En este caso, la pasarela solo crea el límite de entrada más una variante delegada de confianza para el lado de salida, redirigiendo la comunicación directamente bajo un contrato de confianza explícito y aceptando que el proxy es el verdadero límite de seguridad.
- Una API descendente rechaza los tokens de audiencia de usuario y no está prevista su modificación. Se debe posponer este enfoque hasta que eso cambie. Recurrir a un principal de servicio “por ahora” debe ser una decisión consciente con los riesgos bien documentados, nunca una opción predeterminada.
Consideraciones operativas:
- Latencia. Cada intercambio OBO implica un viaje adicional al endpoint de tokens por cada llamada descendente. Mantenga los tokens intercambiados mientras el token de entrada siga siendo válido, identificados mediante
(sub, downstream_audience), y nunca los comparta entre usuarios. - Limitación del proveedor de identidad. Las cuotas del endpoint de tokens no son algo trivial. Debe haber margen de maniobra y se deben monitorear las tasas de fallo en los intercambios.
- Actualización. Las sesiones de los agentes pueden durar más que la vida útil de un único token. Planifique la rotación de tokens de actualización, o acepte que una sesión del agente finalice cuando su token expire, lo cual suele ser la postura de seguridad adecuada de todos modos.
- Plan de respaldo con cuenta de servicio. Mantenga una ruta de intercambio mediante cuenta de servicio para tareas que realmente no involucran usuarios, como verificaciones de estado o sincronizaciones en segundo plano programadas. Hágala explícita, con un alcance restringido y con registro detallado.
Dónde falla el patrón
Supone que el proveedor de identidades admite un intercambio estándar en nombre del sistema, pero muchos de los más antiguos no lo hacen. En esos entornos, el lado emisor recurre a pasar los tokens directamente, bajo el amparo de un acuerdo de confianza por escrito con cada servicio downstream. Esto es aceptable para los servidores backend dentro del límite de confianza de la pasarela, pero resulta insuficiente cuando otro equipo o un proveedor externo gestiona el servicio downstream. La forma adecuada de migrar es actualizar el proveedor de identidades, en lugar de recurrir permanentemente al reenvío.
También supone que los servicios posteriores aceptan tokens de audiencia de usuario. Muchos sistemas empresariales son anteriores a la autorización delegada y solo se autentican contra principios de servicio. En ese caso, es necesario esperar a que el servicio se actualice o se introduzca un proxy consciente de la identidad delante de él. Ejecutarlo parcialmente, con algunas llamadas atribuidas al usuario y otras al servicio, es el estado normal durante la migración, pero debe considerarse como deuda técnica con una fecha de cierre y no normalizarse.
Finalmente, no resuelve el problema de la rendición de cuentas del agente como actor. Preservar la identidad humana a través de los saltos del gateway es el control principal adecuado, pero un agente basado en LLM aún puede ser inducido a realizar acciones que el usuario nunca solicitó literalmente. Este patrón garantiza la atribución, no la intención. Los controles compensatorios asumen esa responsabilidad: confirmación humana para acciones de alto impacto, alcances mínimos en el límite de entrada y registro de evidencias por llamada.
Lista de verificación para la adopción
- Documente el contrato de entrada (emisor, público objetivo, alcances soportados) como una interfaz con versiones y guárdelo como si fuera una API pública.
- Implemente la asignación de alcances a herramientas como un campo declarativo en el registro de las herramientas, y no como verificaciones ad hoc dispersas en su código.
- Establezca un intercambio en nombre de la empresa con el proveedor de identidades empresariales para cada backend posterior. Haga que el intercambio en nombre de la empresa sea el valor predeterminado y mantenga el reenvío directo para los backends dentro del límite de confianza explícito de la pasarela.
- Almacene en caché los tokens intercambiados durante la vida útil del token entrante, utilizando como clave al usuario más el público al que va dirigido, y nunca comparta estos tokens entre usuarios.
- En cada llamada a la herramienta, registre tanto el
subentrante como elsubsaliente, para que se pueda auditar la propagación de forma completa desde el inicio hasta el final. - Permita un camino de cuenta de servicio con un alcance estrictamente definido para operaciones sin usuario, y exija aprobación cada vez que se utilice.
- Registre las APIs posteriores que rechacen los tokens dirigidos a usuarios como un problema arquitectónico y trabaje para eliminar esa limitación.
A medida que las empresas estadounidenses y los programas federales adoptan el Marco de Gestión de Riesgos de IA de NIST, su función de gobernanza exige que exista una responsabilidad individual que sobreviva a las acciones automatizadas en sistemas regulados. Finalizar la identidad en la puerta de enlace va en contra de esto y conlleva hallazgos de auditoría en entornos SOX, HIPAA y FedRAMP, donde cada cambio automático debe poder rastrearse hasta una persona específica.
Leer más
Las bases son el RFC 6749 (el Marco de Autorización OAuth 2.0) y el RFC 8693 (Intercambio de Tokens OAuth 2.0) del IETF. La documentación de los proveedores aborda el flujo OAuth 2.0 On-Behalf-Of de la plataforma de identidades de Microsoft, la referencia de la API AWS STS AssumeRoleWithWebIdentity y los grupos de identidades de Amazon Cognito. NIST publica el Marco de Gestión de Riesgos de IA (AI RMF 1.0), y la especificación MCP incluye una sección sobre autorización que merece ser leída junto con todo lo anterior.
Leer más
- Los servidores MCP se están convirtiendo en plataformas de capacidades, no en envoltorios de herramientas — Transformación de herramientas, búsqueda, nombres de espacio, visibilidad, proxies, habilidades, tareas, control de versiones y pruebas: patrones que convierten a un servidor MCP en una plataforma de capacidades regulada.
- MCP y OAuth2: ¿Quién autorizó la acción del agente? — Por qué OAuth2 solo cubre parte de la autorización de agentes bajo MCP, y cómo se integran OIDC, alcances, reglas de representante confundido, GNAP y capacidades.