Des points d’accès de test pour chaque schéma d’authentification d’API
Basic, Digest, tokens bearer, clés d’API, signatures HMAC, JWT, OAuth 1.0a et OAuth 2.0 avec PKCE, avec les identifiants.
Exemples exécutés pour la dernière fois contre l’API le
Pour tester l’authentification d’un client d’API sans mettre en place de fournisseur d’identité, utilisez les points d’accès de test publics de https://api.testingapis.com/api/authconnectez-vous pour voir. Il y en a un par schéma : HTTP Basic, Digest, tokens bearer fixes, clés d’API en en-tête, en query ou en cookie, et signature HMAC des requêtes. Une connexion JWT fournit des refresh tokens et un token expiré pour les tests négatifs, un serveur OAuth 2.0 prend en charge PKCE et client credentials, et un serveur OAuth 1.0a vérifie les requêtes signées. Les identifiants sont fixes et publics, listés ci-dessous et sur GET /api/authconnectez-vous pour voir. De mauvais identifiants renvoient un vrai 401, et un token valide sans le bon rôle renvoie un 403.
Points d’accès et identifiants
| Schéma | Point d’accès | Identifiants |
|---|---|---|
| HTTP Basic | GET /api/auth/basicconnectez-vous pour voir | apibee / password123 |
| HTTP Digest (MD5, qop=auth) | GET /api/auth/digestconnectez-vous pour voir | apibee / password123 |
| Token bearer | GET /api/auth/bearerconnectez-vous pour voir | Authorization: Bearer apibee-token-123 |
| Clé d’API, en-tête | GET /api/auth/api-key/headerconnectez-vous pour voir | X-API-Key: apibee-key-123 |
| Clé d’API, query | GET /api/auth/api-key/queryconnectez-vous pour voir | ?api_key=apibee-key-123 |
| Clé d’API, cookie | GET /api/auth/api-key/cookieconnectez-vous pour voir | Cookie: api_key=apibee-key-123 |
| Signature HMAC-SHA256 | POST /api/auth/hmacconnectez-vous pour voir | Secret apibee-hmac-secret |
| Rôles et scopes | GET /api/auth/roles/adminconnectez-vous pour voir | admin-token, user-token, readonly-token |
| JWT | POST /api/auth/jwt/loginconnectez-vous pour voir | admin / admin123, user / user123 |
| OAuth 2.0 | /api/auth/oauth/*connectez-vous pour voir | apibee-client / apibee-secret, client public apibee-public |
| OAuth 1.0a | /api/auth/oauth1/*connectez-vous pour voir | Consumer apibee-consumer-key / apibee-consumer-secret, token apibee-access-token / apibee-token-secret |
Basic et Digest
Un mot de passe absent ou faux renvoie 401 avec un en-tête WWW-Authenticate, qui pousse les navigateurs et les bibliothèques HTTP à demander des identifiants ou à réessayer avec :
$ 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."}Digest demande deux allers-retours. Le premier 401 contient un nonce, et le client répond avec un hachage du mot de passe au lieu du mot de passe lui-même. L’option --digest de curl fait les deux :
$ 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"}Pour tester un client qui doit composer avec des serveurs qui cachent les ressources protégées, /api/auth/basic-hidden/{user}/{pass}connectez-vous pour voir renvoie 404 au lieu de 401 quand les identifiants ne correspondent pas.
Tokens bearer et clés d’API
Les trois points d’accès de clé d’API acceptent la même clé à des endroits différents, ce qui permet de vérifier que votre client la place là où l’API l’attend. Un mauvais token bearer renvoie WWW-Authenticate: Bearer error="invalid_token".
/api/auth/api-key/header401 ou 403
401 signifie que le serveur ne sait pas qui vous êtes : pas d’identifiants, ou de mauvais. 403 signifie qu’il sait exactement qui vous êtes, et que vous n’avez pas le droit. Un client doit les traiter différemment. Après un 401, il peut se reconnecter ou renouveler un token. Après un 403, se reconnecter ne change rien. Les points d’accès de rôles renvoient l’un et l’autre :
$ 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/resourceconnectez-vous pour voir vérifie un scope au lieu d’un rôle. readonly-token n’a que read, il reçoit donc un 403 qui nomme le scope delete manquant.
Signature HMAC des requêtes
Les API de paiement et de webhooks signent souvent les requêtes au lieu d’envoyer un secret. Ici, la signature est le HMAC-SHA256 en hexadécimal minuscule de timestamp + "." + raw body, envoyé dans X-Signature avec le timestamp dans X-Timestamp. Signez exactement les octets que vous envoyez. Resérialiser le JSON après la signature est la façon la plus courante de la casser.
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
}Pour vérifier une signature à la main, le générateur de hachages calcule du HMAC-SHA256 dans le navigateur.
Connexion JWT, expiration et renouvellement
/api/auth/jwt/loginconnectez-vous pour voir renvoie un access token signé valable 15 minutes et un refresh token. Les refresh tokens tournent : chaque renouvellement en renvoie un nouveau, et l’ancien cesse de fonctionner. /api/auth/jwt/expiredconnectez-vous pour voir fournit un token correctement signé et déjà expiré, pour tester le chemin d’expiration sans attendre 15 minutes.
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); // 200Collez l’un de ces tokens dans le décodeur JWT pour voir ses claims et son heure d’expiration.
OAuth 2.0 avec PKCE
Le serveur OAuth prend en charge le grant authorization code avec PKCE, client credentials, password et refresh token, ainsi que l’introspection et la révocation. L’autorisation est accordée automatiquement en tant qu’utilisateur apibee, donc tout le parcours tourne dans un script. Les redirect URI sur localhost ou 127.0.0.1 (n’importe quel port) et le callback de Postman reçoivent une vraie redirection 302.
Authorization code avec 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
}Les deux échecs qu’un client PKCE doit gérer :
# 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
Pour les appels de service à service, sans utilisateur :
$ 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"}Requêtes signées avec OAuth 1.0a
OAuth 1.0a signe chaque requête avec deux secrets, celui du consumer et celui du token, au lieu d’envoyer un token seul. Le serveur de test accepte les signatures HMAC-SHA1, HMAC-SHA256, HMAC-SHA512 et PLAINTEXT. Utilisez l’access token fixe apibee-access-token / apibee-token-secret, qui n’expire jamais, ou suivez le parcours three-legged pour obtenir votre propre token. L’autorisation est accordée automatiquement en tant qu’utilisateur apibee.
POST /api/auth/oauth1/request_tokenconnectez-vous pour voir, signé uniquement avec la clé et le secret du consumer, avecoauth_callbackdéfini sur une URL ou suroob.GET /api/auth/oauth1/authorize?oauth_token=…connectez-vous pour voir redirige vers un callback localhost ou Postman avecoauth_verifier, et le renvoie en JSON pouroob.POST /api/auth/oauth1/access_tokenconnectez-vous pour voir, signé avec le request token et son secret, plusoauth_verifier. L’access token dure une heure.
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.PLAINTEXT, dans l’onglet cURL, n’a besoin d’aucune bibliothèque de signature : la signature est la clé de signature elle-même, consumer_secret&token_secret, avec le & envoyé sous la forme %26. Servez-vous-en pour vérifier les identifiants, puis passez à HMAC-SHA1 pour tester votre code de signature.
Quand une signature ne correspond pas, le 401 contient la chaîne de base de signature construite par le serveur, pour que vous puissiez la comparer à celle de votre client. Les échecs à tester :
# 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.Les règles de signature OAuth 1.0a expliquent où placer les paramètres, la fenêtre du timestamp et chaque code oauth_problem.
Des tests négatifs qui valent la peine
- Sans identifiants : attendez un 401 et un en-tête
WWW-Authenticate. - Mauvais mot de passe, mauvaise clé, token altéré : 401, jamais 500.
- Utilisateur valide sans le rôle ou le scope : 403, et le client ne déconnecte pas l’utilisateur.
- JWT expiré : le client renouvelle une fois, puis relance la requête d’origine.
- Un refresh token utilisé deux fois : la seconde utilisation échoue.
- Un code OAuth utilisé deux fois, ou avec le mauvais verifier :
invalid_grant. - Une signature HMAC calculée sur un autre corps : 401.
- Une requête OAuth 1.0a avec un nonce réutilisé ou une mauvaise signature : 401
nonce_usedousignature_invalid.
Les notions derrière ces tests sont dans la leçon sur l’authentification et l’autorisation (en anglais).