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émaPoint d’accèsIdentifiants
HTTP BasicGET /api/auth/basicconnectez-vous pour voirapibee / password123
HTTP Digest (MD5, qop=auth)GET /api/auth/digestconnectez-vous pour voirapibee / password123
Token bearerGET /api/auth/bearerconnectez-vous pour voirAuthorization: Bearer apibee-token-123
Clé d’API, en-têteGET /api/auth/api-key/headerconnectez-vous pour voirX-API-Key: apibee-key-123
Clé d’API, queryGET /api/auth/api-key/queryconnectez-vous pour voir?api_key=apibee-key-123
Clé d’API, cookieGET /api/auth/api-key/cookieconnectez-vous pour voirCookie: api_key=apibee-key-123
Signature HMAC-SHA256POST /api/auth/hmacconnectez-vous pour voirSecret apibee-hmac-secret
Rôles et scopesGET /api/auth/roles/adminconnectez-vous pour voiradmin-token, user-token, readonly-token
JWTPOST /api/auth/jwt/loginconnectez-vous pour voiradmin / admin123, user / user123
OAuth 2.0/api/auth/oauth/*connectez-vous pour voirapibee-client / apibee-secret, client public apibee-public
OAuth 1.0a/api/auth/oauth1/*connectez-vous pour voirConsumer 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 :

Terminal
$ 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."}
Cet exemple utilise des points d’accès réservés aux membres connectés.Se connecterCréer un compte gratuit

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 :

Terminal
$ 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"}
Cet exemple utilise des points d’accès réservés aux membres connectés.Se connecterCréer un compte gratuit

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".

GET/api/auth/api-key/header
Cette requête utilise un point d’accès réservé aux membres connectés.Se connecterCréer un compte gratuit

401 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 :

Terminal
$ 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"]}
Cet exemple utilise des points d’accès réservés aux membres connectés.Se connecterCréer un compte gratuit

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.

JavaScript
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());
Cet exemple utilise des points d’accès réservés aux membres connectés.Se connecterCréer un compte gratuit
Sortie
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.

JavaScript
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); // 200
Cet exemple utilise des points d’accès réservés aux membres connectés.Se connecterCréer un compte gratuit

Collez 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

JavaScript
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());
Cet exemple utilise des points d’accès réservés aux membres connectés.Se connecterCréer un compte gratuit
Sortie
200 {
  sub: 'apibee',
  name: 'TestingAPIs Tester',
  preferred_username: 'apibee',
  email: null,
  email_verified: null
}

Les deux échecs qu’un client PKCE doit gérer :

Text
# 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 :

Terminal
$ 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"}
Cet exemple utilise des points d’accès réservés aux membres connectés.Se connecterCréer un compte gratuit

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.

  1. POST /api/auth/oauth1/request_tokenconnectez-vous pour voir, signé uniquement avec la clé et le secret du consumer, avec oauth_callback défini sur une URL ou sur oob.
  2. GET /api/auth/oauth1/authorize?oauth_token=…connectez-vous pour voir redirige vers un callback localhost ou Postman avec oauth_verifier, et le renvoie en JSON pour oob.
  3. POST /api/auth/oauth1/access_tokenconnectez-vous pour voir, signé avec le request token et son secret, plus oauth_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 :

Text
# 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_used ou signature_invalid.

Les notions derrière ces tests sont dans la leçon sur l’authentification et l’autorisation (en anglais).