OpenID Connectにおけるnonceパラメーターについて
SSO(Single Sign On)の実装を検討していた際に、nonce について整理したメモです。
nonceとは?
セキュリティの重要な概念の1つとして、nonce(ノンス) があります。
nonce(number used once)は「一度だけ使用される番号」で、重複や再利用を避けるために用いられます。
利用例:
- 暗号の初期ベクトル(IV)
- 認証トークン
- ワンタイムトークン(文脈によってはこう呼ばれる)
OpenID Connect(OIDC)におけるnonceの役割
OIDCにおけるnonceは、リプレイアタック防止のために使われます。
1. リクエスト時の生成
クライアント(例:Webアプリケーション)は、認証リクエストをIdP(Identity Provider)に送信する際にランダムなnonceを生成します。
import os
nonce = os.urandom(20).hex()
2. セッションへの保持
生成したnonceは、クライアント側のセッションストアや一時的ストレージに保存します。
3. IDトークンへの組み込み
IdPは認証成功時、生成されたnonce値をIDトークンに含めてクライアントへ返します。
4. トークンの検証
クライアントは、受け取ったIDトークン内のnonceとセッションに保存したnonceを比較します。
-
一致 → トークンは有効
-
不一致 → トークンは拒否(不正アクセスの可能性)
リプレイアタック防止の仕組み
攻撃者が既に 使用済みのIDトークン を再利用しようとした場合、
nonceが一致しないためアクセスは拒否されます。
さらに、検証後はnonceを削除することで、同じIDトークンを再利用することはできません。
これにより、 IDトークンの使い回し(リプレイアタック) を防止できます。
リプレイアタック防止の仕組み(図解付き解説)
-
認証リクエストの送信
クライアントは、認証リクエストを送信する際にランダムなnonce
(例:A
)を生成し、IdP(Identity Provider)へ送ります。 -
IDトークンの発行
IdPは認証に成功すると、受け取ったnonce
をIDトークンに含めてクライアントへ返します。
(例:IDトークン { nonce: A }
) -
攻撃者による再利用試行
攻撃者がネットワークなどから盗み取った既に使用されたIDトークンを再利用しようとします。 -
トークンの検証とエラー
クライアントは受け取ったIDトークン内のnonce
と、自分のセッションに保存していたnonce
を比較します。- 一致しない場合 → エラーとして処理を拒否
- すでに使用済みの
nonce
の場合もセッション上に存在しないためエラー
補足:実装時の注意点
-
nonceは検証後に必ず破棄すること
再利用を完全に防ぐため、検証が終わったnonce
は必ずセッションやストレージから削除します。 -
nonceの生成は十分にランダムにすること
推測や衝突を防ぐため、暗号論的に安全なランダム生成を行う(例:os.urandom()
など)。 -
nonceの保存期間は最小限にすること
セッション有効期限や認証要求のタイムアウトと合わせて、短期間で破棄できるようにする。
これらを実践することで、OpenID Connectのnonce
はリプレイアタック防止の強力な手段となります。
Pythonでのnonce生成サンプル
OpenID Connect(OIDC)のnonce
は暗号学的に安全な乱数で生成する必要があります。
以下は、Python標準ライブラリのみを使った例です。
import os
import base64
import secrets
# 1) 推奨:Base64URL(URLやCookieに安全に載せられる形式)
def generate_nonce_b64url(n_bytes: int = 20) -> str:
# n_bytesは16〜32を目安に。20なら約27文字のBase64URL文字列
raw = os.urandom(n_bytes)
return base64.urlsafe_b64encode(raw).rstrip(b"=").decode("ascii")
# 2) 代替:Hex(読みやすいが長くなる)
def generate_nonce_hex(n_bytes: int = 20) -> str:
return os.urandom(n_bytes).hex()
# 3) 簡易:secrets(内部で安全な乱数生成)
def generate_nonce_token(n_bytes: int = 20) -> str:
return secrets.token_urlsafe(n_bytes) # Base64URL
# サンプル出力
print(generate_nonce_b64url()) # 例: 'Q2XqU2eZ2q3d8y8lCqGq2h4s1Lc'
print(generate_nonce_hex()) # 例: 'a3f4b2c1d9e84f5f9e20b5dce8af3123a4f6d8b1'
print(generate_nonce_token()) # 例: 'r7vX4m3oZt8GqPjKk1pE9cU3hVw'
実装時のポイント
-
暗号学的に安全な乱数生成
os.urandom() または secrets モジュールを利用(randomモジュールは不可) -
バイト長の目安
16〜32バイト程度が推奨(セキュリティと長さのバランスを考慮) -
検証後は必ず破棄
nonceは認証後の検証が終わったらセッションから削除すること
まとめ
- nonceは「一度だけ使用される番号」
- OIDCではリプレイアタック防止に活用
- 認証リクエスト時に生成 → セッション保持 → IDトークンと照合 → 検証後破棄
- 結果:IDトークンは一度しか使えなくなる
つまり、nonceを使うことで、認証に利用するIDトークンは 一回きり になります。