Redirect URI
Redirect URI は、認可エンドポイント(/auth。OP を /oidc に mount した場合は /oidc/auth)の処理が終わったあとに、OP がユーザを送り戻す URL です。成功時は ?code=...&state=...、失敗時は ?error=...&state=... が付きます。RP はクライアントの作成時に 1 つ以上を登録しておき、OP はすべての authorize リクエストの redirect_uri クエリパラメータを登録値と比較します。一致しなければ、ユーザをどこにも送り出す前にリクエストを拒否します。
30 秒でつかむ要点
redirect_uriは登録値と byte 単位で完全一致 しなければなりません。/cbと/cb/は別の URI です。- 緩和は RFC 8252 の loopback ポートだけで、適用されるのは public client と native client に限られます。
- OP は不正なリクエストを、どこかに redirect する 前 に拒否します。redirect 自体が攻撃ベクトルだからです。
このページで触れる仕様
- RFC 6749 — OAuth 2.0 §3.1.2.3(redirect URI 登録と一致判定)
- RFC 7591 — Dynamic Client Registration
- RFC 8252 — OAuth 2.0 for Native Apps(§7.3 loopback、§7.1 custom URI scheme、§8.3 DNS rebinding)
- RFC 9700 — OAuth 2.0 Security Best Current Practice(§4.1 redirect URI handling)
- OpenID Connect Registration 1.0 — §2(
redirect_uris、application_type)
なぜ完全一致なのか
redirect_uri のワイルドカードや前方一致は、OAuth の黎明期に一群の脆弱性を生み出しました。登録された prefix の下ならどの URL にも redirect する OP に対して、攻撃者は次の手順を踏めます。
- prefix の下の URL を 1 つ手に入れる。自分のサブドメインやパスを登録するか、prefix が含む下流リソースを 1 つ侵害する。
- その URL を
redirect_uriにして authorize リクエストを開始する。 - redirect で認可コードを受け取る。
- コードをトークンに交換する。
RFC 9700 §4.1 はこれを閉じています。
RFC 9700 §4.1(訳) authorization server は
redirect_uriパラメータを登録 URI と単純な文字列比較で照合しなければならない。パターンマッチ、ワイルドカード、部分一致を使ってはならない。
本ライブラリはこれを文字どおり実装します。https://app.example.com/cb を登録したなら、リクエストはこの文字列と 1 バイトも違ってはなりません。
拒否される行はいずれも登録値と 1〜2 文字しか違いませんが、OP はどれも正規化しません。2 つの文字列を比較して、そこで終わりです。唯一の例外が後述の loopback の緩和で、ポート番号だけが変わってもよくなります。
本ライブラリが拒否する形
登録時は、DCR でも op.WithStaticClients でも同じ validator が走ります。redirect_uris は空でなく、各エントリは url.Parse が通る空でない絶対 URL で、fragment を含んではいけません。fragment は redirect で意味を持たず、仕様でも禁止されているためです。scheme のルールは application_type によって変わります。
application_type | 受け付ける scheme |
|---|---|
web(既定) | host を持ち userinfo を含まない https。加えて loopback の IP リテラル 127.0.0.1 と [::1] 上の http。http://localhost は op.WithAllowLocalhostRedirectURIs() を設定した場合だけ。custom scheme は拒否。 |
native | https(claimed link)、上記の loopback http、com.example.app:/cb のような private-use scheme。 |
private-use scheme には dot を含める必要があります(reverse-DNS 形式)。RFC 8252 §7.1 が、myapp: のような短い名前はアプリ間で衝突しやすいと警告しているためです。javascript:、data:、file:、ftp:、ws:、wss: は一律に拒否します。
authorize 時には、登録リストに対する文字列比較(と後述の loopback ルール)で判定します。どれにも一致しないリクエストには、ブラウザをどこかに redirect する 前 に OP 自身がエラー応答を返します。ユーザが目にするのは攻撃者制御の URL ではなく、サーバ側のエラー画面です。
Loopback(RFC 8252)
CLI ツールは OS が実行時に割り当てるポートで待ち受けるため、固定の redirect を登録できません。今日は http://127.0.0.1:54321/cb、明日は http://127.0.0.1:62114/cb になるからです。RFC 8252 §7.3 はここに例外を設けています。
RFC 8252 §7.3(訳) loopback redirect を使うネイティブアプリケーションは、登録時のポートと異なっていても、リクエスト時にポートを指定できなければならない。
本ライブラリがこの例外を認めるのは public client と application_type=native のクライアントだけ で、DNS rebinding(RFC 8252 §8.3)への対策として次のように絞っています。
| 登録された redirect URI | 挙動 |
|---|---|
127.0.0.1 または [::1] 上の http | public クライアントとネイティブクライアントは、authorize 時に任意のポートを使えます。scheme・host・path・query は引き続き完全一致が必要です。confidential な Web クライアントはポートを含めて完全一致です。 |
テキストの localhost 上の http | op.WithAllowLocalhostRedirectURIs() を設定しない限り、登録時に拒否されます(web も native も同じです)。localhost は DNS で解決され、乗っ取られる可能性があるためです。許可した場合は、IP リテラルと同じ任意ポートのルールに従います。 |
loopback host 上の https | ポートの緩和はありません。認証局は 127.0.0.1 に証明書を発行しないので、この組み合わせはまれです。 |
localhost の登録が 2 番目の判定に届くのは、登録時に op.WithAllowLocalhostRedirectURIs() が通した場合に限られます。localhost が特別扱いされる理由は DNS rebinding
テキストの localhost は、OS が DNS で解決する名前です。localhost の DNS を汚染できる攻撃者(乗っ取られた LAN 内リゾルバ、悪意あるリゾルバ、敵対的な WiFi)は、OP が loopback に送ったつもりのユーザを、攻撃者の制御する host に着地させられます。IP リテラルの 127.0.0.1 と [::1] は DNS を使わないので既定の loopback 形式になり、localhost だけが明示的なオプトインを要求されます。根拠は設計判断 — RFC 8252 loopback handlingを参照してください。
クライアントの登録方法
redirect URI が OP に届く経路は 2 つあり、どちらも同じ validator を通ります。
- 静的シード: 構築時に
op.WithStaticClients(seeds...)を渡し、各シードにRedirectURIsを列挙します(クライアント種別を参照)。OP はop.Newの中で検証し、不正な値があれば起動を拒否します。社内ツール、シングルテナント SaaS、審査済みの FAPI クライアントのような、少数の既知クライアントに向いています。 - 動的クライアント登録(RFC 7591): RP が
POST /registerにredirect_urisを含めて実行時に自己登録します。規定に違反するエントリは400 invalid_redirect_uriまたは400 invalid_client_metadataで拒否されます。マルチテナント SaaS、マーケットプレース、OP の運用者がクライアントを事前に把握できない用途に向いています。詳細は 動的クライアント登録 を参照してください。
よくあるミス
- 開発用に
http://localhost/...を登録する。op.WithAllowLocalhostRedirectURIs()を設定しない限り登録に失敗します。オプション不要のhttp://127.0.0.1:<port>/...を使ってください。 - 末尾スラッシュの揺れ。
https://app.example.com/cbとhttps://app.example.com/cb/は別物です。形をひとつ決めて登録し、RP のフレームワークにもそのまま送らせてください。 - host の大文字混在。貼り付けた URL をもとに RP のフレームワークが host を大文字化することがあります。比較は大文字小文字を区別するので、リクエストが一致しなくなります。
- path だけの登録。
/cbには scheme も host もないため有効な redirect URI ではなく、登録時に拒否されます。 - ワイルドカードへの期待。path の中の
*は効きません。RFC 9700 がワイルドカードを禁止しています。
次に読む
- 認可コード + PKCE フロー — redirect URI の流れと、各ステップで OP が何を確認するか。
- クライアント種別 — loopback のポート例外が適用されるのは public client と native client。
- 動的クライアント登録 — 実行時自己登録と、同じ検証ルールがどう適用されるか。
- 設計判断 — RFC 8252 loopback handling — loopback 規則の明示的な根拠。