1. Sobre o que é esta aula e o que ela não cobre
Será uma aula bem interessante; nela nós:
- montamos mentalmente a imagem do “triângulo de confiança” entre MCP Client, MCP Server e MCP Auth Server — com a pessoa usuária, que fica “acima” desse triângulo como proprietária dos recursos;
- explicamos o fluxo: quem envia token para quem, onde o usuário faz login e por que o MCP Server nunca vê a senha dele;
- conectamos isso ao nosso backend Next.js/MCP e à futura configuração de Keycloak/Auth0.
O que não faremos hoje:
- não marcamos caixas de seleção no Keycloak e não configuramos um IdP específico;
- não escrevemos uma verificação completa de JWT nem introspecção — esses são temas das próximas aulas (sobre o Auth Server e sobre o MCP Server como recurso protegido).
A meta agora – que você possa pegar um papel, desenhar setas entre o ChatGPT, seu servidor e o Auth0/Keycloak e explicar sem hesitar: onde é o login, onde está o token e onde estão os dados.
2. Triângulo de confiança: MCP Client, MCP Server, MCP Auth Server
Comecemos pelos personagens. O “triângulo de confiança” técnico é formado pelo MCP Client, MCP Server e MCP Auth Server; o usuário (User) é um papel à parte, o proprietário dos recursos, que fica como que acima desse triângulo e concede acesso. Na especificidade do MCP e do Apps SDK, essa arquitetura é formalizada de maneira bem clara.
User (Resource Owner)
É a pessoa do outro lado da tela. Ela:
- entra no ChatGPT;
- envia uma solicitação “mostre meus pedidos / minhas listas de presentes”;
- aceita “vincular a conta” do seu serviço ao ChatGPT.
O principal: é ela quem possui os recursos (histórico de pedidos, perfis, listas de presentes) e é ela quem concede acesso a eles.
MCP Client
Para nós aqui, isso é:
- o ChatGPT com o Apps SDK;
- às vezes – o MCP Jam Inspector (durante a depuração).
O MCP Client sabe:
- ler metadados do seu MCP Server (via .well-known);
- iniciar o fluxo OAuth no navegador do usuário;
- armazenar e anexar tokens às chamadas das ferramentas MCP.
É importante lembrar que o MCP Client é um public client. Ele não armazena seu client_secret, portanto fala com o Auth Server como um aplicativo SPA público: Authorization Code + PKCE.
MCP Server (Resource Server)
É o seu backend que implementa MCP:
- estabelece conexão com o ChatGPT;
- declara ferramentas (tools), recursos, prompts;
- em cada chamada de ferramenta, analisa o cabeçalho Authorization: Bearer <token>;
- verifica o token (assinatura, exp, aud, scope) e, se estiver tudo ok, executa a lógica de negócios.
Ponto fundamental: o MCP Server não cuida do login. Ele não vê senhas, não desenha formulário de login, não envia ao usuário um e-mail “confirme o e-mail”. Ele confia apenas em tokens assinados criptograficamente emitidos pelo Auth Server.
MCP Auth Server (Authorization Server / IdP)
É um serviço separado de autenticação e autorização: Keycloak, Auth0, Ory Hydra+Kratos, Okta, Cognito, Azure AD etc.
Ele é responsável por:
- UI de login (e-mail/senha, SSO, 2FA);
- armazenamento de contas de usuários;
- emissão de tokens (access token, refresh token);
- publicação de metadados OAuth/OIDC (/authorize, /token, jwks_uri, /registration etc.).
Para MCP, ele deve oferecer suporte a OAuth 2.1 para public clients (PKCE S256, dynamic client registration etc.).
Tabela-resumo de papéis
| Quem | O que faz | O que não faz |
|---|---|---|
| User | Insere login/senha, dá consentimento para acesso aos dados | Não se comunica diretamente com o MCP Server |
| MCP Client (ChatGPT/Jam) | Inicia OAuth, armazena o token, chama MCP tools | Não verifica senhas, não valida a assinatura do token |
| MCP Server | Valida tokens, executa a lógica de negócios das tools | Não renderiza formulário de login, não armazena senhas |
| MCP Auth Server | Autentica o usuário, emite tokens | Não conhece suas ferramentas MCP nem sua lógica de negócios |
Se, na sua cabeça, tudo isso se misturava em um “grande servidor que faz tudo” – é hora de separar.
3. Como é o fluxo: de “sem token” até a chamada protegida das ferramentas
Agora vamos olhar o fluxo de mensagens. Na especificação MCP esse processo é chamado de “The Flow”: discovery → redirect → code → token → authorized calls.
Passo 0. Tentativa de chamar uma ferramenta protegida sem token
O usuário escreve: “Mostre minhas ideias salvas de presentes”.
O ChatGPT, como MCP Client, decide: “para isso é preciso chamar a ferramenta getUserGiftLists do nosso MCP Server”. Ele faz a chamada sem token (o usuário ainda não fez login).
Seu MCP Server:
- vê a ausência ou incorreção do cabeçalho Authorization;
- responde com 401 Unauthorized e adiciona o cabeçalho WWW-Authenticate: Bearer resource_metadata="https://api.giftgenius.com/.well-known/oauth-protected-resource" com um link para os metadados do recurso protegido (resource metadata, veremos abaixo).
Fica aproximadamente assim (lógica, não HTTP completo):
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://api.giftgenius.com/.well-known/oauth-protected-resource"
O ChatGPT vê esse cabeçalho e entende: “ok, o recurso é protegido por OAuth, precisamos executar o fluxo OAuth e vincular a conta”.
Discovery: .well-known/oauth-protected-resource
Em seguida, o MCP Client solicita ao seu servidor os metadados:
GET /.well-known/oauth-protected-resource
O servidor responde com um documento JSON contendo o identificador do recurso e a lista de servidores de autorização dos quais obter tokens.
Exemplo mínimo (vamos configurar em detalhes depois; agora importa a ideia):
{
"resource": "https://api.giftgenius.com",
"authorization_servers": [
"https://auth.giftgenius.com"
],
"scopes_supported": ["gifts.read", "gifts.write"]
}
Aqui:
- resource — o ID canônico do seu recurso; ele deve ser usado depois como audience ou resource na emissão do token;
- authorization_servers — a lista de Auth Servers nos quais o ChatGPT pode solicitar um token;
- scopes_supported — quais “permissões” o seu MCP Server entende.
Solicitação de autorização: redirecionamento para o Auth Server
Após receber os metadados, o MCP Client vai ao Auth Server. Ele abre no navegador uma aba:
GET https://auth.giftgenius.com/authorize
?response_type=code
&client_id=chatgpt-giftgenius
&redirect_uri=... (URL de retorno do MCP Client)
&code_challenge=...
&code_challenge_method=S256
&scope=openid gifts.read
&resource=https://api.giftgenius.com
O usuário:
- vê a tela de login conhecida (por exemplo, Keycloak ou Auth0);
- insere login/senha e passa pela 2FA;
- confirma que o ChatGPT pode ler suas listas de presentes (escopo gifts.read).
Code → Token: troca do código por token com PKCE
Após um login bem-sucedido, o Auth Server redireciona o usuário de volta ao MCP Client com um code. O MCP Client:
- faz um POST para /token;
- envia o code e o code_verifier (que corresponde ao code_challenge do passo anterior).
O Auth Server verifica o PKCE: calcula o hash do code_verifier e compara com o code_challenge original. Se estiver tudo ok e o cliente for realmente o mesmo que iniciou o fluxo, então:
- emite um access_token de curta duração (geralmente JWT);
- nele indica:
- sub — o ID do usuário no Auth Server;
- aud ou resource — o seu MCP Server;
- scope — as ações permitidas (gifts.read, openid etc.).
Solicitação autenticada: chamada da ferramenta MCP com token
Agora o MCP Client está pronto para chamar novamente a sua ferramenta, mas com o cabeçalho:
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
O MCP Server:
- verifica a assinatura do token (via JWK do Auth Server) ou via introspection;
- verifica o tempo de expiração (exp);
- verifica aud / resource — se o token foi realmente emitido para https://api.giftgenius.com;
- olha o scope e decide se pode chamar getUserGiftLists.
Depois disso, ele acessa tranquilamente seu banco de dados por algum userId e retorna as listas pessoais de presentes.
Observe que até este momento falamos apenas do fluxo de rede: como o token é obtido e chega ao MCP Server. Em seguida, é importante entender como de sub e outras claims no token se obtém um userId específico no seu BD — é aqui que entra a ponte de identidade (identity bridge).
4. Identity Bridge: como o usuário do ChatGPT vira userId no seu BD
A parte mais interessante da arquitetura é a “ponte de identidade” (identity bridge). A especificação MCP enfatiza diretamente: o MCP Server não conhece os usuários do ChatGPT, ele se baseia nos dados do token do Auth Server.
O esquema é aproximadamente assim:
flowchart TD User[User no ChatGPT] -->|Login/SSO| Auth[Auth Server] Auth -->|JWT: sub, email, tenant| MCP[MCP Server] MCP -->|userId/tenantId| DB[(Seu banco de dados)]
Passo a passo fica assim.
Primeiro, o Auth Server conhece seus próprios usuários: ele tem entidades user, email, id, possivelmente tenant, roles. Ao fazer login com sucesso, ele coloca essas informações no token (nas claims):
{
"sub": "auth0|abc123",
"email": "user@example.com",
"given_name": "Alice",
"https://giftgenius.com/tenant": "tenant-42",
"scope": "openid gifts.read",
"aud": "https://api.giftgenius.com"
}
Em segundo lugar, o MCP Server, ao verificar o token, extrai essas claims e decide quem é essa pessoa no seu mundo. Por exemplo:
- se o sub já existe na tabela User.authProviderId, pegamos o userId associado;
- se não existir, criamos um registro local (on‑the‑fly provisioning) e vinculamos.
Um trecho típico de código TypeScript no lado do MCP Server (simplificado, sem verificação de assinatura) pode ser assim:
type TokenClaims = {
sub: string;
email?: string;
scope?: string;
};
async function mapClaimsToUserId(claims: TokenClaims): Promise<string> {
const user = await db.user.findUnique({ where: { authSub: claims.sub } });
if (user) return user.id;
const created = await db.user.create({
data: { authSub: claims.sub, email: claims.email ?? null }
});
return created.id;
}
Em terceiro lugar, já com seu próprio userId, o MCP Server busca tudo o que precisa: listas de presentes, histórico de pedidos, configurações, plano.
Assim, o Auth Server se torna a “ponte” entre o mundo externo (ChatGPT, Google, SSO) e o seu mundo interno (customer_id no banco de dados de pedidos).
5. Por que separar Auth Server e MCP Server
Pode surgir a tentação: “Vamos fazer com que meu MCP Server também mostre o login e emita o token”. Formalmente é possível (você pode embutir um mini‑IdP nele), mas arquiteturalmente é uma má ideia. As razões são bem pragmáticas.
Primeiro, segurança e escalabilidade. O Auth Server é uma máquina pesada: 2FA, social login, políticas de senha, bloqueio de conta, recuperação de acesso, auditoria de logins, possivelmente certificações. Reescrever isso em cada microsserviço (em cada MCP Server) é caminho para o inferno e para o PCI‑DSS. Muito melhor delegar isso ao Keycloak/Auth0 e apenas verificar o token deles.
Segundo, capacidade de trocar clientes. Hoje você só tem o ChatGPT. Amanhã você conecta o Claude Desktop, seu web frontend em Next.js, o app móvel. Todos eles podem usar o mesmo Auth Server e o mesmo esquema OAuth 2.1, e o seu MCP Server apenas continua verificando tokens. Você não precisará reescrever a lógica de negócios para cada novo cliente.
Terceiro, limpeza do código. O MCP Server, no ideal:
- sabe publicar /.well-known/oauth-protected-resource;
- sabe verificar o token Bearer e extrair dele userId, scopes, tenant;
- implementa as ferramentas de negócio (orders, gifts, profiles).
Toda a UI de login — formulários, layout, logins sociais — vive no Auth Server e não polui o backend.
6. Como isso fica no nosso app didático GiftGenius
Voltando ao aplicativo que conduzimos ao longo do curso. Suponha que temos:
- um ChatGPT App “GiftGenius” com um widget (Apps SDK) que sabe sugerir presentes;
- um MCP Server em Node/Next.js que expõe ferramentas:
- searchGifts — anônima, não requer login;
- getSavedGiftLists — pessoal, requer autenticação;
- um Auth Server (depois — Keycloak/Auth0), onde cada usuário tem uma conta.
Cenário de usuário anônimo e autenticado
Se o usuário simplesmente diz “escolha um presente para meu irmão, 30 anos, gosta de jogos de tabuleiro”, nosso App pode:
- chamar a ferramenta anônima searchGifts;
- exibir recomendações na interface.
Nesse caso:
- não é necessário token;
- o MCP Server apenas executa a solicitação (por exemplo, ao seu catálogo ou a uma API de terceiros).
Assim que o usuário diz “salve isto nas minhas listas” ou “mostre minhas ideias salvas”, o modelo decide chamar a ferramenta protegida getSavedGiftLists. O servidor responde com 401 + WWW-Authenticate com resource_metadata. O ChatGPT inicia o assistente “Link GiftGenius account”, guia o usuário pelo login e obtém um token.
Depois, a cada chamada protegida:
- o MCP Server já vê Authorization: Bearer ...;
- extrai o userId do token;
- filtra os dados por esse userId.
É graças a isso que podemos:
- isolar os dados de usuários diferentes;
- exibir com segurança histórico de pedidos, lista de favoritos;
- implementar funções de comércio (mais adiante no curso).
Arquitetura do backend: middleware + handlers de ferramentas
Na prática, em código Node/Next.js, isso frequentemente aparece como uma cadeia: “middleware de autenticação → handler de negócios da ferramenta”. Na aula sobre implementação de handlers de ferramentas, já enfatizamos que é preciso repassar o contexto: user_id, tokens, configurações.
Um fragmento de código pode ser assim:
// auth-context.ts
export type AuthContext = {
userId: string | null; // null para chamadas anônimas
scopes: string[];
};
Middleware pendurado em todos os endpoints MCP:
// mcp-auth-middleware.ts
export async function buildAuthContext(req: Request): Promise<AuthContext> {
const header = req.headers.authorization || "";
const token = header.replace(/^Bearer\s+/i, "");
if (!token) return { userId: null, scopes: [] }; // usuário anônimo
const claims = await verifyAndDecodeToken(token); // verificação do token
const userId = await mapClaimsToUserId(claims);
const scopes = (claims.scope || "").split(" ");
return { userId, scopes };
}
E o próprio handler da ferramenta recebe esse contexto:
// tools/getSavedGiftLists.ts
export async function getSavedGiftLists(_args: {}, ctx: AuthContext) {
if (!ctx.userId) throw new Error("User must be authenticated");
return db.giftList.findMany({
where: { ownerId: ctx.userId }
});
}
A ideia é que o handler da ferramenta não sabe nada sobre OAuth nem sobre PKCE. Ele simplesmente trabalha com um userId “óbvio”. Toda a mágica de OAuth fica antes dele: no MCP Client e no Auth middleware.
7. Esquemas visuais: como convivem Client, Server e Auth
Já detalhamos o fluxo no texto da seção 3. Às vezes é mais fácil ver uma vez do que explicar sete, então vamos mostrar as mesmas interações em duas diagramas.
Esqueleto da interação (The Triangle of Trust)
flowchart TD U[User] -->|1. Login / Consent| A[MCP Auth Server] U -->|2. Conversa| C["MCP Client (ChatGPT)"] C -->|3. OAuth Flow| A C -->|4. Bearer Token| S[MCP Server] S -->|5. Data| C
O esquema se lê assim.
Primeiro, o usuário faz login via o Auth Server, que essencialmente confirma sua identidade e emite um token. O MCP Client gerencia esse processo e depois usa o token para acessar o MCP Server. O MCP Server não vê login/senha; ele vê apenas o token e decide o que é permitido.
Fluxo do pedido à resposta
sequenceDiagram participant User participant ChatGPT as MCP Client participant Auth as Auth Server participant MCP as MCP Server User->>ChatGPT: "Mostre minhas listas de presentes" ChatGPT->>MCP: callTool(getSavedGiftLists) (sem token) MCP-->>ChatGPT: 401 + WWW-Authenticate (resource_metadata) ChatGPT->>Auth: /authorize + PKCE User->>Auth: Insere login/senha, dá consentimento Auth-->>ChatGPT: redirect + code ChatGPT->>Auth: /token + code_verifier Auth-->>ChatGPT: access_token (JWT) ChatGPT->>MCP: callTool(getSavedGiftLists) + Authorization: Bearer ... MCP-->>ChatGPT: JSON com listas pessoais ChatGPT-->>User: Lista renderizada no widget
Este diagrama é aquilo que você deve conseguir “explicar de olhos fechados” ao final do módulo.
8. Um pouco mais fundo: múltiplos recursos, múltiplos clientes, DCR
O bom dessa arquitetura — ela escala.
Primeiro, você pode ter vários MCP Servers (por exemplo, um sobre presentes, outro sobre pedidos) e um único Auth Server emitindo tokens com diferentes aud/resource. Cada servidor de recursos é obrigado a verificar que o token foi realmente destinado a ele, caso contrário teremos o problema clássico do “confused deputy”, quando um token para um serviço é aceito por outro.
Segundo, você pode ter muitos clientes:
- ChatGPT App;
- seu próprio frontend;
- aplicativo móvel;
- integração de parceiro via MCP Gateway.
Todos eles irão:
- ler /.well-known/oauth-protected-resource;
- descobrir onde está o Auth Server;
- passar pelo fluxo OAuth 2.1;
- obter tokens e chamar o MCP Server.
Terceiro, Auth Servers modernos cada vez mais suportam Dynamic Client Registration (DCR) — a capacidade de registrar clientes dinamicamente via API. A especificação MCP justamente pressupõe tal possibilidade: o cliente (ChatGPT/Jam) pode se registrar automaticamente no Auth Server via seu registration_endpoint.
Neste módulo, é importante entender que:
- o MCP Client, o MCP Server e o Auth Server se comunicam por meio de documentos de discovery padronizados e tokens;
- você não precisa “fixar rigidamente” todos os clientes no código do backend;
- você pode expandir o ecossistema sem quebrar o modelo de autorização existente.
9. Erros típicos no entendimento da arquitetura de autorização MCP
Erro nº 1: “O MCP Server deve ele mesmo autenticar o usuário”.
Às vezes desenvolvedores tentam embutir o formulário de login diretamente no MCP Server e então enviar login/senha por meio das ferramentas. Isso subverte a própria ideia do OAuth. O MCP Server não deve ver a senha sob nenhuma circunstância. Login e consentimento são responsabilidade do Auth Server. O MCP Server trabalha apenas com tokens e suas claims.
Erro nº 2: Confusão entre MCP Client e MCP Server.
Acontece de tomarem o ChatGPT como “parte do meu backend” e tentarem, por exemplo, armazenar segredos nele ou esperar que ele próprio verifique direitos de acesso. Na verdade, o MCP Client apenas inicia o OAuth e anexa tokens. Validar token e permissões é tarefa do MCP Server, não do ChatGPT.
Erro nº 3: “Chave de API no .env em vez de OAuth”.
Anti‑padrão clássico: criar um grande SERVICE_API_KEY, colocá‑lo no .env do MCP Server e achar que está resolvido. Nesse cenário não há segregação de permissões por usuário, não é possível exibir dados pessoais com segurança ou efetuar compras, tudo é feito “em nome do serviço”, não do usuário. Isso contraria completamente os objetivos de autorização nos ChatGPT Apps.
Erro nº 4: Ignorar audience e resource.
Se o MCP Server aceita qualquer JWT válido com assinatura correta e não verifica aud/resource, então qualquer token emitido para outro serviço pelo mesmo Auth Server pode ser usado para chamar suas ferramentas. É uma violação direta do modelo de segurança do OAuth. O servidor deve verificar que o token foi emitido exatamente para o seu resource.
Erro nº 5: Misturar lógica de auth e lógica de negócios.
Às vezes começam a enfiar nos handlers de ferramentas toda a análise do token, verificação de assinatura, trabalho com JWK etc. No fim, o código fica frágil e difícil de manter. Muito mais correto é separar a camada “verificação do token, mapeamento para userId” (middleware) da camada “lógica da ferramenta”, que recebe um AuthContext já claro.
Erro nº 6: Esperar que o ChatGPT “faça tudo sozinho” sem .well-known.
Sem o endpoint correto /.well-known/oauth-protected-resource, o MCP Client simplesmente não sabe onde está o seu Auth Server nem quais scopes são necessários. Resultado — o chat “não sabe fazer login”, e o desenvolvedor fica olhando logs vazios. O caminho certo: o MCP Server declara claramente seus requisitos de autorização via .well-known, o cliente lê e constrói o fluxo.
Erro nº 7: Usuário “esquecido” na lógica de negócios.
Às vezes, mesmo configurando corretamente o OAuth e o mapeamento do token para userId, os desenvolvedores não usam isso nas consultas ao BD: por exemplo, esquecem de filtrar por ownerId = userId. Assim, qualquer usuário autenticado pode ver dados de outros. Ter um token é apenas o primeiro passo; o segundo é sempre usar corretamente userId e scope no código de negócios.
GO TO FULL VERSION