1. O que é um Auth Server na prática e por que escolhemos o Keycloak
Vamos começar com um breve lembrete: um Auth Server (IdP) — é um serviço que:
- exibe para o usuário a tela de login/registro e o consentimento;
- emite tokens OAuth/OIDC (access_token, id_token, refresh_token);
- publica o documento de discovery e as chaves JWKS para que os servidores de recursos possam validar esses tokens.
No nosso stack:
- ChatGPT / MCP Jam atua como um cliente OAuth (public client);
- seu MCP‑servidor — como Resource Server;
- Keycloak — como Auth Server.
Por que o Keycloak é prático para o curso e para a vida real:
- é open source e fácil de subir localmente/em Docker;
- tem um modelo de entidades bastante claro: realm, clients, users, roles;
- na prática, qualquer configuração que você dominar no Keycloak se transfere quase 1:1 para Auth0/Okta/Cognito: são as mesmas ideias — client, scopes, redirect URIs, PKCE.
Ideia importante: não estamos configurando não “o Keycloak para o projeto inteiro”, e sim um Realm específico para o nosso ChatGPT App. É uma espécie de “sandbox” de autenticação só para os clientes MCP.
Em resumo, ao final da aula você terá:
- seu próprio realm no Keycloak para o aplicativo do ChatGPT;
- um public‑client configurado com Authorization Code + PKCE;
- um conjunto mínimo de scopes e claims;
- entendimento de como esse token “vive” no seu servidor Node‑MCP.
2. Entidades básicas do Keycloak sob a ótica do MCP
Para não se perder depois no admin, vamos organizar as entidades.
Realm: espaço de configurações e usuários
O Realm no Keycloak é um espaço isolado com seus próprios usuários, clientes e políticas. Uma analogia útil — “um escritório alugado em um centro empresarial”: cada um tem sua própria sala, sua lista de funcionários e suas regras de entrada.
Para o curso e para o seu primeiro App real, faz sentido criar um realm separado, por exemplo giftgenius-mcp ou mcp-course. Isso permite:
- não mexer no realm master para não quebrar o admin por engano;
- reaproveitar configurações e usuários entre diferentes ambientes (dev / staging / prod) via export/import do realm.
Client: registro do aplicativo (ChatGPT / MCP Jam)
O Client no Keycloak não é um “usuário”, e sim um aplicativo que busca tokens no Auth Server. No nosso caso, não é o seu backend em Next.js, e sim o cliente MCP: ChatGPT, MCP Jam, possivelmente um widget seu em separado, caso você faça um fluxo OAuth manual no UI.
Campos-chave do cliente:
- client_id — identificador em string;
- tipo (public / confidential / bearer-only);
- fluxos OAuth habilitados (Standard Flow, Client Credentials etc.);
- lista de redirect URI permitidos;
- lista de scopes e protocol mappers (claims no token).
Para ChatGPT/MCP Jam, precisamos de um public client, porque:
- o ChatGPT, como cliente, não pode armazenar com segurança o client_secret;
- o MCP Jam, como ferramenta desktop/baseada em navegador, também roda em um ambiente não confiável.
User: a pessoa real
User é a pessoa “de verdade”: tem username, senha, email, atributos, grupos, roles. Quando alguém faz login pelo Keycloak, o sub e outros dados dele entram no token que você valida no servidor MCP e mapeia para seu accountId / tenantId.
No nível da nossa demo, é suficiente ter:
- um ou dois usuários de teste (por exemplo, alice@example.com, bob@example.com);
- talvez alguns atributos como tenant ou plan, se quisermos mostrar como claims do token afetam o comportamento dos tools.
3. Escolha do tipo de cliente: public, PKCE e por que sem secret
Agora, ao que interessa: como configurar o cliente no Keycloak para ChatGPT/MCP.
Public vs Confidential: por que não usar client_secret
Em um aplicativo web clássico, você tem um backend, coloca nele o client_secret, e é o servidor que vai ao IdP buscar tokens. Isso é um confidential client: ele pode armazenar um segredo.
No mundo do ChatGPT é o contrário:
- o cliente OAuth é a própria plataforma ChatGPT ou uma utilidade como o MCP Jam;
- você não controla o código e o ambiente dela;
- qualquer client_secret que você entregue ao ChatGPT deve ser considerado comprometido imediatamente.
Por isso, ChatGPT/Jam funcionam como public clients, ou seja, sem client_secret, e compensam isso com PKCE — Proof Key for Code Exchange.
PKCE em linguagem humana
PKCE é um “segredo” descartável por sessão. O objetivo é impedir que alguém apenas intercepte o authorization code e o troque por um token em outro lugar. O fluxo é:
- O cliente gera uma string aleatória code_verifier.
- Calcula o hash dela (geralmente SHA‑256) e obtém o code_challenge.
- Ao redirecionar para /authorize, envia o code_challenge e o code_challenge_method=S256.
- Depois do login, o usuário volta com o code no redirect URI.
- O cliente faz POST para /token, passando o code e o code_verifier original.
- O Keycloak calcula o hash do verifier, compara com o challenge e, se estiver tudo certo, emite o token.
O importante para nós: quem faz tudo isso é o ChatGPT/MCP Jam. Nós só precisamos habilitar no cliente do Keycloak o Authorization Code + PKCE (S256) e não exigir client_secret.
4. Passo a passo de configuração do Keycloak para o cenário MCP
Definimos então: para ChatGPT/MCP Jam, precisamos de um public client com Authorization Code Flow e PKCE (S256), sem client_secret. Agora vamos ver como essa configuração aparece nas telas do Keycloak.
Suponha que você já tenha um Keycloak funcionando (container Docker, instalação local — tanto faz). Neste momento, nos interessa a lógica das configurações, e não “onde clicar” exatamente.
Criando um novo realm para o App
Vamos criar o realm giftgenius-mcp: é uma área separada, onde ficarão:
- usuários especificamente para o aplicativo do ChatGPT;
- clientes pelos quais ChatGPT/MCP Jam passará no OAuth;
- suas próprias políticas de senhas e de tokens.
Dica prática: não misture o realm no qual você autoriza os funcionários do admin com o realm para clientes do ChatGPT. Isso é mais seguro e também mais simples de entender.
Adicionando um usuário de teste
Crie um usuário, por exemplo alice:
- username: alice;
- email: alice@example.com;
- defina uma senha (para simplificar — sem políticas complexas);
- se quiser, adicione o atributo tenantId=demo-tenant ou a role ROLE_PREMIUM.
Depois, no servidor MCP, você poderá decodificar o token, extrair o sub, email, tenantId e associá-los ao seu modelo de usuário.
Criando um public client para MCP Jam / ChatGPT
Agora vem a parte mais interessante — o Client.
Conceitualmente, os parâmetros devem ficar assim:
- Client ID: giftgenius-mcp-client (o nome fica a seu critério);
- Tipo: public / Client Authentication off;
- Standard Flow (Authorization Code) habilitado;
- PKCE habilitado com método S256;
- redirect URI configurados;
- scopes necessários configurados (openid + o seu customizado, por exemplo, mcp:tools).
Habilitando Standard Flow e PKCE
No nível conceitual:
- habilite o Authorization Code Flow (muitas vezes chamado de “Standard Flow Enabled”);
- na seção PKCE, defina pkceRequired=true e, na maioria dos casos, explicitamente code_challenge_method=S256.
Por que S256: na documentação moderna do OAuth 2.1 e nas recomendações de OpenAI/Model Context Protocol, S256 é o método seguro suportado; o PKCE “plain” é considerado inseguro.
Redirect URI — a parte mais sensível
Os redirect URI devem corresponder caractere por caractere ao que será usado pelo cliente. Caso contrário, teremos um erro invalid_redirect_uri na etapa de autorização.
Em nosso curso, há dois clientes típicos:
- MCP Jam/Inspector para depuração. Eles geralmente rodam em http://localhost:PORT/.... Para o cenário local, faz sentido permitir redirects como:
- http://localhost:5173/* ou um caminho específico que o Jam use.
- ChatGPT / Apps SDK em produção. Aqui, o redirect URI é definido pela própria plataforma. Em uma integração real, você consultará a documentação atual da OpenAI e informará a URL necessária que o ChatGPT usará como callback.
Para a aula, o importante é entender: o ChatGPT não pode usar qualquer redirect; ele deve corresponder ao registrado no Auth Server. Portanto:
- nunca use * e “qualquer URL serve”;
- para desenvolvimento local, wildcards podem ser aceitáveis dentro de localhost, mas não em produção.
Scopes: mínimo, mas suficiente
Scopes são um mini‑conjunto de permissões que o cliente solicita.
Para o nosso cenário MCP, normalmente precisamos de:
- openid — para habilitar o OpenID Connect e receber o id_token com o campo sub, às vezes email;
- um scope customizado, por exemplo mcp:tools, que sinaliza “acesso permitido aos MCP tools”.
No Keycloak, isso pode ser feito via Client Scopes:
- mantenha o openid;
- desative por padrão scopes supérfluos como profile e email, se você não precisar deles;
- adicione um novo scope mcp:tools, com o qual você limitará o acesso aos tools no Resource Server.
Isso importa por dois motivos:
- Sem openid, você não recebe o id_token nem parte dos campos OIDC padrão.
- Sem um scope customizado separado, você não conseguirá, no lado do servidor MCP, afirmar claramente: “este token pode ser usado para chamar meus tools”.
5. Configuração dos tokens: tempo de vida, assinatura e claims
Agora, vamos ver quais tokens o Keycloak emite e como ajustá‑los para o cenário MCP.
Tempo de vida do access token
Nas configurações do realm, o Keycloak tem a seção Tokens, onde você pode definir:
- Access Token Lifespan;
- Refresh Token Lifespan e outros timeouts.
Para o ChatGPT App, são importantes access tokens de curta duração:
- alguns minutos ou horas — um valor razoável;
- se o token expirar, o servidor MCP responde 401, o ChatGPT reinicia o fluxo OAuth e o usuário, se necessário, faz login novamente.
A ideia é a mesma da documentação do Apps SDK da OpenAI: TTL curto + renovação de tokens e a possibilidade de “deslogar” rapidamente um usuário revogando o token no IdP.
Refresh tokens, para o cliente do ChatGPT, geralmente não são tão críticos ou são emitidos com prazo curto, para evitar sessões eternas.
Quais claims queremos ver no token
Minimamente, precisamos de:
- sub — identificador único do usuário no Keycloak;
- iss — quem emitiu o token (issuer);
- aud — para qual recurso é o token (usado depois no servidor MCP);
- exp — tempo de expiração;
- scope — a lista de scopes.
Adicionalmente, são úteis com frequência:
- email — se você quiser ver o endereço do usuário;
- tenantId ou claim similar — para cenários multi‑tenant;
- roles — para autorização adicional.
No Keycloak, isso se ajusta com Protocol Mappers:
- mappers padrão para email, preferred_username etc.;
- mappers customizados para atributos de usuário (user.attribute → claim.name).
Exemplo: um mapper que adiciona o email como claim no token, definindo user.attribute=email, claim.name=email.
No lado do servidor MCP, você poderá obter essas claims do JWT parseado e:
- associar o sub ao seu accountId;
- usar o tenantId para buscar apenas dados pertencentes a esse tenant;
- usar roles para uma autorização mais “fina”.
Assinatura do token e JWKS
Por padrão, o Keycloak assina os tokens de access/id com um algoritmo assimétrico (geralmente RS256) e publica as chaves públicas via o endpoint JWKS do documento de OpenID Discovery.
Para nós, isso é importante porque o servidor MCP consegue:
- pegar o issuer do token;
- pelo /.well-known/openid-configuration encontrar o endpoint JWKS;
- obter a chave pública e verificar a assinatura do token localmente.
Vamos detalhar isso na aula sobre o servidor MCP como resource server protegido, mas já é útil entender por que o Keycloak fornece esses metadados.
6. Dynamic Client Registration (DCR): quando realmente é necessário
Esta seção é mais avançada. Até agora, configuramos o cliente “na mão” pelo admin, e isso já é mais do que suficiente para subir o App. Mas o protocolo OAuth permite que clientes se registrem dinamicamente por um endpoint específico.
No contexto do ChatGPT e do MCP, a OpenAI afirma que a plataforma pode usar Dynamic Client Registration. Ou seja, o ChatGPT se registra no Auth Server “on the fly”, pelo registration_endpoint do documento de discovery.
No Keycloak, isso se parece com:
- ativar o DCR no nível do realm;
- configurar a política: quem pode registrar novos clientes e com quais grant types/scopes.
Exemplo de JSON para registrar um public client com Authorization Code + PKCE e scope openid mcp:tools:
{
"clientName": "My ChatGPT App",
"redirectUris": ["https://jam.proxy.mcpapps.com/callback"],
"grantTypes": ["authorization_code"],
"responseTypes": ["code"],
"scope": "openid mcp:tools",
"tokenEndpointAuthMethod": "none"
}
Onde tokenEndpointAuthMethod: "none" significa justamente um public client sem client_secret.
Para o curso, basta saber que:
- DCR é útil quando há muitos clientes ou quando eles são de curta duração;
- o ChatGPT potencialmente pode se registrar sozinho no seu IdP;
- mas, no começo, dá para usar um cliente estático criado pelo UI.
7. Como isso se conecta ao nosso aplicativo didático
Lembre que temos um servidor MCP didático (por exemplo, GiftGenius), que sabe:
- listar possíveis presentes;
- armazenar algumas listas de desejos do usuário;
- mais adiante — acessar a parte de comércio, criar pedidos etc.
Enquanto o servidor MCP está aberto, ele não sabe quem está batendo nele:
- uma requisição do ChatGPT pode ser “da Alice” ou “do Bob” do ponto de vista lógico, mas o servidor MCP não distingue;
- você não pode exibir o histórico de presentes privado;
- você não pode cobrar com confiança da conta correta.
Depois de configurar o Keycloak como Auth Server, a situação muda:
- O ChatGPT entende pelo .well-known do seu recurso MCP que ele é protegido e exige token.
- O ChatGPT envia o usuário ao Keycloak pelo Authorization Code + PKCE flow.
- O usuário faz login (a nossa alice).
- O ChatGPT recebe um access token com sub, email, mcp:tools e outras claims.
- O ChatGPT chama a ferramenta GiftGenius já com Authorization: Bearer <token>.
- O servidor MCP, ao validar o token, entende: “Certo, é a Alice com sub=... e tenantId=demo-tenant” — e responde de acordo.
Integraremos isso na próxima aula, em que transformaremos o servidor MCP em um resource server “de verdade”: implementaremos o endpoint de metadados, a verificação de token e o vínculo ao usuário.
8. Pequenos exemplos práticos (nosso stack: TypeScript + Node)
Tudo abaixo não é “a única forma correta”, e sim uma referência de como isso pode ficar em um stack típico de Node/TypeScript. Se você estiver mais focado agora em clicar no Keycloak, pode apenas passar os olhos e voltar quando for plugar o servidor MCP.
Embora a configuração do Keycloak seja feita principalmente no UI ou via Admin REST API, é útil mostrar alguns trechos de código ao redor, para ficar claro como você usará tudo isso no lado do servidor MCP.
Suponha que já temos um servidor MCP em Node.js (TypeScript) baseado no SDK oficial.
Config de autorização (issuer e audience)
Vamos criar um pequeno módulo authConfig.ts:
// authConfig.ts
export const authConfig = {
issuer: 'https://auth.my-company.com/realms/giftgenius-mcp',
audience: 'https://mcp.my-company.com', // URL do seu servidor MCP
requiredScopes: ['mcp:tools'], // mínimo que esperamos no token
};
Aqui, o issuer é o URL do realm do Keycloak e o audience é o identificador do recurso (ainda vamos usá-lo na configuração do token e do MCP).
Verificação básica de JWT via JWKS
Na prática, você provavelmente usará uma biblioteca como jsonwebtoken + jwks-rsa ou utilitários prontos do MCP SDK. Um esqueleto simples pode ser assim:
// verifyToken.ts
import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';
import { authConfig } from './authConfig';
const client = jwksClient({
jwksUri: `${authConfig.issuer}/protocol/openid-connect/certs`,
});
function getKey(header: any, callback: any) {
client.getSigningKey(header.kid, (err, key) => {
const signingKey = key?.getPublicKey();
callback(err, signingKey);
});
}
export function verifyAccessToken(token: string): Promise<any> {
return new Promise((resolve, reject) => {
jwt.verify(
token,
getKey,
{
audience: authConfig.audience,
issuer: authConfig.issuer,
},
(err, decoded) => (err ? reject(err) : resolve(decoded)),
);
});
}
Claro, o tratamento de erros e o cache de chaves devem ser feitos com mais cuidado, mas a ideia é: o Keycloak publica as chaves JWKS, nós as buscamos e verificamos a assinatura.
Verificar scope e extrair a identidade
No middleware para os MCP‑tools, você pode fazer algo como:
// authMiddleware.ts
import { verifyAccessToken } from './verifyToken';
import { authConfig } from './authConfig';
export async function requireAuth(bearerToken: string) {
const token = bearerToken.replace(/^Bearer\s+/i, '');
const decoded: any = await verifyAccessToken(token);
const scopes = (decoded.scope as string).split(' ');
const hasScope = authConfig.requiredScopes.every(s => scopes.includes(s));
if (!hasScope) {
throw new Error('Insufficient scope');
}
return {
userId: decoded.sub,
email: decoded.email,
tenantId: decoded.tenantId,
};
}
E então, nos handlers dos MCP‑tools, você usará userId e tenantId para carregar as listas de presentes do usuário. Os tools em si já implementamos em módulos anteriores; agora, o importante é ver como o token do Keycloak vira uma identidade compreensível para o seu backend.
9. Erros comuns ao configurar o Keycloak como MCP Auth Server
Erro nº 1: usar confidential client com client_secret.
Às vezes, por hábito, criam um cliente do tipo confidential e tentam colocar o client_secret no config do MCP/ChatGPT. No ecossistema do ChatGPT App isso não deve funcionar e não é seguro: o ChatGPT é um public client, ele não pode guardar segredo. O caminho correto — public client + PKCE.
Erro nº 2: scopes muito amplos por padrão.
Deixar profile, email e uma porção de scopes padrão habilitados — e depois distribuir esses tokens para cada chat — não é uma boa ideia. Melhor minimizar: openid e um mcp:tools específico (ou alguns scopes aplicacionais) — suficientes para as primeiras versões. Isso reduz o risco de vazar dados desnecessários e torna o comportamento mais previsível.
Erro nº 3: redirect URI incorreto.
Clássico: no Keycloak está http://localhost:5173/callback, mas o MCP Jam usa http://localhost:5173/. Ou o contrário. Resultado — invalid_redirect_uri e uma depuração extremamente frustrante. Sempre confira o redirect URI exato na documentação do Jam/ChatGPT e registre-o letra por letra.
Erro nº 4: PKCE não habilitado ou habilitado com o método errado.
Algumas versões do Keycloak exigem habilitar “PKCE required” e definir o método S256. Se isso não for feito, o ChatGPT/Jam, que espera PKCE, pode receber invalid_request reclamando do code_challenge. Verifique sempre as configurações de PKCE para public clients.
Erro nº 5: claims incorretas ou ausentes no token.
Acontece de faltar sub ou email no token porque o scope correspondente ou o protocol mapper não está configurado. Como resultado, no servidor MCP você vê o token, mas não consegue mapeá-lo para um usuário real. Solução: garanta que os campos necessários (mínimo sub, e de preferência também email/tenantId) estejam mapeados em access/id tokens.
Erro nº 6: TTL muito longo para access tokens.
Do ponto de vista de segurança, emitir access tokens válidos por um dia/semana é uma má ideia. Em caso de vazamento, o atacante terá acesso de longo prazo ao recurso MCP. Prefira access tokens de curta duração (minutos ou horas) e reautorize quando necessário.
Erro nº 7: confusão com o realm e uso do master.
Às vezes, a primeira coisa que fazem é criar clientes e usuários direto no realm master. Depois acoplam mais alguns projetos no mesmo lugar e, no fim, nada fica claro. Melhor criar desde o início um realm separado para cada aplicativo/curso. Isso facilita a vida para você e para o seu DevOps.
GO TO FULL VERSION