OIDC / OAuth 2.0#
Zeroword は Environment ごとに Canonical Issuer を持つ。アプリケーションは discovery から endpoint と署名鍵を取得し、Authorization Code + PKCE で認証する。
SPA は Browser SDK (@auth-platform/browser)、BFF は Server SDK (@auth-platform/server)、React Native は React Native SDK (@auth-platform/react-native) の案内も参照する。Hosted Login の画面挙動は Hosted Login、headless 認証は Public Authentication API、Client 登録は Management API、環境モデルは 概念とテナントモデル を参照する。
Issuer と discovery#
Issuer は必ず Environment ID を含む。root の /.well-known/openid-configuration ではなく、次の環境ごとの URL を使う。
export AUTH_ISSUER="https://id.zeroword.smartcrab.ai/e/<environmentId>"
curl -sS "$AUTH_ISSUER/.well-known/openid-configuration"
Discovery の issuer は AUTH_ISSUER と完全一致する。Discovery が返す全 endpoint もこの issuer の下にある。
| Metadata field | Endpoint |
|---|---|
authorization_endpoint |
GET /oauth2/authorize |
token_endpoint |
POST /oauth2/token |
userinfo_endpoint |
GET /oauth2/userinfo |
jwks_uri |
GET /oauth2/jwks |
revocation_endpoint |
POST /oauth2/revoke |
introspection_endpoint |
POST /oauth2/introspect |
end_session_endpoint |
GET または POST /oauth2/logout |
Discovery が返す主な capability は次のとおりである。
| Field | Values |
|---|---|
response_types_supported |
code |
grant_types_supported |
authorization_code, refresh_token |
code_challenge_methods_supported |
S256 |
token_endpoint_auth_methods_supported |
client_secret_basic, client_secret_post, none |
scopes_supported |
openid, profile, email, offline_access |
subject_types_supported |
public |
id_token_signing_alg_values_supported |
ES256 |
claims_supported |
iss, sub, aud, exp, iat, jti, sid, client_id, scope, amr, acr, auth_time, sv, nonce, name, email, email_verified, locale, updated_at |
GET $AUTH_ISSUER/oauth2/jwks は {"keys":[...]} を返す。各 ES256 公開鍵は kty: EC, crv: P-256, x, y, kid, alg: ES256, use: sig を持つ。JWKS は Cache-Control: public, max-age=60 でキャッシュできる。署名鍵は Environment ごとに異なるため、他 Environment の issuer/key を流用しない。
Endpoint 一覧#
全 endpoint は issuer の下にあり、<environmentId> を含む。AUTH_ISSUER を discovery の issuer と一致させる。
| Method | Path suffix | 用途 / 認証 |
|---|---|---|
GET |
/.well-known/openid-configuration |
環境別 discovery。認証不要 |
GET |
/oauth2/authorize |
Authorization Code フロー開始。ブラウザーを Hosted Login へ redirect |
POST |
/oauth2/token |
Authorization Code と Refresh Token grant |
GET |
/oauth2/userinfo |
Authorization: Bearer <access_token> が必要 |
GET |
/oauth2/jwks |
JWT 検証用公開鍵。認証不要 |
POST |
/oauth2/introspect |
web_bff の client secret、または introspect Scope を持つ Management API key (X-Management-API-Key) が必要 |
POST |
/oauth2/revoke |
refresh token の family 失効。client authentication は要求されない |
GET, POST |
/oauth2/logout |
RP-Initiated Logout。logout 用 redirect URI を検証して redirect |
POST endpoint は application/x-www-form-urlencoded を受け付ける。JSON も受け付ける endpoint ではあるが、OAuth token/introspection/revocation の例では form encoding を使う。resource は form body で同じ名前を複数回送信できる。
Client 認証方式#
Client 作成時の token_endpoint_auth_method に設定する。Public client に secret は発行されない。
| Client type | Token endpoint authentication |
|---|---|
web_spa |
none。client_id のみを送信し、PKCE を必ず使う |
react_native |
none。client_id のみを送信し、PKCE を必ず使う |
web_bff |
client_secret_basic または client_secret_post。secret と PKCE の両方を使う |
client_secret_basic は HTTP Basic、client_secret_post は form body の client_id と client_secret で送る。どちらも token endpoint で利用できる。Client secret はサーバー側だけに保存し、SPA やアプリバイナリへ埋め込まない。
Authorization Code + PKCE#
対応する interactive flow は Authorization Code Grant のみである。BFF、SPA、React Native のすべてで PKCE S256 が必須。plain、Implicit、Resource Owner Password Credentials、Device Authorization、Dynamic Client Registration はサポートしない。
GET /oauth2/authorize の query parameter:
| Parameter | 要件 |
|---|---|
response_type |
必須。code のみ |
client_id |
必須。active client ID |
redirect_uri |
必須。Client に登録した URI と byte-for-byte 一致 |
scope |
必須。space-delimited。Client に許可された scope のみ |
state |
必須。1–512 文字。callback で client 側が元の値と照合する |
code_challenge |
必須。SHA-256 challenge の base64url、43 文字 |
code_challenge_method |
必須。S256 のみ |
nonce |
openid scope を要求する場合に必須。1–512 文字。ID token の nonce と照合する |
resource |
任意。Client 登録済み HTTPS API audience を最大 20 個指定できる。同じ resource parameter を繰り返す |
prompt、max_age、login_hint は認証ポリシーに使われず無視される。未対応の response_type / plain、未知の scope token、query parameter の形式不正・必須値欠落は invalid_request になる。構文上有効でも openid に nonce がない場合や、Client status / 許可 scope / 登録 redirect URI / resource の業務検査に失敗した場合は Hosted Login の Interaction Domain error page へ redirect され、Client callback には code を返さない。
例では、SPA client の verifier/challenge を RFC 7636 に従って事前生成する。Verifier は 43–128 個の unreserved characters、challenge は BASE64URL(SHA256(verifier)) とする。
curl -iG "$AUTH_ISSUER/oauth2/authorize" \
--data-urlencode 'response_type=code' \
--data-urlencode "client_id=$AUTH_CLIENT_ID" \
--data-urlencode "redirect_uri=$AUTH_REDIRECT_URI" \
--data-urlencode 'scope=openid profile email offline_access' \
--data-urlencode "state=$STATE" \
--data-urlencode "nonce=$NONCE" \
--data-urlencode "code_challenge=$CODE_CHALLENGE" \
--data-urlencode 'code_challenge_method=S256'
Browser は Hosted Login へ redirect される。認証後に返された one-time code と同じ redirect_uri、保存していた code_verifier で token exchange する。Verifier は認証開始前に生成して callback まで保持する。Authorization code の有効期限は 60 秒。
curl -sS "$AUTH_ISSUER/oauth2/token" \
-H 'content-type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode "client_id=$AUTH_CLIENT_ID" \
--data-urlencode "code=$CODE" \
--data-urlencode "redirect_uri=$AUTH_REDIRECT_URI" \
--data-urlencode "code_verifier=$CODE_VERIFIER"
web_bff は上の client_id を body から外し、次のいずれかで認証する。
# client_secret_basic
curl -sS "$AUTH_ISSUER/oauth2/token" \
-u "$AUTH_CLIENT_ID:$AUTH_CLIENT_SECRET" \
-H 'content-type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode "code=$CODE" \
--data-urlencode "redirect_uri=$AUTH_REDIRECT_URI" \
--data-urlencode "code_verifier=$CODE_VERIFIER"
# client_secret_post
curl -sS "$AUTH_ISSUER/oauth2/token" \
-H 'content-type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode "client_id=$AUTH_CLIENT_ID" \
--data-urlencode "client_secret=$AUTH_CLIENT_SECRET" \
--data-urlencode "code=$CODE" \
--data-urlencode "redirect_uri=$AUTH_REDIRECT_URI" \
--data-urlencode "code_verifier=$CODE_VERIFIER"
成功時は JSON で access_token, token_type: "Bearer", expires_in, scope を返す。openid が付与された場合は id_token、offline_access が付与された場合は refresh_token も返る。
Token endpoint request は grant_type で次の形に分かれる。resource は任意で、複数値は form body の同名 field を繰り返す。JSON body を使う場合は array とする。
grant_type |
Required fields | Optional fields |
|---|---|---|
authorization_code |
code, redirect_uri, code_verifier |
resource |
refresh_token |
refresh_token |
scope, resource |
Public client は client_id を body に加える。BFF は client_secret_basic なら Basic header を、client_secret_post なら body の client_id, client_secret を使う。
Scope、Refresh Token、TTL#
| Scope | 効果 |
|---|---|
openid |
ID token を発行し、UserInfo を利用可能にする。authorize request では nonce も必須 |
profile |
ID token / UserInfo の name, locale, updated_at を許可する |
email |
ID token / UserInfo の email, email_verified を許可する |
offline_access |
Authorization Code exchange 時に Refresh Token を発行する |
未知の scope、Client に未許可の scope は拒否される。email は Access Token に含まれない。
| Value | Lifetime |
|---|---|
| Authorization code | 60 秒 |
| Access token | 300 秒(token response の expires_in: 300) |
| ID token | 300 秒 |
| Refresh token idle expiry | 最終利用から 30 日 |
| Refresh token absolute expiry | 発行から 90 日 |
| Login session idle expiry | 7 日 |
| Login session absolute expiry | 30 日 |
Refresh Token は rt_<family-id>.<generation>.<secret> 形式の bearer credential で、毎回の成功した refresh で世代を進め、新しい refresh token を返す。旧 token を安全に破棄し、保存値を新しい token で置換する。scope と resource は元の grant の部分集合にだけ狭められる。省略した値は元の grant を維持する。
curl -sS "$AUTH_ISSUER/oauth2/token" \
-H 'content-type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode "client_id=$AUTH_CLIENT_ID" \
--data-urlencode "refresh_token=$AUTH_REFRESH_TOKEN" \
--data-urlencode 'scope=openid profile' \
--data-urlencode 'resource=https://api.customer.example'
web_bff は同じ Basic または Post client authentication を使う。直前世代の concurrent retry 用に 5 秒の reuse window がある。その期間の同一 token の再送には勝者が生成した token response を返す。5 秒を過ぎて旧 generation を再使用すると、Refresh Token family 全体が失効し、現行 token も invalid_grant になる。盗難・再送時は最新 token だけを保有する。
JWT header、claims、audience#
Access Token と ID Token は Environment の署名鍵で ES256 署名され、header に kid を含む。JWT header の alg を信頼せず、ES256 固定で検証する。
| Token | Header typ |
主な claims |
|---|---|---|
| Access Token | at+jwt |
iss, sub, aud, exp, iat, jti, sid, client_id, scope, amr, acr, auth_time, sv |
| ID Token | JWT |
iss, sub, aud, exp, iat, sid, auth_time, amr, acr, nonce。profile / email scope に応じ name, locale, updated_at, email, email_verified |
主な claim の意味は次のとおりである。
| Claim | 意味 |
|---|---|
iss |
Environment の Canonical Issuer。Discovery の issuer と完全一致 |
sub |
Environment 内の user ID。ID/Access Token で同じ値 |
aud |
Access Token は要求した API resource、ID Token は client ID。単一 audience は string、複数は array |
client_id |
Access Token を発行した client ID。ID Token では aud が client ID を示す |
sid |
Login session ID。ID/Access Token の同じ session を結び付ける |
scope |
Access Token に付与された space-delimited scope |
amr |
認証方法。email_otp、passkey、social。Refresh Token で再発行した token は refresh_token |
acr |
認証保証レベル。Passkey は urn:example-auth:loa:phishing-resistant、Email OTP / social login / Refresh Token は urn:example-auth:loa:basic |
auth_time |
元のユーザー認証時刻(Unix seconds)。Refresh で再発行時刻に更新しない |
sv |
User security version。security version 更新後の token は UserInfo / introspection で inactive になる |
jti |
Access Token ごとの JWT ID |
iat |
JWT 発行時刻(Unix seconds) |
exp |
JWT 失効時刻(Unix seconds)。exp - iat は access / ID token で 300 秒 |
nonce |
authorize request の nonce。ID Token に echo される |
name, locale, updated_at |
profile scope がある場合だけ ID Token / UserInfo に含まれる。updated_at は Unix seconds |
email, email_verified |
email scope がある場合だけ ID Token / UserInfo に含まれる |
azp は発行 token に含まれず、claims_supported にもない。Access Token を API で利用する場合は署名だけで許可せず、期待する iss と API 固有 aud、期限を検証する。openid を要求していない token には ID Token がない。
Resource / audience binding#
API resource は Client の allowed_resources に HTTPS URL として登録する。Authorize request では登録済み resource を指定する。未指定の場合、Access Token の audience は issuer URL となる。指定した場合はその resource URL が Access Token の aud になる。複数 audience は同じ query/body field を繰り返して送る。
Authorization Code exchange の resource は authorize transaction に保存された集合を狭められるだけである。Refresh 時にも元の family にない resource を加えられない。拡張要求は invalid_scope で拒否される。API は必ず自分の resource URL を aud で確認する。
UserInfo#
GET $AUTH_ISSUER/oauth2/userinfo に Authorization: Bearer <access_token> を付ける。Access Token は openid scope を含まなければならず、UserInfo は常に sub を返す。profile と email claims はそれぞれ該当 scope が付与された場合だけ返る。
curl -sS "$AUTH_ISSUER/oauth2/userinfo" \
-H "authorization: Bearer $AUTH_ACCESS_TOKEN"
Response の例:
{
"sub": "<userId>",
"email": "<email>",
"email_verified": true,
"updated_at": 1790664104
}
無効・期限切れ・logout 済み Access Token は 401 invalid_token Problem Details になる。openid がない場合は 400 invalid_request になる。UserInfo は security version と sid の live session も確認する。
Introspection#
POST $AUTH_ISSUER/oauth2/introspect の token は必須。token_type_hint は access_token または refresh_token を指定できる。呼び出し元は web_bff Client の client_secret_basic / client_secret_post 認証、または introspect Scope を付与された Management API key (X-Management-API-Key: <secret>) を使う。environment_id を設定して Environment に固定した key は、issuer の <environmentId> と environment_id が一致する場合に有効である。environment_id を省略した Workspace-scoped key は同じ Workspace の Environment で使える。管理キーを発行するには、Management API の scope introspect を指定する。認証・Scope・Environment が一致しない呼び出し元には token の存在を明かさず {"active":false} を返す。Introspection は署名・期限・issuer が有効な Access Token を active と判定し、Refresh Token は active と判定しない。
curl -sS "$AUTH_ISSUER/oauth2/introspect" \
-u "$AUTH_CLIENT_ID:$AUTH_CLIENT_SECRET" \
-H 'content-type: application/x-www-form-urlencoded' \
--data-urlencode "token=$AUTH_ACCESS_TOKEN" \
--data-urlencode 'token_type_hint=access_token'
active: true の response は scope, client_id, token_type, exp, iat, sub, aud, iss, jti などを含む。検証できない token、inactive token、権限のない呼び出し元には token の存在を明かさず {"active":false} を返す。Introspection は User の block 状態、security version、session 失効も確認する。
Revocation#
POST $AUTH_ISSUER/oauth2/revoke は token と任意の token_type_hint (access_token / refresh_token) を受け付ける。token_type_hint に関わらず refresh-token wire format を判別して処理する。Refresh Token の revoke は family 全体を失効する。存在しない token、形式不正 token、Access Token は 200 {} となるため、token の存在を response から推測できない。Access Token 自体は denylist に追加されないため、Access Token の revoke は即時失効させない。
curl -i "$AUTH_ISSUER/oauth2/revoke" \
-H 'content-type: application/x-www-form-urlencoded' \
--data-urlencode "token=$AUTH_REFRESH_TOKEN" \
--data-urlencode 'token_type_hint=refresh_token'
Revoke endpoint は client secret / Management API key を要求しない。Refresh Token bearer を保持する client は revoke できる。Storage failure 時は server_error を返す。
RP-Initiated Logout#
Discovery の end_session_endpoint (GET / POST /oauth2/logout) を使う。id_token_hint は任意だが、渡すとその ID Token の sid session を終了する。期限切れ ID Token も logout hint として受け付ける。post_logout_redirect_uri は Client の post_logout_redirect_uris に登録済みの URI と byte-for-byte 一致しなければならず、必須である。client_id は省略時に ID Token の aud から解決される。指定した state は redirect にそのまま返る。
curl -iG "$AUTH_ISSUER/oauth2/logout" \
--data-urlencode "id_token_hint=$AUTH_ID_TOKEN" \
--data-urlencode "post_logout_redirect_uri=$AUTH_POST_LOGOUT_REDIRECT_URI" \
--data-urlencode "state=$LOGOUT_STATE"
成功時は 302 redirect となり、登録済み URI の既存 query を保ったまま state が追加される。post_logout_redirect_uri が必須のため、成功時は常に redirect する。id_token_hint、client_id、または redirect URI が不正な場合は 400 invalid_request となり、未登録 redirect へは遷移しない。Logout で session が終了しても Access Token の署名・exp は変わらないが、UserInfo / introspection はその token を inactive とする。
Redirect URI#
Login の redirect_uri は登録値と byte-for-byte 完全一致させる。scheme、host、port、path、query の正規化や trailing slash の差分は一致扱いにならない。Fragment (#...) と wildcard (*) は禁止する。
| Client type | 許可される redirect |
|---|---|
web_bff, web_spa |
HTTPS。HTTP は hostname が localhost または 127.0.0.1 の場合だけ許可 |
react_native |
HTTPS Universal Link / App Link、または reverse-DNS custom scheme。例: com.customer.app:/oauth/callback |
IPv6 loopback [::1] は localhost の例外に含まれない。React Native で HTTP URI と reverse-DNS でない custom scheme は使えない。Login redirect と post-logout redirect はそれぞれ client の redirect_uris / post_logout_redirect_uris に事前登録する。Callback では state を元の値と比較し、認証レスポンスを処理する前に CSRF を防ぐ。
OAuth error response#
OAuth endpoint の JSON error は { "error": "...", "error_description": "..." } の形で返る。主な error と発生条件は次のとおりである。
| Error | Typical status | 発生例 |
|---|---|---|
invalid_request |
400 | malformed authorize/token request、未対応の response_type / PKCE method / grant_type |
invalid_client |
401 | BFF secret 欠落・不正、public client で secret を送信 |
invalid_grant |
400 | 無効・期限切れ・使用済み code、期限切れ / 失効済み / replay 済み refresh token |
invalid_scope |
400 | Refresh 時に元の grant を超える scope/resource を要求 |
access_denied |
403 | block 済み user |
server_error |
503 | endpoint の repository、key、storage などの server-side failure |
Response contract には unsupported_grant_type と temporarily_unavailable も定義されるが、未対応 grant_type は invalid_request を返す。/userinfo の bearer failure は OAuth JSON ではなく 401 invalid_token Problem Details。/introspect の不正 token / 不十分な caller は 200 {"active":false}、/revoke は通常 200 {} を返す。