Endpoints de teste para cada esquema de autenticação de API
Basic, Digest, tokens bearer, chaves de API, assinaturas HMAC, JWT, OAuth 1.0a e OAuth 2.0 com PKCE, com credenciais.
Exemplos executados pela última vez contra a API em
Para testar a autenticação de um cliente de API sem configurar um provedor de identidade, use os endpoints de teste públicos em https://api.testingapis.com/api/authentre para ver. Há um endpoint por esquema: HTTP Basic, Digest, tokens bearer fixos, chaves de API em cabeçalho, query ou cookie, e assinatura HMAC de requisições. Um login JWT tem refresh tokens e um token expirado para testes negativos, um servidor OAuth 2.0 suporta PKCE e client credentials, e um servidor OAuth 1.0a confere requisições assinadas. As credenciais são fixas e públicas, listadas abaixo e em GET /api/authentre para ver. Credenciais erradas devolvem um 401 correto, e um token válido sem o papel certo devolve um 403.
Endpoints e credenciais
| Esquema | Endpoint | Credenciais |
|---|---|---|
| HTTP Basic | GET /api/auth/basicentre para ver | apibee / password123 |
| HTTP Digest (MD5, qop=auth) | GET /api/auth/digestentre para ver | apibee / password123 |
| Token bearer | GET /api/auth/bearerentre para ver | Authorization: Bearer apibee-token-123 |
| Chave de API, cabeçalho | GET /api/auth/api-key/headerentre para ver | X-API-Key: apibee-key-123 |
| Chave de API, query | GET /api/auth/api-key/queryentre para ver | ?api_key=apibee-key-123 |
| Chave de API, cookie | GET /api/auth/api-key/cookieentre para ver | Cookie: api_key=apibee-key-123 |
| Assinatura HMAC-SHA256 | POST /api/auth/hmacentre para ver | Segredo apibee-hmac-secret |
| Papéis e scopes | GET /api/auth/roles/adminentre para ver | admin-token, user-token, readonly-token |
| JWT | POST /api/auth/jwt/loginentre para ver | admin / admin123, user / user123 |
| OAuth 2.0 | /api/auth/oauth/*entre para ver | apibee-client / apibee-secret, cliente público apibee-public |
| OAuth 1.0a | /api/auth/oauth1/*entre para ver | Consumer apibee-consumer-key / apibee-consumer-secret, token apibee-access-token / apibee-token-secret |
Basic e Digest
Uma senha ausente ou errada devolve 401 com um cabeçalho WWW-Authenticate, que é o que faz navegadores e bibliotecas HTTP pedirem credenciais ou tentarem de novo com elas:
$ curl -i -u apibee:password123 https://api.testingapis.com/api/auth/basic
HTTP/1.1 200 OK
{"authenticated":true,"scheme":"basic","user":"apibee"}
$ curl -i https://api.testingapis.com/api/auth/basic
HTTP/1.1 401 Unauthorized
www-authenticate: Basic realm="apibee", charset="UTF-8"
{"status":401,"error":"Unauthorized","message":"Missing or malformed Basic Authorization header."}O Digest leva duas idas e voltas. O primeiro 401 traz um nonce, e o cliente responde com um hash da senha em vez da própria senha. O --digest do curl faz as duas coisas:
$ curl -i --digest -u apibee:password123 https://api.testingapis.com/api/auth/digest
HTTP/1.1 401 Unauthorized
www-authenticate: Digest realm="apibee", qop="auth", algorithm=MD5, nonce="MTc5...", opaque="069d..."
HTTP/1.1 200 OK
{"authenticated":true,"scheme":"digest","user":"apibee","qop":"auth","nc":"00000001"}Para testar um cliente que precisa lidar com servidores que escondem recursos protegidos, /api/auth/basic-hidden/{user}/{pass}entre para ver devolve 404 em vez de 401 quando as credenciais não batem.
Tokens bearer e chaves de API
Os três endpoints de chave de API aceitam a mesma chave em lugares diferentes, então você confere se o seu cliente a coloca onde a API espera. Um token bearer errado devolve WWW-Authenticate: Bearer error="invalid_token".
/api/auth/api-key/header401 vs 403
401 quer dizer que o servidor não sabe quem você é: sem credenciais, ou com credenciais ruins. 403 quer dizer que ele sabe exatamente quem você é, e você não tem permissão. Um cliente deve tratá-los de forma diferente. Depois de um 401, ele pode entrar de novo ou renovar um token. Depois de um 403, entrar de novo não muda nada. Os endpoints de papéis devolvem cada um:
$ curl -i https://api.testingapis.com/api/auth/roles/admin
HTTP/1.1 401 Unauthorized
www-authenticate: Bearer realm="apibee"
$ curl -i -H "Authorization: Bearer user-token" https://api.testingapis.com/api/auth/roles/admin
HTTP/1.1 403 Forbidden
{"status":403,"error":"Forbidden","message":"This endpoint requires the 'admin' role.","roles":["user"],"scopes":["read","write"]}
$ curl -H "Authorization: Bearer admin-token" https://api.testingapis.com/api/auth/roles/admin
{"authorized":true,"user":"Alice Admin","roles":["admin","user"],"scopes":["read","write","delete"]}DELETE /api/auth/roles/resourceentre para ver confere um scope em vez de um papel. readonly-token só tem read, então recebe um 403 que cita o scope delete que falta.
Assinatura HMAC de requisições
APIs de pagamento e de webhooks costumam assinar requisições em vez de enviar um segredo. Aqui a assinatura é o HMAC-SHA256 em hexadecimal minúsculo de timestamp + "." + raw body, enviado em X-Signature com o timestamp em X-Timestamp. Assine exatamente os bytes que você envia. Reserializar o JSON depois de assinar é o jeito mais comum de quebrar a assinatura.
import { createHmac } from "node:crypto";
const secret = "apibee-hmac-secret";
const body = JSON.stringify({ orderId: 1001, amount: 49.99 });
const timestamp = Math.floor(Date.now() / 1000).toString();
const signature = createHmac("sha256", secret)
.update(`${timestamp}.${body}`)
.digest("hex");
const res = await fetch("https://api.testingapis.com/api/auth/hmac", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Timestamp": timestamp,
"X-Signature": signature,
},
body,
});
console.log(res.status, await res.json());200 { authenticated: true, scheme: 'hmac-sha256', timestamp: 1791341858, bodyLength: 31 }
# The same signature sent with a different body:
401 {
status: 401,
error: 'Unauthorized',
message: 'Signature mismatch.',
hint: 'X-Signature = lowercase hex HMAC-SHA256(secret, X-Timestamp + "." + raw request body). Optional \'sha256=\' prefix.',
bodyLength: 30
}Para conferir uma assinatura à mão, o gerador de hashes calcula HMAC-SHA256 no navegador.
Login JWT, expiração e renovação
/api/auth/jwt/loginentre para ver devolve um access token assinado que dura 15 minutos e um refresh token. Os refresh tokens rotacionam: cada renovação devolve um novo, e o antigo para de funcionar. /api/auth/jwt/expiredentre para ver entrega um token corretamente assinado que já expirou, para você testar o caminho de expiração sem esperar 15 minutos.
const BASE = "https://api.testingapis.com/api/auth/jwt";
const login = await fetch(`${BASE}/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username: "user", password: "user123" }),
});
const tokens = await login.json();
// { accessToken, tokenType: "Bearer", expiresIn: 900, refreshToken, user }
const auth = { Authorization: `Bearer ${tokens.accessToken}` };
console.log((await fetch(`${BASE}/me`, { headers: auth })).status); // 200
console.log((await fetch(`${BASE}/admin`, { headers: auth })).status); // 403: role is "user"
// A correctly signed token that expired an hour ago, for the negative test.
const { accessToken: expired } = await (await fetch(`${BASE}/expired`)).json();
const old = await fetch(`${BASE}/me`, { headers: { Authorization: `Bearer ${expired}` } });
console.log(old.status, old.headers.get("www-authenticate"));
// 401 Bearer error="invalid_token", error_description="The token expired at '...'"
// Refresh tokens rotate: use the new one next time.
const refreshed = await fetch(`${BASE}/refresh`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ refreshToken: tokens.refreshToken }),
});
console.log(refreshed.status); // 200Cole qualquer um desses tokens no decodificador de JWT para ver os claims e o horário de expiração.
OAuth 2.0 com PKCE
O servidor OAuth suporta o grant authorization code com PKCE, client credentials, password e refresh token, além de introspecção e revogação. A autorização é aprovada automaticamente como o usuário apibee, então o fluxo inteiro roda num script. Redirect URIs em localhost ou 127.0.0.1 (qualquer porta) e o callback do Postman recebem um redirecionamento 302 de verdade.
Authorization code com PKCE
import { createHash, randomBytes } from "node:crypto";
const BASE = "https://api.testingapis.com/api/auth/oauth";
const redirectUri = "http://localhost:3000/callback";
// 1. A random code verifier and its S256 challenge.
const verifier = randomBytes(32).toString("base64url");
const challenge = createHash("sha256").update(verifier).digest("base64url");
// 2. Send the user to /authorize. This server approves at once and redirects
// to the redirect URI with ?code=...&state=...
const authorize = new URL(`${BASE}/authorize`);
authorize.search = new URLSearchParams({
response_type: "code",
client_id: "apibee-public",
redirect_uri: redirectUri,
scope: "read profile",
state: "xyz123",
code_challenge: challenge,
code_challenge_method: "S256",
});
const redirect = await fetch(authorize, { redirect: "manual" });
const code = new URL(redirect.headers.get("location")).searchParams.get("code");
// 3. Exchange the code and the verifier for tokens.
const tokenRes = await fetch(`${BASE}/token`, {
method: "POST",
body: new URLSearchParams({
grant_type: "authorization_code",
client_id: "apibee-public",
code,
redirect_uri: redirectUri,
code_verifier: verifier,
}),
});
const tokens = await tokenRes.json();
// 4. Call a protected endpoint.
const me = await fetch(`${BASE}/userinfo`, {
headers: { Authorization: `Bearer ${tokens.access_token}` },
});
console.log(me.status, await me.json());200 {
sub: 'apibee',
name: 'TestingAPIs Tester',
preferred_username: 'apibee',
email: null,
email_verified: null
}As duas falhas que um cliente PKCE deve tratar:
# Sending the same code a second time
400 { error: 'invalid_grant', error_description: 'Authorization code is invalid, expired or already used.' }
# Sending a code_verifier that doesn't match the challenge
400 { error: 'invalid_grant', error_description: 'PKCE verification failed: code_verifier does not match code_challenge.' }Client credentials
Para chamadas entre serviços, sem nenhum usuário envolvido:
$ curl -X POST https://api.testingapis.com/api/auth/oauth/token \
-d "grant_type=client_credentials&client_id=apibee-client&client_secret=apibee-secret&scope=read"
{"access_token":"oat_3fc4...","token_type":"Bearer","expires_in":3600,"scope":"read"}Requisições assinadas com OAuth 1.0a
O OAuth 1.0a assina cada requisição com dois segredos, o do consumer e o do token, em vez de mandar um token sozinho. O servidor de teste aceita assinaturas HMAC-SHA1, HMAC-SHA256, HMAC-SHA512 e PLAINTEXT. Use o access token fixo apibee-access-token / apibee-token-secret, que nunca expira, ou rode o fluxo three-legged para ter um token seu. A autorização é aprovada automaticamente como o usuário apibee.
POST /api/auth/oauth1/request_tokenentre para ver, assinado só com a chave e o segredo do consumer, comoauth_callbackdefinido como uma URL ouoob.GET /api/auth/oauth1/authorize?oauth_token=…entre para ver redireciona para um callback em localhost ou do Postman comoauth_verifier, e o devolve como JSON paraoob.POST /api/auth/oauth1/access_tokenentre para ver, assinado com o request token e o segredo dele, maisoauth_verifier. O access token dura uma hora.
The protected resource, with the static access token:
1. New request: GET https://api.testingapis.com/api/auth/oauth1
2. Authorization → Type: OAuth 1.0
Signature Method: HMAC-SHA1
Consumer Key: apibee-consumer-key
Consumer Secret: apibee-consumer-secret
Access Token: apibee-access-token
Token Secret: apibee-token-secret
Add authorization data to: Request Headers
3. Send. The response has "authenticated": true.
The three-legged flow, with the same Authorization settings:
1. POST https://api.testingapis.com/api/auth/oauth1/request_token
Leave Access Token and Token Secret empty.
Advanced → Callback URL: oob
2. Open GET https://api.testingapis.com/api/auth/oauth1/authorize?oauth_token=<oauth_token from step 1>
and copy oauth_verifier from the JSON.
3. POST https://api.testingapis.com/api/auth/oauth1/access_token
Access Token: oauth_token from step 1
Token Secret: oauth_token_secret from step 1
Advanced → Verifier: oauth_verifier from step 2
4. Use the oauth_token and oauth_token_secret it returns as
Access Token and Token Secret for GET /api/auth/oauth1.O PLAINTEXT, na aba cURL, não precisa de biblioteca de assinatura: a assinatura é a própria chave de assinatura, consumer_secret&token_secret, com o & enviado como %26. Use-o para conferir as credenciais e depois mude para HMAC-SHA1 para testar o seu código de assinatura.
Quando uma assinatura não bate, o 401 inclui a string base de assinatura que o servidor montou, para você comparar com a do seu cliente. As falhas que vale a pena testar:
# Signed with the wrong token secret
401 signature_invalid: Signature mismatch.
details.signatureBaseString: GET&https%3A%2F%2Fapi.testingapis.com%2Fapi%2Fauth%2Foauth1&oauth_consumer_key%3D...
# The same oauth_nonce sent twice
401 nonce_used: This oauth_nonce was already used with this timestamp. Send a new nonce.
# The same request token exchanged twice
401 token_used: This request token was already exchanged for an access token.As regras de assinatura do OAuth 1.0a explicam onde ficam os parâmetros, a janela do timestamp e cada código oauth_problem.
Testes negativos que valem a pena
- Sem credenciais: espere 401 e um cabeçalho
WWW-Authenticate. - Senha errada, chave errada, token adulterado: 401, nunca 500.
- Usuário válido sem o papel ou o scope: 403, e o cliente não desconecta o usuário.
- JWT expirado: o cliente renova uma vez e depois repete a requisição original.
- Um refresh token usado duas vezes: o segundo uso falha.
- Um código OAuth usado duas vezes, ou com o verifier errado:
invalid_grant. - Uma assinatura HMAC sobre outro corpo: 401.
- Uma requisição OAuth 1.0a com nonce repetido ou assinatura errada: 401
nonce_usedousignature_invalid.
Os conceitos por trás desses testes estão na lição de autenticação e autorização (em inglês).