一覧SD-05-3 · 認証機能の安全な実装

OIDCの検証とアプリケーションの認可

SSOで確認する身元と、アプリが決める操作権限を分けて実装します。

説明

OIDCで利用者を認証できても、その利用者がアプリ内の管理操作をできるとは限りません。検証したissuerとsubをローカルのアカウントへ対応させ、アプリの認可を別に行います。

固定の署名付きID tokenをcallbackへ渡し、redirect URI、state、issuer、audience、nonceを変更します。正しい認証後に、外部のrole値とローカル権限による更新結果を比較します。

SSOで確認する身元と、アプリが決める操作権限を分けて実装します。

1外部IDとローカル権限を読む

issuerとsubの組がlocal-7のviewerへ対応しています。tokenのrole=adminは、このアプリが権限の根拠に採用していない外部情報です。

from sd05 import OIDC app = OIDC()独立した認証要求とローカル対応表を用意する token = app.sample() print(app.db.execute("SELECT * FROM identities").fetchall())

同じsub文字列でもissuerが違えば別の識別子です。メール表示名だけで既存アカウントへ自動連結しないよう、対応規則を決めます。

2返送先とstateを照合する

期待したcallbackと、開始した要求のstateが合っているか確認します。

print(app.callback(token,redirect="https://other.example.test/cb"返送先を変更する)) print(app.callback(token,state="state-8"別の要求のstateを提示する)) print(app.pending)

redirect_rejectedとstate_rejectedです。失敗しても元の要求は未消費です。

3正しく署名された条件違いのtoken

固定の発行鍵で署名した、issuer・audience・nonceだけが異なる試料を比較します。

print(app.callback(app.sample(iss="https://other.example.test"発行者を変える))) print(app.callback(app.sample(aud="other-client"受信者を変える))) print(app.callback(app.sample(nonce="nonce-8"認証要求との対応を変える)))

それぞれの不一致を拒否します。署名が正しいことだけで別の発行者名や利用先の主張を受け入れません。

4未連結の外部利用者を比較する

同じ発行者から認証された別subを提示します。

print(app.callback(app.sample(sub="unlinked-user"対応表にない外部IDを提示する))) print(app.pending)

unlinked_identityです。身元の認証に成功しても、アプリに受け入れるアカウントの作成・連結方針は別に必要です。

5正しい認証を一度受け入れる

元のtokenを、開始した要求へ対応させます。

print(app.callback(token)正しい認証結果を受け入れる) print(app.callback(token))

初回はlocal-7とviewer、二度目はstate_rejectedです。外部tokenのrole=adminを、そのままアプリの管理権限へ使っていません。

6認証後も更新権限を確認する

viewerのlocal-7が文書7を更新しようとします。

print(app.edit("local-7")認証済みアカウントの編集権限を検査する) print(app.db.execute("SELECT body FROM documents WHERE id=7").fetchone())

更新0でv1を保ちます。ログインが成功したことと、編集を許すことは別の判定です。

7ローカル権限の変更を反映する

アプリ側の管理操作でeditorへ変更した設定を適用します。

app.db.execute("UPDATE identities SET role=アプリが管理する権限を変更する'editor' WHERE account='local-7'") app.db.commit() print(app.edit("local-7")変更後の権限で操作を要求する) print(app.db.execute("SELECT body FROM documents WHERE id=7").fetchone()) app.db.close()

更新1でv2です。権限をどこで管理し、次の要求へどう反映したかを追える構成になっています。

まとめ

issuerとsubを、確認済みのローカル対応表へ結び付けます。

state・nonce・audience・redirect URIを適切な段階で検証します。

認証成功後も、アプリの操作権限を別に確認します。

操作履歴と解説を振り返れます。

Python 3.15 / cryptography 50.0.2 / 公開試料と固定時刻

1
from sd05 import OIDC app = OIDC() token = app.sample() print(app.db.execute("SELECT * FROM identities").fetchall())
[('https://idp.example.test', 'alice', 'local-7', 'viewer')]
2
print(app.callback(token,redirect="https://other.example.test/cb")) print(app.callback(token,state="state-8")) print(app.pending)
redirect_rejected
state_rejected
True
3
print(app.callback(app.sample(iss="https://other.example.test"))) print(app.callback(app.sample(aud="other-client"))) print(app.callback(app.sample(nonce="nonce-8")))
issuer_mismatch
audience_mismatch
nonce_mismatch
4
print(app.callback(app.sample(sub="unlinked-user"))) print(app.pending)
unlinked_identity
True
5
print(app.callback(token)) print(app.callback(token))
('local-7', 'viewer')
state_rejected
6
print(app.edit("local-7")) print(app.db.execute("SELECT body FROM documents WHERE id=7").fetchone())
0
('v1',)
7
app.db.execute("UPDATE identities SET role='editor' WHERE account='local-7'") app.db.commit() print(app.edit("local-7")) print(app.db.execute("SELECT body FROM documents WHERE id=7").fetchone()) app.db.close()
1
('v2',)

演習完了