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

2026-03-23
This post cover image
#aws
#cloud
#serverless
#iam
#mongodb

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.

Imagen mostrando la visión general de la arquitectura

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.

Imagen mostrando la visión general de la arquitectura con MongoDB

¡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.

Imagen mostrando la consola IAM con Configuración de cuenta resaltada

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

Imagen mostrando la sección de Federación de Identidad Saliente con el botón 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.

Imagen mostrando la federación habilitada con la URL del Emisor de Tokens

También puedes habilitarla a través de CLI si lo prefieres:

aws iam enable-outbound-web-identity-federation

Ahora 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 RS256

Obtendremos 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.

Imagen mostrando el menú de Federación de MongoDB

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

Imagen mostrando el inicio de Federación de MongoDB

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

Imagen mostrando el inicio de Identidad de Trabajo de MongoDB

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

Imagen mostrando la configuración del proveedor de identidad de MongoDB

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

Imagen mostrando la configuración del proveedor de identidad de MongoDB completada

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.

Imagen mostrando la conexión de organización del proveedor de identidad de MongoDB

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

Imagen mostrando el diálogo de conexión de organización del proveedor de identidad de MongoDB

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

Imagen mostrando la conexión de organización del proveedor de identidad de MongoDB completada

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.Arn

Agregamos 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_client

Luego, 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.

Imagen mostrando la creación de usuario de MongoDB

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.

Imagen mostrando la creación de usuario de MongoDB completada

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ú

Imagen mostrando el acceso de red de MongoDB en el menú

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

Imagen mostrando la autorización de red IP de MongoDB

¡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í.

Imagen mostrando los datos de prueba de MongoDB

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!