OIDC認可コードフローを手で再現する
目標
ラボ用のOIDCプロバイダーを相手に、認可コードフローを自分の手で実行し、IDトークンをデコード・署名検証し、検証チェックリストを作成できるようになります。
なぜ重要なのか
OIDCをライブラリでしか使ったことがない人は、障害が起きたときにどこを見ればよいかわかりません。redirect_uriの不一致、stateの未検証、nonceの欠落、期限切れのコードの再利用。すべて、フローを目で見たことがあって初めて、勘が働きます。特に、IDトークンはbase64なので誰でもデコードできるという事実を、手で確認することが重要です。ペイロードを書き換えても、署名の検証をしなければそのまま通ってしまうことを自分で再現してみれば、なぜalgを固定しなければならないのかが、身につきます。
ステップ
- ラボ用のIdPを起動してください。
python3 /opt/lab/fixtures/auth/oidc/idp.py 9000(バックグラウンド) discoveryドキュメントを取得して、/root/oidc/discovery.jsonに保存してください。 (http://127.0.0.1:9000/.well-known/openid-configuration)issuer、authorization_endpoint、token_endpoint、jwks_uriが含まれている必要があります。 /root/oidc/auth-url.txtにauthorization URLを1行で書いてください。 必須パラメーター:response_type=code、client_id=labhub-web、redirect_uri=http://127.0.0.1:9100/callback、scope=openid profile email、state=<16자 이상>、nonce=<16자 이상>(山括弧の中の韓国語はプレースホルダーで、16文字以上を意味します)。- そのURLをリダイレクトを追わずに呼び出して、レスポンスの
Locationヘッダーを/root/oidc/callback.txtに保存し、そこからcodeの値だけを取り出して/root/oidc/code.txtに保存してください。 Locationのstateは、ステップ2で送った値と同じである必要があります。 - トークンエンドポイントでコードを交換し、レスポンスのJSON全体を
/root/oidc/token.jsonに保存してください。client_id=labhub-web、client_secret=labhub-secretを一緒に送ります。access_token、id_token、token_typeが含まれている必要があり、token_typeはBearerです。 id_tokenのペイロードをデコードして、/root/oidc/claims.jsonに保存してください。iss、aud、sub、exp、nonceが含まれている必要があり、audはlabhub-web、nonceはステップ2で送った値と同じである必要があります。/root/oidc/verify.shを作成してください。2つの引数(ID토큰 공개키PEM。プレースホルダーはIDトークンと公開鍵のPEMです)を受け取り、署名が有効なら終了コード0、そうでなければ0以外の値で終了します。 公開鍵は/opt/lab/fixtures/auth/oidc/idp-public.pemにあります。access_tokenでuserinfoエンドポイントを呼び出して、/root/oidc/userinfo.jsonに保存してください。subの値が、ステップ5のclaims.jsonのsubと同じである必要があります。/root/oidc/checklist.csvを作成してください。1行目はitem,riskです。서명、iss、aud、exp、nonce、algの6項目がitemにある必要があり(最初の項目は、韓国語で「署名」を意味する語です)、riskには、その項目を検証しないと可能になる攻撃/事故を、10文字以上で書いてください。
参考
- リダイレクトを追わない方法:
curl -s -D - -o /dev/null "<URL>"のあと、Location:の行を確認します - base64urlのデコード:
-を+に、_を/に置き換え、パディング(=)を補ってからbase64 -d - 署名の検証: 署名の対象は
<헤더>.<페이로드>(プレースホルダーはヘッダーとペイロードです)の文字列で、アルゴリズムはRS256(SHA-256)ですopenssl dgst -sha256 -verify <공개키> -signature <서명파일> <데이터파일>(プレースホルダーは公開鍵、署名ファイル、データファイルです) - よくあるミス1: 認可コードを2回使うことです。使い捨てなので、2回目は失敗します。
- よくあるミス2: base64urlをそのまま
base64 -dに渡して、エラーになることです。 - よくあるミス3: トークン交換のときに、
redirect_uriをauthorizationのときと違う値で送ることです。 2つの値が完全に同じである必要があります。
IdPの起動とdiscoveryドキュメント
ラボ用のIdPを起動してください。
python3 /opt/lab/fixtures/auth/oidc/idp.py 9000(バックグラウンド)
discoveryドキュメントを取得して、/root/oidc/discovery.jsonに保存してください。
(http://127.0.0.1:9000/.well-known/openid-configuration)
issuer、authorization_endpoint、token_endpoint、jwks_uriが含まれている必要があります。
OIDCプロバイダーは、標準のパスに設定ドキュメントを置きます。そのドキュメント1つで、エンドポイントのアドレスをすべて知れます。jqで必要な値だけを取り出してみてください。
authorization URLの組み立て
/root/oidc/auth-url.txtにauthorization URLを1行で書いてください。
必須パラメーター: response_type=code、client_id=labhub-web、redirect_uri=http://127.0.0.1:9100/callback、scope=openid profile email、state=<16자 이상>、nonce=<16자 이상>(山括弧の中の韓国語はプレースホルダーで、16文字以上を意味します)。
必須パラメーターを抜かすと、IdPがエラーを返します。stateとnonceは役割が違います。1つはCSRF対策、もう1つはトークンのリプレイ対策です。
認可コードの受信
そのURLをリダイレクトを追わずに呼び出して、レスポンスのLocationヘッダーを/root/oidc/callback.txtに保存し、そこからcodeの値だけを取り出して/root/oidc/code.txtに保存してください。
Locationのstateは、ステップ2で送った値と同じである必要があります。
ブラウザーがなくても、リダイレクトのレスポンスのLocationヘッダーを読めば、コードが見えます。curlがリダイレクトを追わないようにする必要があります。
トークン交換
トークンエンドポイントでコードを交換し、レスポンスのJSON全体を/root/oidc/token.jsonに保存してください。
client_id=labhub-web、client_secret=labhub-secretを一緒に送ります。
access_token、id_token、token_typeが含まれている必要があり、token_typeはBearerです。
トークンエンドポイントはPOSTで、form形式です。認可コードは使い捨てなので、2回使うと失敗します。失敗したら、ステップ2から3をやり直してください。
IDトークンのペイロードのデコード
id_tokenのペイロードをデコードして、/root/oidc/claims.jsonに保存してください。
iss、aud、sub、exp、nonceが含まれている必要があり、audはlabhub-web、nonceはステップ2で送った値と同じである必要があります。
JWTはドットで区切られた3つの部分で、それぞれbase64urlです。base64urlは標準のbase64と文字が2つ違い、パディングがない場合があります。
署名検証スクリプト
/root/oidc/verify.shを作成してください。2つの引数(ID토큰 공개키PEM。プレースホルダーはIDトークンと公開鍵のPEMです)を受け取り、署名が有効なら終了コード0、そうでなければ0以外の値で終了します。
公開鍵は/opt/lab/fixtures/auth/oidc/idp-public.pemにあります。
署名の対象は「ヘッダー.ペイロード」の文字列全体です。openssl dgstで公開鍵の検証ができ、署名値はbase64urlのデコードが必要です。
userinfoの呼び出し
access_tokenでuserinfoエンドポイントを呼び出して、/root/oidc/userinfo.jsonに保存してください。
subの値が、ステップ5のclaims.jsonのsubと同じである必要があります。
アクセストークンは、AuthorizationヘッダーにBearer方式で送ります。返ってきたsubが、IDトークンのsubと同じかを確認するのが、このステップの核心です。
IDトークン検証チェックリスト
/root/oidc/checklist.csvを作成してください。1行目はitem,riskです。
서명、iss、aud、exp、nonce、algの6項目がitemにある必要があり(最初の項目は、韓国語で「署名」を意味する語です)、riskには、その項目を検証しないと可能になる攻撃/事故を、10文字以上で書いてください。
各項目がなぜ必要かではなく、「検証しないとどんな攻撃が可能か」を書いてください。そうすれば、あとのレビューで説得力が出ます。