あらゆる API 認証方式のテスト用エンドポイント
Basic、Digest、Bearer トークン、API キー、HMAC 署名、JWT、OAuth 1.0a、PKCE 付き OAuth 2.0 を、資格情報付きで。
例を API に対して最後に実行した日:
ID プロバイダーを用意せずに API クライアントの認証をテストするなら、https://api.testingapis.com/api/authサインインして表示 の公開テスト用エンドポイントを使ってください。方式ごとにエンドポイントが 1 つあります。HTTP Basic、Digest、固定の Bearer トークン、ヘッダー・クエリ・Cookie の API キー、HMAC によるリクエスト署名です。JWT のログインには refresh token とネガティブテスト用の期限切れトークンがあり、OAuth 2.0 サーバーは PKCE と client credentials に対応し、OAuth 1.0a サーバーは署名付きリクエストを検証します。資格情報は固定で 公開されており、下の表と GET /api/authサインインして表示 に載っています。資格情報が間違っていれば正しく 401 を返し、有効なトークンでも必要なロールがなければ 403 を返します。
エンドポイントと資格情報
| 方式 | エンドポイント | 資格情報 |
|---|---|---|
| HTTP Basic | GET /api/auth/basicサインインして表示 | apibee / password123 |
| HTTP Digest(MD5、qop=auth) | GET /api/auth/digestサインインして表示 | apibee / password123 |
| Bearer トークン | GET /api/auth/bearerサインインして表示 | Authorization: Bearer apibee-token-123 |
| API キー(ヘッダー) | GET /api/auth/api-key/headerサインインして表示 | X-API-Key: apibee-key-123 |
| API キー(クエリ) | GET /api/auth/api-key/queryサインインして表示 | ?api_key=apibee-key-123 |
| API キー(Cookie) | GET /api/auth/api-key/cookieサインインして表示 | Cookie: api_key=apibee-key-123 |
| HMAC-SHA256 署名 | POST /api/auth/hmacサインインして表示 | シークレット apibee-hmac-secret |
| ロールとスコープ | GET /api/auth/roles/adminサインインして表示 | admin-token, user-token, readonly-token |
| JWT | POST /api/auth/jwt/loginサインインして表示 | admin / admin123、user / user123 |
| OAuth 2.0 | /api/auth/oauth/*サインインして表示 | apibee-client / apibee-secret、公開クライアント apibee-public |
| OAuth 1.0a | /api/auth/oauth1/*サインインして表示 | コンシューマー apibee-consumer-key / apibee-consumer-secret、トークン apibee-access-token / apibee-token-secret |
Basic と Digest
パスワードがない、または間違っていると、WWW-Authenticate ヘッダー付きの 401 が返ります。ブラウザや HTTP ライブラリが資格情報を求めたり、資格情報を付けてリトライしたりするのは、このヘッダーがあるからです。
$ 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 は 2 往復かかります。最初の 401 に nonce が含まれ、クライアントはパスワードそのものではなくパスワードのハッシュで 応答します。curl の --digest はその両方を行います。
$ 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"}保護されたリソースを隠すサーバーに対応しなければならないクライアントをテストするなら、/api/auth/basic-hidden/{user}/{pass}サインインして表示 は資格情報が一致しないとき 401 ではなく 404 を返します。
Bearer トークンと API キー
3 つの API キーのエンドポイントは、同じキーを別々の場所で受け付けるので、クライアントが API の期待する場所にキーを置いて いるかを確かめられます。間違った Bearer トークンには WWW-Authenticate: Bearer error="invalid_token" が返ります。
/api/auth/api-key/header401 と 403 の違い
401 は、サーバーがあなたが誰かを知らないことを意味します。資格情報がないか、正しくありません。403 は、あなたが誰かをはっきり知ったうえで、許可されていないことを意味します。クライアントは両者を区別して扱うべきです。401 の後はログインし直すかトークンを更新できますが、403 の後にログインし直しても何も変わりません。ロールのエンドポイントは それぞれを返します。
$ 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/resourceサインインして表示 はロールではなくスコープを確認します。readonly-token には read しかないので、足りない delete スコープを名指しした 403 が返ります。
HMAC によるリクエスト署名
決済や Webhook の API は、シークレットを送る代わりにリクエストに署名することがよくあります。ここでの署名は timestamp + "." + raw body の HMAC-SHA256 を小文字の 16 進数にしたもので、X-Signature に入れ、タイムスタンプは X-Timestamp に入れます。送るバイト列そのものに署名してください。署名した後に JSON をシリアライズし直すのが、署名を壊すいちばんよくある原因です。
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
}署名を手で確かめるなら、ハッシュジェネレーターがブラウザで HMAC-SHA256 を計算します。
JWT のログイン、期限切れ、更新
/api/auth/jwt/loginサインインして表示 は 15 分有効な署名付きアクセストークンと refresh token を返します。refresh token はローテーションします。更新するたびに新しいものが返り、古いものは使えなくなります。/api/auth/jwt/expiredサインインして表示 は正しく署名されたうえで期限切れになったトークンを渡すので、15 分待たずに期限切れの経路をテストできます。
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これらのトークンを JWT デコーダーに貼り付ければ、クレームと有効期限を確認できます。
PKCE 付きの OAuth 2.0
OAuth サーバーは、PKCE 付きの authorization code、client credentials、password、refresh token の各グラントに加え、 イントロスペクションと失効に対応しています。認可はユーザー apibee として自動で承認されるので、フロー全体をスクリプトで実行できます。localhost や 127.0.0.1(ポートは任意)のリダイレクト URI と Postman のコールバックには、本物の 302 リダイレクトが返ります。
PKCE 付きの authorization code
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
}PKCE クライアントが扱うべき 2 つの失敗です。
# 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
ユーザーが関わらない、サービス間の呼び出し向けです。
$ 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"}OAuth 1.0a の署名付きリクエスト
OAuth 1.0a は、トークンだけを送るのではなく、コンシューマーシークレットとトークンシークレットの 2 つでリクエストごとに署名します。テストサーバーは HMAC-SHA1、HMAC-SHA256、HMAC-SHA512、PLAINTEXT の署名を受け付けます。期限のない固定のアクセストークン apibee-access-token / apibee-token-secret を使うか、three-legged フローを実行して自分のトークンを取得してください。認可はユーザー apibee として自動で承認されます。
POST /api/auth/oauth1/request_tokenサインインして表示。コンシューマーのキーとシークレットだけで署名し、oauth_callbackには URL かoobを指定します。GET /api/auth/oauth1/authorize?oauth_token=…サインインして表示 は localhost か Postman のコールバックにoauth_verifier付きでリダイレクトし、oobなら JSON で返します。POST /api/auth/oauth1/access_tokenサインインして表示。request token とそのシークレットで署名し、oauth_verifierを付けます。アクセストークンは 1 時間有効です。
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.cURL タブの PLAINTEXT は署名ライブラリが要りません。署名は署名鍵そのもの、つまり consumer_secret&token_secret で、& は %26 として送ります。これで資格情報を確かめてから、HMAC-SHA1 に切り替えて自分の署名コードをテストしてください。
署名が一致しないとき、401 にはサーバーが組み立てた署名ベース文字列が含まれるので、クライアントのものと比較できます。 テストする価値のある失敗です。
# 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.OAuth 1.0a の署名ルールでは、パラメータの置き場所、タイムスタンプの許容幅、 すべての oauth_problem コードを説明しています。
書いておきたいネガティブテスト
- 資格情報なし:401 と
WWW-Authenticateヘッダーを期待する。 - 間違ったパスワード、間違ったキー、改ざんされたトークン:401。500 になってはいけない。
- 有効なユーザーだがロールやスコープがない:403。クライアントはユーザーをログアウトさせない。
- 期限切れの JWT:クライアントは 1 回更新してから、元のリクエストをリトライする。
- 2 回使われた refresh token:2 回目は失敗する。
- 2 回使われた OAuth のコード、または間違った verifier:
invalid_grant。 - 別のボディで計算した HMAC 署名:401。
- nonce を使い回したか署名が間違った OAuth 1.0a のリクエスト:401 の
nonce_usedかsignature_invalid。
これらのテストの背景にある考え方は、認証と認可のレッスン(英語)にあります。