Autenticação no MongoDB Atlas com Federação de Identidade de Saída do AWS IAM

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

Este arquivo foi traduzido automaticamente por IA, erros podem ocorrer

Estive escrevendo vários posts sobre tópicos de autenticação e autorização, usando o padrão PEP e PDP. PEP e PDP para Autorização Segura com Cognito, depois estendi isso com Amazon Verified Permissions, e mais tarde adicionei ABAC no topo. Tudo isso era sobre autorização de usuário para serviço. Ou seja, um humano faz login, obtém um token e o backend decide o que ele pode fazer.

Mas e quanto a serviço a serviço? Máquina para máquina? Seu Lambda precisa falar com um banco de dados ou chamar uma API externa, e não há um humano no loop. Você ainda precisaria de credenciais.

Todo o código-fonte está disponível no Serverless Handbook.

A Maneira Comum de Lidar com Tokens M2M

Uma das abordagens mais comuns que vejo é usar Cognito User Pools, Auth0 ou similares com o fluxo de credenciais do cliente OAuth 2.0. Você cria um servidor de recursos, registra um cliente de aplicativo com um ID e segredo do cliente, e seu Lambda troca isso por um token de acesso. Isso funciona bem e, se você já estiver usando Cognito para autenticação de usuário, parece natural estendê-lo para M2M também. O que deve ser lembrado é que os tokens M2M desses serviços podem rapidamente se tornar caros.

Outra abordagem clássica é simplesmente armazenar credenciais no Secrets Manager. Senhas do banco de dados, chaves de API, o que você precisar. Habilite a rotação e siga em frente. Funciona, mas é mais uma coisa para gerenciar, mais uma coisa que pode falhar e mais um segredo que pode vazar.

Federação de Identidade de Saída

Pouco antes do re:Invent 2025, houve um lançamento muito interessante, a Federação de Identidade de Saída do AWS IAM.

Estamos usando federação de identidade na AWS há muito tempo. Google, Okta, Entra ou outros emitem um token, a AWS confia nele e você obtém acesso aos recursos da AWS. Isso é federação entrante. A AWS é quem está confiando.

A Federação de Identidade de Saída é o inverso. Agora a AWS é quem emite os tokens. Seu Lambda (ou EC2, ou tarefa ECS) chama o STS para trocar um papel IAM por um token JWT assinado que basicamente diz "ei, eu sou este papel IAM, e aqui está uma prova criptográfica." Depois, você entrega esse token para qualquer serviço externo ou interno com o qual está falando, e eles podem verificá-lo usando fluxos padrão.

Isso significa: sem segredos de cliente ou senhas armazenadas. O token emitido é de curta duração, vinculado ao papel IAM e assinado pela AWS. O serviço receptor só precisa confiar no emissor.

O Que Há no Token?

Quando chamamos sts:GetWebIdentityToken, obteremos um token JWT padrão, com algumas declarações que importam:

iss - O emissor. Este é o URL do Emissor de Tokens que obteremos ao habilitar o recurso, ele se parece com isso "https://a1810f8a-5a75-4e21-b1cd-a6b09f1836cb.tokens.sts.global.api.aws". É único para nossa conta AWS e não podemos modificá-lo.
sub - O assunto. Este é o ARN do Papel IAM. É a identidade.
aud - O público. Isso é definido por nós e pode ser qualquer coisa, "my-api", "atlas-demo", o que quiser. Ambos os lados só precisam concordar com o valor.
exp - Expiração. A duração curta é o ponto principal.

Visão geral da arquitetura

Neste post, construiremos duas soluções diferentes que usam a Federação de Identidade de Saída, apenas para mostrar como configurá-la para um serviço interno, hospedado no Lambda atrás do API Gateway, e um banco de dados externo, neste caso, MongoDB Atlas.

No nosso caso de uso interno, temos uma função Lambda de trabalhador que chamará o STS para obter um token JWT de saída assinado. As funções chamam uma API no API Gateway onde temos um Lambda Authorizer que usa as chaves públicas do STS para validar o JWT.

Imagem mostrando a visão geral da arquitetura

No caso do MongoDB, temos praticamente o mesmo. Estabelecemos confiança entre o MongoDB e o STS fazendo alguma configuração no lado do MongoDB. Nossa função Lambda de trabalhador chamará o STS para obter o JWT e usá-lo como autenticação para o MongoDB, que usa as chaves públicas etc do STS para validar o usuário. Para o caso de uso do MongoDB, precisamos mapear tudo isso para um usuário, mas falaremos mais sobre essa configuração mais tarde.

Imagem mostrando a visão geral da arquitetura com MongoDB

Agora, vamos construir!

Habilitar a Federação de Identidade de Saída

Antes de podermos usar a Federação de Identidade de Saída, precisamos habilitá-la, por padrão ela está desativada. Portanto, vá para o AWS Console e IAM > Configurações da Conta.

Imagem mostrando o console IAM com Configurações de Conta destacadas

Role para baixo até a seção Federação de Identidade de Saída e clique em Habilitar.

Imagem mostrando a seção Federação de Identidade de Saída com o botão Habilitar

Uma vez habilitado, você obterá seu URL de Emissor de Tokens específico da conta imediatamente. Copie-o, você precisará dele mais tarde.

Imagem mostrando federação habilitada com URL do Emissor de Tokens

Você também pode habilitá-la via CLI se preferir:

aws iam enable-outbound-web-identity-federation

Agora vamos tentar isso e chamar o STS para obter um token, executamos o comando CLI abaixo para obter um token para nosso principal conectado.

aws sts get-web-identity-token --audience "my-api" --signing-algorithm RS256

Nós obteremos uma resposta como:

{
    "WebIdentityToken": "Token Codificado em Base64",
    "Expiration": "2026-03-19T08:20:06.177000+00:00"
}

Se decodarmos o WebIdentityToken, usando uma página da web como jwt.io, poderemos ver as diferentes declarações.

{
  "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": "..."
}

Verificando o Token com o API Gateway

Com isso funcionando, vamos construir uma API simples que uma função Lambda chama, onde temos um Lambda authorizer que valida o token.

Começamos criando a função chamadora, isso é bastante simples, chamamos o STS para obter um token e depois o apresentamos ao chamar a 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": "Erro ao chamar API protegida",
                    "error": str(e),
                }
            ),
        }

Se olharmos a função get_token(), ela é muito simples, apenas três parâmetros e pronto. Não precisamos buscar credenciais de lugar algum. O Lambda apenas diz "dê-me um token para este público" e o STS assina um com a própria identidade do Lambda.

O Lambda Authorizer

O Lambda Authorizer anexado ao API Gateway é onde as coisas começam a ficar realmente interessantes. O que faremos é buscar as chaves públicas do STS e depois validar o token usando uma biblioteca JWT padrão.

Criamos o descoberta usando o URL do emissor e anexando /.well-known/openid-configuration Isso nos diz onde podemos encontrar as chaves públicas e qual deve ser o valor do emissor. Depois, criamos um PyJWKClient que lida com o fetch e o cache das chaves.

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 expirou")
    except jwt.InvalidAudienceError:
        logger.warning("Audiência inválida")
    except jwt.InvalidIssuerError:
        logger.warning("Emissor inválido")
    except Exception as e:
        logger.error("Verificação do token falhou: %s", e)

    return generate_policy("Deny", event["methodArn"])

A função jwt.decode() é o núcleo de tudo e validará os tokens reais contra os valores esperados e lançará uma exceção em caso de problema.

Se tudo passar, permitimos a solicitação e encaminhamos a declaração sub para que o backend saiba quem está chamando.

Vale notar que não há nada específico da AWS nesta lógica de verificação. Esta é apenas uma validação padrão de token JWT.

Autenticação no MongoDB Atlas

Agora vamos colocar isso em um caso de uso real, conectando-se a um cluster MongoDB Atlas usando tokens do IAM Outbound Federation, eliminando a necessidade de armazenar senhas no Secrets Manager.

Há uma coisa muito importante a notar. Para que isso funcione, você precisará executar um cluster dedicado M10+ do MongoDB Atlas com MongoDB 7.0.11 ou superior. A Federação de Identidade de Trabalho não é suportada em clusters gratuitos ou compartilhados (M0, M2, M5).

A ideia é bastante simples e diretaforward. Nossa função Lambda chama o STS para obter um token, depois apresentamos esse token ao Atlas em vez de um nome de usuário e senha. O Atlas validará o token, usando fluxos padrão, e mapeará o Papel IAM para um usuário do banco de dados.

Configurando o Atlas

O primeiro passo agora é configurar as coisas no lado do MongoDB, faça login na sua conta e navegue até Federação em Identidade e Acesso.

Imagem mostrando o menu Federação do MongoDB

Em Federação, selecione Provedores de Identidade e começaremos a configurá-lo.

Imagem mostrando o início da Federação do MongoDB

Na tela seguinte, selecione Identidade de Trabalho para que possamos começar a adicionar detalhes.

Imagem mostrando o início da Identidade de Trabalho do MongoDB

Na tela de configuração, preencha nome e descrição. O URL do emissor é aquele do AWS IAM Console, que vimos anteriormente neste post. Para audiência, defina um valor de sua escolha, importante que seja o mesmo na configuração do MongoDB e AWS (ao chamar o STS). A audiência deve corresponder. Para a declaração do usuário, mantenha o padrão sub

Imagem mostrando a configuração do Provedor de Identidade do MongoDB

Salve e finalize sua configuração e você deverá ser apresentado com a tela de visão geral.

Imagem mostrando a configuração do Provedor de Identidade do MongoDB concluída

Em seguida, precisamos conectar o provedor de identidade que acabamos de criar com nossa organização. Selecione Organizações no menu e comece a conectar.

Imagem mostrando a conexão do Provedor de Identidade do MongoDB com a organização

No diálogo de popup, selecione o provedor que acabamos de criar e clique em Conectar.

Imagem mostrando o diálogo de conexão do Provedor de Identidade do MongoDB

Na próxima visão, devemos ver a visão geral da conexão.

Imagem mostrando a conexão do Provedor de Identidade do MongoDB concluída

Implantar aplicação de teste

Foram muitos passos, mas agora podemos implantar nossa aplicação de teste e fazer a configuração final no Atlas antes de testar. Manteremos a infraestrutura de teste mínima. Apenas uma função Lambda que tem permissão 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: ARN do Papel IAM para a função Lambda (use isso no mapeamento de papel OIDC do Atlas)
    Value: !GetAtt DatabaseOpFunctionRole.Arn

Adicionamos o ARN do papel à saída da pilha, pois precisaremos disso para a parte final do Atlas. O código está dividido no manipulador Lambda e um cliente que lidará com a conexão ao banco de dados MongoDB.

O driver pymongo tem um ótimo mecanismo de callback para OIDC. Nós fornecemos uma classe, e o driver a chama sempre que precisa de um token. O driver acessa fetch() na primeira conexão e novamente quando o token está prestes a expirar. Definimos o valor para 280 segundos, um pouco abaixo do real de 300, para ter uma margem de segurança.

Definir authMechanism="MONGODB-OIDC" diz ao driver para pular nome de usuário/senha e usar OIDC em vez disso.

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

Depois, em nosso manipulador Lambda, criamos o cliente MongoDB e interagimos com o banco de dados, neste exemplo simples, apenas inserindo um documento na coleção items.

def handler(event, context):
    try:
        client = get_mongo_client()
        db = client["sample_db"]
        collection = db["items"]

        document = {
            "message": "Olá do Lambda com autenticação OIDC",
            "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": "Conectado com sucesso ao MongoDB Atlas com OIDC",
                "insertedId": str(result.inserted_id),
            }),
        }
    except Exception as e:
        logger.error("Erro: %s", e)
        return {"statusCode": 500, "body": json.dumps({"error": str(e)})}

Criar um usuário do banco de dados no Atlas

Agora precisamos fazer o passo final no Atlas, criando um usuário do banco de dados e mapeando-o para o ARN do Papel IAM, para que possamos obter permissões para interagir com o banco de dados. Vá para Acesso ao Banco de Dados e adicione um novo usuário do banco de dados.

Imagem mostrando a criação de usuário do MongoDB

Selecione Autenticação Federada como o método de autenticação e defina o nome de usuário para o ARN do Papel da função Lambda, obtenha isso da saída da pilha. Atribua ao usuário as permissões Ler e escrever em qualquer banco de dados.

Imagem mostrando a conclusão da criação de usuário do MongoDB

Esse ARN de papel corresponderá à declaração sub no JWT. É assim que o Atlas sabe qual usuário do banco de dados usar.

Allowlist de acesso à rede

Antes de testarmos tudo, há mais uma coisa que precisamos fazer, devemos permitir o acesso à rede para nossa função Lambda. Como não executamos em uma VPC, devemos permitir 0.0.0.0/0 em uma configuração de produção, não faça isso, mas para este teste está tudo bem.

Navegue até Lista de Acesso de IP no menu

Imagem mostrando o acesso à rede do MongoDB no menu

Então adicione 0.0.0.0/0, defina-o como temporário para que seja removido automaticamente.

Imagem mostrando a permissão de rede IP do MongoDB

É isso! Agora podemos usar a Federação de Identidade de Saída no STS para autenticar no MongoDB Atlas e ler e escrever dados, sem senhas, sem segredos para gerenciar!!

Testando

Hora de fazer um pequeno teste, para isso, basta invocar a função Lambda implantada. Depois, vá para o Data Explorer no Atlas e verifique se os dados estão lá.

Imagem mostrando os dados de teste do MongoDB

Palavras Finais

Eu realmente gosto desse padrão. Sem segredos armazenados. Tokens que expiram em 5 minutos. Você sabe exatamente qual papel IAM fez cada conexão. E funciona com qualquer coisa que fale OIDC, não apenas MongoDB. Qualquer serviço externo que possa verificar tokens OIDC pode usar a mesma função get_oidc_token(). O mesmo código, destino diferente.

Confira meus outros posts em jimmydqv.com e me siga no LinkedIn e X para mais conteúdo sobre serverless.

Agora vá construir!