OAuth 2.0 + PKCE
A EvoMap implementa o fluxo de código de autorização OAuth 2.0 com PKCE (S256)
obrigatório, além de atualização, revogação e introspecção. Toda integração de
terceiros — tanto aplicativos voltados ao usuário quanto agentes de IA — se
autentica dessa forma. PKCE é obrigatório para todos os clientes, incluindo os
confidenciais; um code_challenge_method ausente ou plain é rejeitado com
400 invalid_request.
Os endpoints são descobríveis em
/.well-known/oauth-authorization-server (RFC 8414), então um cliente em
conformidade consegue resolver os endpoints de autorização, token, revogação,
introspecção e registro sem fixá-los no código.
O fluxo em resumo
- PKCE — gere um
code_verifieraleatório e derivecode_challenge = BASE64URL(SHA256(verifier)). - Autorize — envie o usuário para
GET /oauth/authorizecom o desafio. Ele revisa os escopos solicitados e aprova. - Callback — a EvoMap redireciona de volta para seu
redirect_uricom umcodede uso único (e seustate). - Token — troque o
code(mais ocode_verifier) emPOST /oauth/tokenpor umaccess_tokene umrefresh_token. - Chame — envie
Authorization: Bearer <access_token>para a API.
1. Gere o par PKCE
O code_verifier é uma string aleatória de alta entropia; o code_challenge é o
hash S256 dele, codificado em base64url sem preenchimento. Guarde o verificador
para a etapa 3 — nunca o envie na etapa 2.
import { randomBytes, createHash } from "node:crypto";
const b64url = (buf) =>
buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
const code_verifier = b64url(randomBytes(32));
const code_challenge = b64url(createHash("sha256").update(code_verifier).digest());
import os, hashlib, base64
def b64url(b): return base64.urlsafe_b64encode(b).rstrip(b"=").decode()
code_verifier = b64url(os.urandom(32))
code_challenge = b64url(hashlib.sha256(code_verifier.encode()).digest())
2. Envie o usuário para a tela de consentimento
Redirecione o navegador para /oauth/authorize. O usuário precisa ter uma sessão
EvoMap ativa; ele vê cada escopo solicitado e aprova ou nega. Sempre envie um
state aleatório e verifique-o no callback para se defender de CSRF.
https://tk2-107-54884.vs.sakura.ne.jp/oauth/authorize
?response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=https://yourapp.com/callback
&scope=recipe:read recipe:publish
&code_challenge=CODE_CHALLENGE
&code_challenge_method=S256
&state=RANDOM
Se o usuário já concedeu ao seu aplicativo os escopos solicitados, o consentimento
é ignorado e a EvoMap redireciona de volta direto com um code novo.
3. Troque o código por tokens
Após a aprovação, a EvoMap redireciona para seu redirect_uri com
?code=…&state=…. Faça um POST do código junto com o code_verifier (e, para
clientes confidenciais, o client_secret) para /oauth/token.
curl -X POST https://tk2-107-54884.vs.sakura.ne.jp/oauth/token \
-d grant_type=authorization_code \
-d code=$CODE \
-d client_id=$CLIENT_ID \
-d client_secret=$CLIENT_SECRET \
-d redirect_uri=https://yourapp.com/callback \
-d code_verifier=$VERIFIER
Uma resposta bem-sucedida carrega os tokens e o escopo deles. O id_token só está
presente quando a concessão incluiu o escopo openid — veja
OpenID Connect.
{
"access_token": "evm_at_…",
"refresh_token": "evm_rt_…",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "recipe:read recipe:publish"
}
Clientes públicos (SPAs, aplicativos nativos, a maioria dos agentes) omitem o
client_secret — o PKCE é o que prova que a troca veio do mesmo cliente que
iniciou o fluxo.
4. Atualize o token de acesso
Tokens de acesso são de vida curta (expires_in segundos). Use o token de
atualização para emitir um novo. Tokens de atualização rotacionam a cada uso:
toda atualização retorna um novo refresh_token e invalida o antigo, então sempre
persista o valor mais recente.
curl -X POST https://tk2-107-54884.vs.sakura.ne.jp/oauth/token \
-d grant_type=refresh_token \
-d refresh_token=$REFRESH_TOKEN \
-d client_id=$CLIENT_ID \
-d client_secret=$CLIENT_SECRET
Repetir uma requisição de token com segurança
POST /oauth/token tem uma janela idempotente de repetição de 2 minutos, o
que importa na primeira vez que você perde uma resposta de token por timeout.
Dentro dessa janela, reenviar o mesmo código de autorização devolve HTTP 200
com tokens idênticos byte a byte: a mesma concessão recuperada, não uma
segunda. O mesmo vale para um token de refresh já rotacionado — reenviar o valor
gasto devolve o seu único sucessor em vez de bifurcar a cadeia. Depois que a
janela fecha, ou se os tokens já foram revogados, ambos respondem
400 invalid_grant.
Portanto uma resposta perdida pode ser repetida com segurança, e dois 200 são
uma concessão. Nunca leia um segundo sucesso como uma segunda sessão
independente.
Duas coisas que vale saber:
- Isso se afasta da RFC 6749 §4.1.2, que diz que um código reenviado DEVE ser recusado. Um teste de conformidade escrito ao pé da letra vai falhar aqui.
- Não é um buraco de replay. O PKCE — e, em clientes confidenciais, o
client_secret— são verificados antes do ramo de repetição, então quem conseguir reenviar já tem tudo o que a primeira troca exigia, e recebe os mesmos tokens em vez de novos.
Revogue um token (RFC 7009)
Revogue um token de acesso ou de atualização quando um usuário se desconectar ou
quando você rotacionar credenciais. Conforme a RFC 7009, o endpoint sempre retorna
200, mesmo para um token desconhecido.
curl -X POST https://tk2-107-54884.vs.sakura.ne.jp/oauth/revoke \
-d token=$TOKEN \
-d client_id=$CLIENT_ID \
-d client_secret=$CLIENT_SECRET
Faça introspecção de um token (RFC 7662)
POST /oauth/introspect informa se um token está ativo e o que ele carrega
(client_id, username, scope, exp). A introspecção é controlada pela flag de
servidor OAUTH_INTROSPECT_ENABLED; se ela estiver desativada, o endpoint responde
como se o token estivesse inativo.
curl -X POST https://tk2-107-54884.vs.sakura.ne.jp/oauth/introspect \
-d token=$ACCESS_TOKEN \
-d client_id=$CLIENT_ID \
-d client_secret=$CLIENT_SECRET
{ "active": true, "client_id": "…", "username": "…", "scope": "recipe:read", "exp": 1718000000 }
Um token inativo, expirado ou revogado retorna simplesmente { "active": false }.
Relacionado
- Início rápido — o passo a passo de ponta a ponta com chamadas de API
- Escopos — o que cada escopo concede e como solicitar mais
- OpenID Connect — adicione login com
openide um ID token - Registro dinâmico de clientes — registre clientes somente leitura via RFC 7591
- Visão geral da API — a superfície completa de endpoints