Test-Endpunkte für jedes API-Authentifizierungsverfahren
Basic, Digest, Bearer-Tokens, API-Schlüssel, HMAC-Signaturen, JWT, OAuth 1.0a und OAuth 2.0 mit PKCE, mit Zugangsdaten.
Beispiele zuletzt am gegen die API ausgeführt
Um die Authentifizierung eines API-Clients zu testen, ohne einen Identity Provider aufzusetzen, nutze die öffentlichen Test-Endpunkte unter https://api.testingapis.com/api/authanmelden zum Anzeigen. Es gibt einen Endpunkt pro Verfahren: HTTP Basic, Digest, feste Bearer-Tokens, API-Schlüssel in Header, Query oder Cookie sowie HMAC-Signaturen für Requests. Ein JWT-Login hat Refresh-Tokens und ein abgelaufenes Token für Negativtests, ein OAuth-2.0-Server unterstützt PKCE und Client Credentials, und ein OAuth-1.0a-Server prüft signierte Requests. Die Zugangsdaten sind fest und öffentlich, sie stehen unten und unter GET /api/authanmelden zum Anzeigen. Falsche Zugangsdaten liefern ein ordentliches 401, ein gültiges Token ohne die richtige Rolle ein 403.
Endpunkte und Zugangsdaten
| Verfahren | Endpunkt | Zugangsdaten |
|---|---|---|
| HTTP Basic | GET /api/auth/basicanmelden zum Anzeigen | apibee / password123 |
| HTTP Digest (MD5, qop=auth) | GET /api/auth/digestanmelden zum Anzeigen | apibee / password123 |
| Bearer-Token | GET /api/auth/beareranmelden zum Anzeigen | Authorization: Bearer apibee-token-123 |
| API-Schlüssel, Header | GET /api/auth/api-key/headeranmelden zum Anzeigen | X-API-Key: apibee-key-123 |
| API-Schlüssel, Query | GET /api/auth/api-key/queryanmelden zum Anzeigen | ?api_key=apibee-key-123 |
| API-Schlüssel, Cookie | GET /api/auth/api-key/cookieanmelden zum Anzeigen | Cookie: api_key=apibee-key-123 |
| HMAC-SHA256-Signatur | POST /api/auth/hmacanmelden zum Anzeigen | Geheimnis apibee-hmac-secret |
| Rollen und Scopes | GET /api/auth/roles/adminanmelden zum Anzeigen | admin-token, user-token, readonly-token |
| JWT | POST /api/auth/jwt/loginanmelden zum Anzeigen | admin / admin123, user / user123 |
| OAuth 2.0 | /api/auth/oauth/*anmelden zum Anzeigen | apibee-client / apibee-secret, öffentlicher Client apibee-public |
| OAuth 1.0a | /api/auth/oauth1/*anmelden zum Anzeigen | Consumer apibee-consumer-key / apibee-consumer-secret, Token apibee-access-token / apibee-token-secret |
Basic und Digest
Ein fehlendes oder falsches Passwort liefert 401 mit einem Header WWW-Authenticate, und genau der bringt Browser und HTTP-Bibliotheken dazu, nach Zugangsdaten zu fragen oder es damit erneut zu versuchen:
$ 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 braucht zwei Durchläufe. Das erste 401 enthält eine Nonce, und der Client antwortet mit einem Hash des Passworts statt mit dem Passwort selbst. --digest von curl erledigt beides:
$ 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"}Um einen Client zu testen, der mit Servern zurechtkommen muss, die geschützte Ressourcen verbergen, liefert /api/auth/basic-hidden/{user}/{pass}anmelden zum Anzeigen bei falschen Zugangsdaten 404 statt 401.
Bearer-Tokens und API-Schlüssel
Die drei Endpunkte für API-Schlüssel nehmen denselben Schlüssel an verschiedenen Stellen an, du kannst also prüfen, ob dein Client ihn dort ablegt, wo die API ihn erwartet. Ein falsches Bearer-Token liefert WWW-Authenticate: Bearer error="invalid_token".
/api/auth/api-key/header401 oder 403
401 heißt, der Server weiß nicht, wer du bist: keine Zugangsdaten oder falsche. 403 heißt, er weiß genau, wer du bist, und du darfst es nicht. Ein Client sollte beide unterschiedlich behandeln. Nach einem 401 kann er sich neu anmelden oder ein Token erneuern. Nach einem 403 ändert eine neue Anmeldung nichts. Die Rollen-Endpunkte liefern beides:
$ 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/resourceanmelden zum Anzeigen prüft einen Scope statt einer Rolle. readonly-token hat nur read und bekommt deshalb ein 403, das den fehlenden Scope delete nennt.
HMAC-Signaturen für Requests
Zahlungs- und Webhook-APIs signieren Requests oft, statt ein Geheimnis mitzuschicken. Hier ist die Signatur der kleingeschriebene hexadezimale HMAC-SHA256 von timestamp + "." + raw body, gesendet in X-Signature, mit dem Zeitstempel in X-Timestamp. Signiere genau die Bytes, die du sendest. Das JSON nach dem Signieren neu zu serialisieren ist der häufigste Weg, die Signatur kaputtzumachen.
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
}Um eine Signatur von Hand zu prüfen, berechnet der Hash-Generator HMAC-SHA256 im Browser.
JWT-Login, Ablauf und Erneuerung
/api/auth/jwt/loginanmelden zum Anzeigen liefert ein signiertes Access Token, das 15 Minuten gilt, und ein Refresh-Token. Refresh-Tokens rotieren: Jede Erneuerung liefert ein neues, und das alte funktioniert nicht mehr. /api/auth/jwt/expiredanmelden zum Anzeigen gibt ein korrekt signiertes, bereits abgelaufenes Token heraus, damit du den Ablaufpfad testen kannst, ohne 15 Minuten zu warten.
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); // 200Füge eines dieser Tokens in den JWT-Decoder ein, um seine Claims und seine Ablaufzeit zu sehen.
OAuth 2.0 mit PKCE
Der OAuth-Server unterstützt den Authorization-Code-Grant mit PKCE, Client Credentials, Password- und Refresh-Token-Grants sowie Introspection und Revocation. Die Autorisierung wird automatisch als Benutzer apibee genehmigt, der ganze Ablauf läuft also in einem Skript. Redirect-URIs auf localhost oder 127.0.0.1 (beliebiger Port) und der Callback von Postman bekommen eine echte 302-Weiterleitung.
Authorization Code mit 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
}Die zwei Fehler, mit denen ein PKCE-Client umgehen sollte:
# 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
Für Aufrufe zwischen Diensten, ohne beteiligten Benutzer:
$ 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"}Signierte Requests mit OAuth 1.0a
OAuth 1.0a signiert jeden Request mit zwei Geheimnissen, dem Consumer Secret und dem Token Secret, statt ein Token allein zu senden. Der Testserver nimmt Signaturen mit HMAC-SHA1, HMAC-SHA256, HMAC-SHA512 und PLAINTEXT an. Nutze das feste Access Token apibee-access-token / apibee-token-secret, das nie abläuft, oder durchlaufe den Three-Legged-Ablauf für ein eigenes Token. Die Autorisierung wird automatisch als Benutzer apibee genehmigt.
POST /api/auth/oauth1/request_tokenanmelden zum Anzeigen, nur mit Consumer Key und Secret signiert, mitoauth_callbackauf eine URL oderoobgesetzt.GET /api/auth/oauth1/authorize?oauth_token=…anmelden zum Anzeigen leitet mitoauth_verifierauf einen localhost- oder Postman-Callback weiter und gibt ihn beioobals JSON zurück.POST /api/auth/oauth1/access_tokenanmelden zum Anzeigen, signiert mit dem Request Token und seinem Secret, plusoauth_verifier. Das Access Token gilt eine Stunde.
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 im cURL-Tab braucht keine Signaturbibliothek: Die Signatur ist der Signaturschlüssel selbst, consumer_secret&token_secret, wobei das & als %26 gesendet wird. Prüf damit die Zugangsdaten und wechsle dann zu HMAC-SHA1, um deinen Signaturcode zu testen.
Passt eine Signatur nicht, enthält das 401 den Signature Base String, den der Server gebaut hat, damit du ihn mit dem deines Clients vergleichen kannst. Die Fehler, die es zu testen lohnt:
# 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.Die Signaturregeln von OAuth 1.0a erklären, wo die Parameter hingehören, das Zeitfenster für den Zeitstempel und jeden Code von oauth_problem.
Negativtests, die sich lohnen
- Keine Zugangsdaten: 401 und ein Header
WWW-Authenticateerwarten. - Falsches Passwort, falscher Schlüssel, manipuliertes Token: 401, niemals 500.
- Gültiger Benutzer, fehlende Rolle oder fehlender Scope: 403, und der Client meldet den Benutzer nicht ab.
- Abgelaufenes JWT: Der Client erneuert einmal und wiederholt dann den ursprünglichen Request.
- Ein zweimal genutztes Refresh-Token: Die zweite Nutzung scheitert.
- Ein zweimal genutzter OAuth-Code oder einer mit falschem Verifier:
invalid_grant. - Eine HMAC-Signatur über einen anderen Body: 401.
- Ein OAuth-1.0a-Request mit wiederverwendeter Nonce oder falscher Signatur: 401
nonce_usedodersignature_invalid.
Die Konzepte hinter diesen Tests stehen in der Lektion zu Authentifizierung und Autorisierung (auf Englisch).