OAuth 2.1
Visão Geral
A Uappi expõe um Authorization Server OAuth 2.1 (authorization_code + PKCE obrigatório) para permitir que clients externos — como agentes de IA (ex.: Claude, via MCP) ou integrações de terceiros — ajam em nome de um usuário do painel sem que ele compartilhe email/senha com o client.
Diferente do login tradicional (email/senha, token de sessão), o fluxo OAuth envolve registro de client, redirecionamento do usuário para consentimento e troca de código por token — o padrão usado por qualquer provedor OAuth de mercado (Google, GitHub, etc.).
Quando usar
Use OAuth em vez do login tradicional quando o consumidor da API é um client de terceiros (não o próprio painel administrativo Uappi) que precisa operar em nome de um usuário específico, com escopo de permissão explicitamente concedido por esse usuário — por exemplo, o servidor MCP Uappi Staff Panel (user/mcp, ver documentação de MCP) aceita tanto um token de sessão do painel quanto um access token OAuth.
Fluxo completo
1. Registro do client — POST /oauth/user/register. Não exige autenticação prévia (RFC 7591, Dynamic Client Registration). Retorna client_id (e client_secret, se o client for confidencial).
2. Descoberta (opcional) — um client pode descobrir os endpoints do Authorization Server via /.well-known/oauth-authorization-server (RFC 8414) no domínio da loja, em vez de assumir os paths fixos abaixo.
3. Autorização — GET /oauth/user/authorize, com code_challenge/code_challenge_method=S256 (PKCE) obrigatórios. Redireciona (302) para a tela de consentimento do painel administrativo.
4. Consentimento — o usuário, já autenticado no painel, vê os escopos solicitados e decide quais concede (GET/POST /oauth/user/consent).
5. Emissão do token — POST /oauth/user/token, trocando o code (ou um refresh_token existente) por um access_token.
6. Uso do token — o access_token é enviado em Authorization: Bearer {access_token} nas chamadas subsequentes à API, exatamente como um token de sessão comum.
Escopos
Os escopos solicitados no authorize (parâmetro scope) são sempre restringidos pela interseção entre o que o client pediu e o que o perfil do usuário logado de fato permite — o usuário nunca consegue conceder mais acesso do que já possui. O access token resultante carrega essa lista de escopos, e cada operação da API continua reforçando o mesmo sistema de permissões usado no restante da plataforma (ver Escopos de Permissão).
Tempo de vida dos tokens
| Token | Duração |
|---|---|
access_token | 8 horas (mesmo TTL da sessão padrão do painel) |
refresh_token | 6 meses |
access_token expirado é renovado enviando o refresh_token para POST /oauth/user/token com grant_type=refresh_token, sem repetir a tela de consentimento.Os endpoints de cada etapa estão detalhados abaixo: registro, autorização, consulta de consentimento, aprovação de consentimento e emissão/renovação de token.
Registrar Client OAuth
/oauth/user/register
Registra um novo client OAuth (RFC 7591 — Dynamic Client Registration)
Descrição
Registra um novo client OAuth para o Authorization Server de usuários do painel. Não exige autenticação prévia — é o primeiro passo de qualquer integração nova, seguindo o formato de resposta padrão da RFC 7591 (sem o envelope de resposta padrão da Uappi).
Parâmetros
Headers Obrigatórios
| Header | Valor | Descrição |
|---|---|---|
Content-Type | application/json | Tipo de conteúdo |
Parâmetros do Corpo
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| client_name | string | Não | Nome do client (máximo 255 caracteres). Se omitido, usa o host da primeira redirect_uri |
| redirect_uris | array de string | Sim | Ao menos uma URL de redirecionamento válida |
| token_endpoint_auth_method | string | Não | client_secret_post (client confidencial, padrão) ou none (client público, ex.: SPA/mobile) |
Exemplo de Requisição
1curl --request POST \2 --url {{MAIN_URL}}/oauth/user/register \3 --header "Content-Type: application/json" \4 --data '{5 "client_name": "Meu Client OAuth",6 "redirect_uris": ["https://meuapp.com/callback"]7 }'✅ Resposta de Sucesso
HTTP Status: 201 Created
1{2 "client_id": "01a0620d-695b-709a-a4da-61c2c07417f0",3 "client_secret": "diP7JT3ya3QrPc6twV9ifqQQvmmIXcPhjjxzXqO8",4 "grant_types": ["authorization_code", "refresh_token"],5 "response_types": ["code"],6 "redirect_uris": ["https://meuapp.com/callback"],7 "scope": "ORDER_READ ORDER_WRITE PRODUCT_READ ...",8 "token_endpoint_auth_method": "client_secret_post"9}⚠️ client_secret só é retornado nesta resposta — guarde-o com segurança, ele não pode ser recuperado depois. Vem null quando token_endpoint_auth_method é none (client público).
⚠️ Possíveis erros:
| Código Interno | Código Http | Descrição |
|---|---|---|
| invalid_client_metadata | 400 | Algum campo do corpo é inválido (ex.: redirect_uris ausente ou não é uma URL válida). |
Solicitar Autorização
/oauth/user/authorize
Inicia o fluxo de autorização OAuth, redirecionando para a tela de consentimento
Descrição
Valida o pedido de autorização (client, redirect_uri, PKCE) e redireciona (302) o navegador para a tela de consentimento do painel administrativo, carregando um continuationToken de curto prazo (10 minutos) que representa esse pedido.
PKCE (code_challenge + code_challenge_method=S256) é obrigatório para todo client — não há suporte a clients sem PKCE.
Parâmetros
Parâmetros de Query
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| response_type | string | Sim | Sempre code |
| client_id | string | Sim | Client obtido no endpoint de registro |
| redirect_uri | string | Sim | Deve corresponder a uma das redirect_uris registradas do client |
| scope | string | Sim | Lista de escopos solicitados, separados por espaço (ex.: ORDER_READ PRODUCT_READ) |
| state | string | Não | Valor opaco devolvido inalterado ao final do fluxo, para prevenir CSRF |
| code_challenge | string | Sim | Desafio PKCE (SHA-256 do code_verifier, base64url sem padding) |
| code_challenge_method | string | Sim | Sempre S256 |
Exemplo de Requisição
1curl --request GET \2 --url "{{MAIN_URL}}/oauth/user/authorize?response_type=code&client_id=SEU_CLIENT_ID&redirect_uri=https://meuapp.com/callback&scope=ORDER_READ%20PRODUCT_READ&state=xyz&code_challenge=SEU_CODE_CHALLENGE&code_challenge_method=S256"✅ Resposta de Sucesso
HTTP Status: 302 Found
1Location: https://loja.exemplo.com/uappi/oauth-authorize?continuationToken=eyJ0eXAiOiJKV1Qi...O client deve seguir esse redirecionamento normalmente (é a tela de login/consentimento do painel, não uma resposta de API a ser tratada programaticamente).
⚠️ Possíveis erros:
| Código Interno | Código Http | Descrição |
|---|---|---|
| invalid_request | 302 (redirect para redirect_uri) ou 400 | client_id/redirect_uri desconhecidos, code_challenge ausente, ou outro parâmetro OAuth inválido (RFC 6749 §4.1.2.1) |
Consultar Consentimento
/oauth/user/consent
Retorna os dados de consentimento (client, escopos) para a tela de autorização
Descrição
Usado pela tela de consentimento do painel administrativo para montar a interface — retorna o nome do client solicitante e a lista de escopos a exibir. Exige uma sessão de painel autenticada; se o usuário ainda não estiver logado, retorna UAPPI_API_OAUTH_SESSION_NOT_AUTHENTICATED para que a tela redirecione para o login e volte automaticamente.
Os escopos retornados já vêm restringidos pela interseção entre o que o client pediu (no authorize) e o que o perfil do usuário logado de fato permite — nunca é preciso filtrar escopo no client.
Parâmetros
Headers Obrigatórios
| Header | Valor | Descrição |
|---|---|---|
Authorization | Bearer {token} | Token de sessão do painel |
Parâmetros de Query
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| continuationToken | string | Sim | Token retornado pelo redirecionamento do endpoint de autorização |
Exemplo de Requisição
1curl --request GET \2 --url "{{MAIN_URL}}/oauth/user/consent?continuationToken=SEU_CONTINUATION_TOKEN" \3 --header "Authorization: Bearer SEU_TOKEN_DE_PAINEL"✅ Resposta de Sucesso
1{2 "status": "consent_required",3 "continuationToken": "eyJ0eXAiOiJKV1Qi...",4 "client": {5 "id": "01a0620d-695b-709a-a4da-61c2c07417f0",6 "name": "Meu Client OAuth"7 },8 "scopes": ["ORDER_READ", "PRODUCT_READ"],9 "scopeDescriptions": {10 "ORDER_READ": "Escopo de permissão para leitura de pedidos",11 "PRODUCT_READ": "Escopo de permissão para leitura de produtos"12 }13}⚠️ O continuationToken desta resposta já vem reemitido com a identidade da sessão autenticada — é esse token (não o original do authorize) que deve ser enviado para o endpoint de aprovação de consentimento.
⚠️ Possíveis erros:
| Código Interno | Código Http | Descrição |
|---|---|---|
| UAPPI_API_OAUTH_SESSION_NOT_AUTHENTICATED | 401 | Sessão do painel ainda não autenticada — redirecionar para o login. |
| UAPPI_API_OAUTH_AUTHORIZATION_REQUEST_INVALID | 401 | continuationToken inválido ou expirado (validade de 10 minutos). |
Aprovar Consentimento
/oauth/user/consent
Aprova o pedido de autorização com os escopos selecionados pelo usuário
Descrição
Confirma o consentimento do usuário autenticado, gerando o código de autorização e retornando a URL de redirecionamento de volta para o client (redirect_uri original, com code e state). Só os escopos enviados em scopes e que o usuário de fato possui são concedidos — a interseção final é sempre recalculada no servidor.
Parâmetros
Headers Obrigatórios
| Header | Valor | Descrição |
|---|---|---|
Authorization | Bearer {token} | Token de sessão do painel |
Content-Type | application/json | Tipo de conteúdo |
Parâmetros do Corpo
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| continuationToken | string | Sim | Token retornado pelo endpoint de consulta de consentimento |
| scopes | array de string | Não | Escopos que o usuário optou por conceder. Omitido/vazio = nenhum escopo concedido |
Exemplo de Requisição
1curl --request POST \2 --url {{MAIN_URL}}/oauth/user/consent \3 --header "Authorization: Bearer SEU_TOKEN_DE_PAINEL" \4 --header "Content-Type: application/json" \5 --data '{6 "continuationToken": "eyJ0eXAiOiJKV1Qi...",7 "scopes": ["ORDER_READ", "PRODUCT_READ"]8 }'✅ Resposta de Sucesso
1{2 "redirectUri": "https://meuapp.com/callback?code=def50200...&state=xyz"3}O client (ou a tela que chamou este endpoint) deve navegar para redirectUri — o code ali contido é trocado por um token no endpoint de emissão de token.
⚠️ Possíveis erros:
| Código Interno | Código Http | Descrição |
|---|---|---|
| UAPPI_API_OAUTH_AUTHORIZATION_REQUEST_INVALID | 401 | continuationToken inválido, expirado, ou sem a identidade da sessão embutida. |
Emitir/Renovar Token
/oauth/user/token
Troca um código de autorização (ou refresh token) por um access token
Descrição
Endpoint de protocolo puro (RFC 6749) — sem o envelope de resposta padrão da Uappi. Suporta dois grant_type:
authorization_code: troca ocodeobtido no endpoint de aprovação de consentimento por umaccess_token+refresh_token. Exige ocode_verifieroriginal (PKCE).refresh_token: renova umaccess_tokenexpirado usando umrefresh_tokenválido, sem repetir a tela de consentimento.access_token: válido por 8 horas (expires_inem segundos).refresh_token: válido por 6 meses.
Parâmetros
Headers Obrigatórios
| Header | Valor | Descrição |
|---|---|---|
Content-Type | application/x-www-form-urlencoded | Tipo de conteúdo |
Parâmetros do Corpo — grant_type=authorization_code
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| grant_type | string | Sim | authorization_code |
| client_id | string | Sim | Client obtido no endpoint de registro |
| client_secret | string | Condicional | Obrigatório se o client for confidencial (token_endpoint_auth_method=client_secret_post) |
| redirect_uri | string | Sim | Mesma redirect_uri usada no endpoint de autorização |
| code | string | Sim | Código retornado em redirectUri pelo endpoint de aprovação de consentimento |
| code_verifier | string | Sim | Verificador PKCE original (usado para gerar o code_challenge do authorize) |
Parâmetros do Corpo — grant_type=refresh_token
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| grant_type | string | Sim | refresh_token |
| client_id | string | Sim | Mesmo client que originou o token |
| client_secret | string | Condicional | Obrigatório se o client for confidencial |
| refresh_token | string | Sim | Refresh token emitido junto do access token anterior |
Exemplo de Requisição
1curl --request POST \2 --url {{MAIN_URL}}/oauth/user/token \3 --data "grant_type=authorization_code" \4 --data "client_id=SEU_CLIENT_ID" \5 --data "client_secret=SEU_CLIENT_SECRET" \6 --data "redirect_uri=https://meuapp.com/callback" \7 --data "code=SEU_CODE" \8 --data "code_verifier=SEU_CODE_VERIFIER"✅ Resposta de Sucesso
1{2 "token_type": "Bearer",3 "expires_in": 28800,4 "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",5 "refresh_token": "def50200..."6}⚠️ Possíveis erros:
| Código Interno | Código Http | Descrição |
|---|---|---|
| invalid_request | 400 | Parâmetro obrigatório ausente ou malformado (ex.: client_secret faltando). |
| invalid_grant | 400 | code/refresh_token inválido, expirado, já usado, ou code_verifier não corresponde ao code_challenge original. |
| invalid_client | 401 | client_id/client_secret inválidos. |