Autenticación en MongoDB Atlas con Federación de Identidad Saliente de AWS

Este archivo ha sido traducido automáticamente por IA, pueden ocurrir errores
He estado escribiendo varias publicaciones sobre temas de autenticación y autorización, usando el patrón PEP y PDP. PEP y PDP para Autorización Segura con Cognito, luego extendí eso con Amazon Verified Permissions, y más tarde agregué ABAC encima. Todo eso era sobre autorización de usuario a servicio. Es decir, un humano inicia sesión, obtiene un token y el backend decide lo que puede hacer.
Pero, ¿qué pasa con servicio a servicio? ¿Máquina a máquina? Tu Lambda necesita hablar con una base de datos o llamar a una API externa, y no hay un humano en el bucle. Todavía necesitarías credenciales.
Todo el código fuente está disponible en el Manual Serverless.
La forma habitual de manejar tokens M2M
Una de las aproximaciones más comunes que veo es usar Cognito User Pools, Auth0 o similar con el flujo de credenciales de cliente de OAuth 2.0. Creas un servidor de recursos, registras un cliente de aplicación con un ID de cliente y un secreto, y tu Lambda los intercambia por un token de acceso. Funciona bien, y si ya estás ejecutando Cognito para la autenticación de usuarios, se siente natural extenderlo para M2M también. Lo que hay que recordar es que los tokens M2M de estos servicios pueden volverse caros rápidamente.
Otro enfoque clásico es simplemente almacenar las credenciales en Secrets Manager. Contraseñas de base de datos, claves de API, lo que necesites. Habilita la rotación y sigue adelante. Funciona, pero es una cosa más que administrar, una cosa más que puede fallar y un secreto más que puede filtrarse.
Federación de Identidad Saliente
Justo antes de re:Invent 2025 hubo un lanzamiento muy interesante, la Federación de Identidad Saliente de AWS IAM.
Hemos estado usando la federación de identidades en AWS durante mucho tiempo. Google, Okta, Entra u otros emiten un token, AWS lo confía y obtienes acceso a los recursos de AWS. Eso es la federación entrante. AWS es quien confía.
La Federación de Identidad Saliente es lo inverso. Ahora AWS es quien emite los tokens. Tu Lambda (o EC2, o tarea de ECS) llama a STS para intercambiar un rol IAM por un token JWT firmado que básicamente dice "oye, soy este rol IAM, y aquí está una prueba criptográfica". Luego le das ese token a cualquier servicio externo o interno con el que estés hablando, y ellos pueden verificarlo usando flujos estándar.
Eso significa, sin secretos de cliente ni contraseñas almacenadas. El token emitido es de corta duración, está vinculado al rol IAM y está firmado por AWS. El servicio receptor solo necesita confiar en el emisor.
Qué hay en el Token?
Cuando llamamos a sts:GetWebIdentityToken, obtendremos un token JWT estándar, con algunas afirmaciones que importan:
iss - El emisor. Este es el URL del Emisor de Tokens que obtendremos cuando habilitemos la función, se ve así "https://a1810f8a-5a75-4e21-b1cd-a6b09f1836cb.tokens.sts.global.api.aws". Es único para nuestra cuenta de AWS y no podemos modificarlo.sub - El sujeto. Este es el ARN del Rol IAM. Es la identidad.aud - El público. Esto lo establecemos nosotros, y puede ser cualquier cosa, "my-api, "atlas-demo", lo que sea. Ambos lados solo necesitan acordar el valor.exp - Expiración. La duración corta es todo el punto.
Visión general de la arquitectura
En esta publicación construiremos dos soluciones diferentes que usan la Federación de Identidad Saliente, solo para mostrar cómo configurarla para un servicio interno, alojado en Lambda detrás de API Gateway, y una base de datos externa, en este caso MongoDB Atlas.
En nuestro caso de uso interno tenemos una función Lambda de trabajador que llamará a STS para obtener un token JWT saliente firmado. La función llama a una API en API Gateway donde tenemos un Autorizador Lambda que usa las claves públicas de STS para validar el JWT.

En el caso de MongoDB, básicamente tenemos lo mismo. Establecemos la confianza entre MongoDB y STS haciendo alguna configuración en el lado de MongoDB. Nuestro Lambda de trabajador llamará a STS para obtener el JWT y lo usará como autenticación hacia MongoDB, que usa las claves públicas, etc. de STS para validar al usuario. Para el caso de uso de MongoDB necesitamos mapear todo esto a un usuario, pero hablaremos más sobre esa configuración más adelante.

¡Ahora, construyamos!
Habilitar la Federación de Identidad Saliente
Antes de poder usar la Federación de Identidad Saliente necesitamos habilitarla, por defecto está deshabilitada. Así que ve al Consola de AWS y IAM > Configuración de la cuenta.

Desplázate hasta la sección de Federación de Identidad Saliente y simplemente haz clic en Habilitar.

Una vez habilitada, obtendrás tu URL específica de la cuenta del Emisor de Tokens de inmediato. Cópiala, la necesitarás más tarde.

También puedes habilitarla a través de CLI si lo prefieres:
aws iam enable-outbound-web-identity-federationAhora vamos a probar esto y llamar a STS para obtener un token, ejecutamos el comando CLI a continuación para obtener un token para nuestro principio conectado.
aws sts get-web-identity-token --audience "my-api" --signing-algorithm RS256Obtendremos una respuesta como:
{
"WebIdentityToken": "Token codificado en Base64",
"Expiration": "2026-03-19T08:20:06.177000+00:00"
}Si decodificamos el WebIdentityToken, usando una página web como jwt.io podemos ver las diferentes afirmaciones.
{
"aud": "my-api",
"sub": "arn:aws:iam::<account_id><iam_role>",
"https://sts.amazonaws.com/": {
"org_id": "<AWS_Org_Id>",
"aws_account": "<account_id>",
"ou_path": [
"...."
],
"original_session_exp": "2026-03-19T10:15:04Z",
"source_region": "eu-west-1",
"principal_id": "<iam_principal>",
"identity_store_user_id": "...."
},
"iss": "https://....tokens.sts.global.api.aws",
"exp": 1773908406,
"iat": 1773908106,
"jti": "..."
}Verificar el Token con API Gateway
Con eso funcionando, construyamos una API simple que una función Lambda llama, donde tenemos un Autorizador Lambda que valida el token.
Comenzamos creando la función del llamador, esto es bastante sencillo, llamamos a STS para obtener un token y luego lo presentamos al llamar a la API.
import json
import logging
import os
import boto3
import requests
logger = logging.getLogger()
logger.setLevel(logging.INFO)
sts_client = boto3.client("sts")
API_ENDPOINT = os.environ["API_ENDPOINT"]
AUDIENCE = os.environ["AUDIENCE"]
def get_token():
response = sts_client.get_web_identity_token(
Audience=[AUDIENCE],
DurationSeconds=300,
SigningAlgorithm="RS256",
)
return response["WebIdentityToken"]
def handler(event, context):
try:
token = get_token()
response = requests.get(
API_ENDPOINT,
headers={"Authorization": f"Bearer {token}"},
timeout=10,
)
return {
"statusCode": response.status_code,
"body": json.dumps(
{
"api_status_code": response.status_code,
"api_response": response.json() if response.ok else response.text,
"request_id": context.aws_request_id,
}
),
}
except Exception as e:
return {
"statusCode": 500,
"body": json.dumps(
{
"message": "Error calling protected API",
"error": str(e),
}
),
}Si miramos la función get_token(), es muy simple, solo tres parámetros y listo. No necesitamos buscar credenciales en ningún lugar. El Lambda simplemente dice "dame un token para esta audiencia" y STS firma uno con la propia identidad del Lambda.
El Autorizador Lambda
El Autorizador Lambda adjunto a API Gateway, aquí es donde comienza a ser realmente interesante. Lo que haremos es obtener las claves públicas de STS y luego validar el token usando una biblioteca estándar de JWT.
Creamos el descubrimiento usando la URL del emisor y append /.well-known/openid-configuration Eso nos dice dónde podemos encontrar las claves públicas y cuál debería ser el valor del emisor. Luego creamos un PyJWKClient que maneja la obtención y el almacenamiento en caché de las claves.
import json
import logging
import os
import urllib.request
import jwt
logger = logging.getLogger()
logger.setLevel(logging.INFO)
AUDIENCE = os.environ["AUDIENCE"]
ISSUER_URL = os.environ["ISSUER_URL"]
DISCOVERY_URL = f"{ISSUER_URL}/.well-known/openid-configuration"
discovery_doc = json.loads(urllib.request.urlopen(DISCOVERY_URL).read())
JWKS_URI = discovery_doc["jwks_uri"]
EXPECTED_ISSUER = discovery_doc["issuer"]
jwks_client = jwt.PyJWKClient(JWKS_URI)
def handler(event, context):
try:
token = extract_token(event)
if not token:
return generate_policy("Deny", event["methodArn"])
signing_key = jwks_client.get_signing_key_from_jwt(token)
decoded = jwt.decode(
token,
signing_key.key,
algorithms=["RS256"],
audience=OIDC_AUDIENCE,
issuer=EXPECTED_ISSUER,
)
return generate_policy(
"Allow",
event["methodArn"],
principal_id=decoded.get("sub", "unknown"),
context={"sub": decoded.get("sub", ""), "iss": decoded.get("iss", "")},
)
except jwt.ExpiredSignatureError:
logger.warning("Token has expired")
except jwt.InvalidAudienceError:
logger.warning("Invalid audience claim")
except jwt.InvalidIssuerError:
logger.warning("Invalid issuer claim")
except Exception as e:
logger.error("Token verification failed: %s", e)
return generate_policy("Deny", event["methodArn"])La función jwt.decode() es la núcleo de todo y validará los tokens reales contra los valores esperados, y lanzará una excepción en caso de un problema.
Si todo pasa, permitimos la solicitud y enviamos la afirmación sub para que el backend sepa quién está llamando.
Vale la pena notar que no hay nada específico de AWS en esta lógica de verificación. Esta es solo una validación estándar de tokens JWT.
Autenticarse en MongoDB Atlas
Ahora pongamos esto en un caso de uso del mundo real, conectándonos a un clúster de MongoDB Atlas usando tokens de la Federación de Identidad Saliente de IAM, eliminando la necesidad de almacenar contraseñas en Secrets Manager.
Hay una cosa muy importante a notar. Para que esto funcione necesitarás ejecutar un clúster dedicado M10+ de MongoDB Atlas con MongoDB 7.0.11 o superior. La Federación de Identidad de Trabajo no está soportada en clústeres gratuitos o compartidos (M0, M2, M5).
La idea es bastante simple y directa. Nuestra función Lambda llama a STS para obtener un token, luego presentamos este token a Atlas en lugar de un nombre de usuario y una contraseña. Atlas validará el token, usando flujos estándar, y mapeará el Rol IAM a un usuario de base de datos.
Configurar Atlas
El primer paso ahora es configurar las cosas en el lado de MongoDB, inicia sesión en tu cuenta y navega a Federación bajo Identidad y Acceso.

Bajo Federación selecciona Proveedores de Identidad, y comenzaremos a configurarlo.

En la siguiente pantalla selecciona Identidad de Trabajo para que podamos comenzar a agregar detalles.

En la pantalla de configuración llena el nombre y la descripción. La URL del emisor es la que del Consola de AWS IAM, que vimos antes en esta publicación. Para audiencia establece un valor de tu elección, importante que sea el mismo en la configuración de MongoDB y AWS (cuando llamamos a STS). La audiencia debe coincidir. Para la afirmación de usuario mantén el valor predeterminado sub

Guarda y termina tu configuración y deberías ver la pantalla de resumen.

A continuación, necesitamos conectar el proveedor de identidad que acabamos de crear con nuestra organización. Selecciona Organizaciones en el menú y comienza a conectar.

En el diálogo emergente selecciona el proveedor que acabamos de crear y haz clic en Conectar.

En la siguiente vista deberíamos ver el resumen de la conexión.

Desplegar la aplicación de prueba
Eso fueron muchos pasos, pero ahora podemos desplegar nuestra aplicación de prueba y luego hacer la configuración final en Atlas antes de probar. Mantendremos la infraestructura de prueba mínima. Solo una función Lambda que tiene permiso sts:GetWebIdentityToken:
Resources:
DatabaseOpFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub ${Application}-database-op
CodeUri: src/DatabaseOp/
Handler: database_op.handler
Environment:
Variables:
MONGODB_URI: !Ref MongoDbUri
OIDC_AUDIENCE: !Ref OidcAudience
Policies:
- Statement:
- Effect: Allow
Action:
- sts:GetWebIdentityToken
Resource: "*"
Outputs:
DatabaseOpFunctionRoleArn:
Description: IAM Role ARN for the Lambda function (use this in Atlas OIDC role mapping)
Value: !GetAtt DatabaseOpFunctionRole.ArnAgregamos el ARN del rol a la salida de la pila, ya que lo necesitaremos para la parte final de Atlas. El código está dividido en el manejador Lambda y un cliente que manejará la conexión a la base de datos MongoDB.
El controlador pymongo tiene un gran mecanismo de devolución de llamada para OIDC. Le damos una clase, y el controlador la llama cada vez que necesita un token. El controlador golpea fetch() en la primera conexión y nuevamente cuando el token está a punto de expirar. Establecemos el valor en 280 segundos, que es un poco menos de los reales 300, así que hay un búfer.
Establecer authMechanism="MONGODB-OIDC" le dice al controlador que omita el nombre de usuario/contraseña y use OIDC en su lugar.
import os
import boto3
from pymongo import MongoClient
from pymongo.auth_oidc import OIDCCallback, OIDCCallbackResult
sts_client = boto3.client("sts")
cached_client = None
def get_oidc_token():
audience = os.environ["OIDC_AUDIENCE"]
response = sts_client.get_web_identity_token(
Audience=[audience],
DurationSeconds=300,
SigningAlgorithm="RS256",
)
return response["WebIdentityToken"]
class AwsOidcCallback(OIDCCallback):
def fetch(self, context):
token = get_oidc_token()
return OIDCCallbackResult(access_token=token, expires_in_seconds=280)
def get_mongo_client():
global cached_client
if cached_client is not None:
return cached_client
uri = os.environ["MONGODB_URI"]
properties = {"OIDC_CALLBACK": AwsOidcCallback()}
cached_client = MongoClient(
uri,
authMechanism="MONGODB-OIDC",
authMechanismProperties=properties,
)
return cached_clientLuego, en nuestro manejador Lambda creamos el cliente de MongoDB e interactuamos con la base de datos, en este ejemplo simple solo insertamos un documento en una colección items.
def handler(event, context):
try:
client = get_mongo_client()
db = client["sample_db"]
collection = db["items"]
document = {
"message": "Hello from Lambda with OIDC auth",
"timestamp": datetime.now(timezone.utc).isoformat(),
"request_id": context.aws_request_id,
}
result = collection.insert_one(document)
doc = collection.find_one({"_id": result.inserted_id})
return {
"statusCode": 200,
"body": json.dumps({
"message": "Successfully connected to MongoDB Atlas with OIDC",
"insertedId": str(result.inserted_id),
}),
}
except Exception as e:
logger.error("Error: %s", e)
return {"statusCode": 500, "body": json.dumps({"error": str(e)})}Crear un usuario de base de datos en Atlas
Ahora necesitamos hacer el último paso en Atlas, crear un usuario de base de datos y mapearlo al ARN del Rol IAM, para que podamos obtener permisos para interactuar con la base de datos. Ve a Acceso a la base de datos y agrega un nuevo usuario de base de datos.

Selecciona Autenticación federada como el método de autenticación y establece el nombre de usuario en el ARN del Rol de la función Lambda, obtén esto de la salida de la pila. Asigna al usuario los permisos Leer y escribir en cualquier base de datos.

Ese ARN de rol coincidirá con la afirmación sub en el JWT. Así es como Atlas sabe qué usuario de base de datos usar.
Lista blanca de acceso de red
Antes de probar todo, hay una cosa más que debemos hacer, debemos permitir el acceso de red para nuestra función Lambda. Como no ejecutamos en una VPC, debemos permitir 0.0.0.0/0 en una configuración de producción, no hagas esto, pero para esta prueba está bien.
Navega a Lista de acceso de IP en el menú

Luego agrega 0.0.0.0/0, establécelo como temporal para que se elimine automáticamente.

¡Eso es todo! Ahora podemos usar la Federación de Identidad Saliente en STS para autenticarnos en MongoDB Atlas y leer y escribir datos, ¡sin contraseñas, sin secretos que administrar!
Pruebas
Es hora de hacer una pequeña prueba, para eso simplemente invoca la función Lambda desplegada. Luego muévete a Explorador de datos en Atlas y verifica que los datos están allí.

Palabras finales
Me gusta mucho este patrón. Sin secretos almacenados. Tokens que expiran en 5 minutos. Sabes exactamente qué rol IAM hizo cada conexión. Y funciona con cualquier cosa que hable OIDC, no solo MongoDB. Cualquier servicio externo que pueda verificar tokens OIDC puede usar la misma función get_oidc_token(). El mismo código, destino diferente.
Echa un vistazo a mis otras publicaciones en jimmydqv.com y sígueme en LinkedIn y X para más contenido sobre serverless.
¡Ahora ve a construir!
