LoginSignup
13
9

More than 3 years have passed since last update.

OIDCのOPを実装したときに悩んだ箇所

Last updated at Posted at 2019-12-13

はじめに

この記事は認証認可技術 Advent Calendar 2019の14日目の記事です。

OpenID Connect の OP をスクラッチで実装したときにRFC上では表現されないような箇所に悩まされました。

  • 各トークンの有効期限はどう設計すべきか
  • 認可コードとトークンの関係性
  • アクセストークンの有効期限=同意画面を出す、出さないなのか?
  • OIDC範囲外ですが、パスワードポリシーはどうするべきか

などなど、上記について記載していきたいと思います。

システム概要

あまり本編とは関係ないですが、こんなシステムを作りました。

  • OpenID Connect の IdPとして振る舞える
    • 一通りのフローをサポートする(Device Autorization grant含む。将来的にCIBAやFAPIにも対応したい)
    • クライアント管理やトークン管理など運用機能も必要
  • 認証手段は、
    • 自前のDBへの問い合わせ(ID/PW)
    • 自前のDBへの問い合わせ(FIDO2、FIDO UAF1.1)
    • 外部IdPへの問い合わせ(Google,Facebook,etc)
    • 独自実装の外部システムへの問い合わせ(なんちゃってOIDCだったり、ROPCだったり)
  • 多要素認証手段
    • SMS/TEL
    • FIDO U2F
    • TOTPベーストークン

各種トークンの有効期限の考え方

OIDCやOAuthでは、認可コード、アクセストークン、リフレッシュトークン、IDトークンといったクレデンシャル情報をIdP/OP側で管理する必要がありますが、RFCでは、これらの有効期限の値に関する具体的な記述は殆どありません。

調べた限りでは、 OAuth2.0仕様にて、

A maximum authorization code lifetime of 10 minutes is RECOMMENDED.
(認可コードの有効期限は最大でも10分を推奨)

という記載がある程度でした。

その他の記載としては、

OIDCのSecurityConsiderationsにて、

16.18. Lifetimes of Access Tokens and Refresh Tokens
Access Tokens might not be revocable by the Authorization Server. Access Token lifetimes SHOULD therefore be kept to single use or very short lifetimes.
(アクセストークンは、認可サーバ側でrevokeできない可能性があるので、アクセストークンの有効期限は、一回使う程度の時間か、非常に短い時間にすべき)
(略)
The Authorization Server SHOULD clearly identify long-term grants to the User during Authorization.
(認可サーバは、ユーザが同意する際に、長期間の認可(リフレッシュトークンなど)を与えようとしていることを明示すべきである)

という程度です。「短い時間にせよ。」と言われても、どれぐらいが妥当なのかよくわかりません。
ユースケースによって有効期限は変わりそうです。

ここまで考えたときに、

そもそもトークンの有効期限って固定の値でいいんだろうか?

という疑問がわきました。

以下のようなケースもあるのではないかと考えました。

クライアントによって、アクセストークンの有効期限を変えたい

  • publicクライアント向けに発行するアクセストークンの有効期限は短くしたい
  • グループ内企業が作るクライアントはガバナンスが効くから長めに、サードパーティのクライアント向けには短めにしたい

スコープによって、アクセストークンの有効期限を変えたい

  • write権限に対応するscopeに紐づくアクセストークンは短くしたい
  • 大した権限の無いscopeのアクセストークンは長めでも良いのではないか

フローによって、アクセストークンの有効期限を変えたい

  • Hybrid flowにおいて、認可エンドポイントから返却されたアクセストークンは短く、トークンエンドポイントから返却されたアクセストークンは長くしたい

OAuth 2.0 Security Best Current Practiceにも以下のような記述があるので、クライアントやスコープによってトークンの有効期限を変えるのはむしろ推奨なのかもしれません。

Refresh tokens SHOULD expire if the client has been inactive for some time, i.e., the refresh token has not been used to obtain fresh access tokens for some time. The expiration time is at the discretion of the authorization server. It might be a global value or determined based on the client policy or the grant associated with the refresh token (and its sensitivity).
(リフレッシュトークンの有効期間は認可サーバの裁量による。グローバル値でもいいし、クライアントのポリシーやトークンに関連付けられたgrantや機密性に基づいて決定される)

これらの検討を踏まえ、結局以下のような実装を行いました。

  • クライアント単位、スコープ単位でトークンの有効期限の値を設定できるようにする
    • IDトークンの有効期限( exp クレーム)も念の為上記のように設定できるようにする
  • 異なる有効期限のスコープが複数指定された場合(例:scope=read+write)、短い方の有効期限にする
  • 認可コードの有効期限は統一する。(クライアントの性質やscopeに依存するものではないので)

余談:自分が調べた限り、Auth0やCognito、Authleteでは、設定レベルで上記を満たすことは不可そうでした。
Authleteはトークンを弄るAPIを使って無理やり実現することは可能そうです。(IDトークンは署名解いてexp変えて再署名する必要があります。)
この粒度の制御が必要な要件を満たすとなると、製品導入するかスクラッチ実装レベルなのかなという気がします。

(2019/12/14 13:30追記)Authleteは2.0からスコープ単位で有効期限設定可能。2.1からは クライアント単位で設定可能とのことです。

「認可した」という行為の考え方

続いて、各トークンの保持の仕方を設計する際にも悩みがありました。

普通に考えると、認可コード:アクセストークンは1:1で紐づけて設計すべきものかと思います。トークンエンドポイントに送信された認可コードに対してアクセストークンは一つのみ払い出されるからです。(Implicitフローは一旦省略(汗))以下は概念です。

認可コード アクセストークン リフレッシュトークン 有効期限
03fbss35dsa 4b5b7e59-254b-4032-8919 25e4033f-a9d6-53414f1de624 30分

一方で、ユーザーが「あるクライアントに対して、このスコープ内の権限を認可した」という情報はどのように管理すべきでしょうか。
認可コードとユーザー、スコープ、クライアントを紐付けて管理するのが良い気がします。認可コードは短期間の有効期限かつ、一度トークンリクエストを行うと無効化する必要があります。履歴管理の観点からも論理削除しないとまずいですね。

認可コード ユーザーID スコープ クライアント 有効期限
03fbss35dsa tom openid+profile ClientA 60秒
deef0f5da11 tom openid+profile+address ClientA 60秒
5da11deeghy tom openid+profile ClientB 60秒

ところで、同意画面があります。

Googleとかに認可要求すると、ログイン後に、クライアントにこんな情報渡すけど良いのかお前?って聞かれるやつです。

あれ、同じクライアントに対して同じスコープの認可要求だったら、一度同意したら(有効期限内であれば)スキップさせたくないですか?(prompt=consentがない場合)

前述のように、認可コードとスコープ、クライアントを紐付けて管理している場合、同意画面をスキップする有効期限は別に管理する必要がありそうです。
では、アクセストークンが有効な間は同意画面を出さない設計にすればよいでしょうか?
アクセストークンがリボークされた後に認可要求をしたら再度同意画面を出すべきでしょうか?リフレッシュトークンが有効な間は認可要求しないから毎回同意画面表示でもいいかもしれません。

このあたりの結論が出なかったので、「同意画面をスキップするかどうかの有効期限」を別で設定できるように「同意したという行為」を別に管理することにしました。やりすぎ感もあるかなとも思っていますが、同意履歴をコードやトークンとは別の概念で管理することは意味があるかなと思いました。

このあたりベスプラがあれば是非教えてほしいです。

パスワードポリシー

認証機能を作る際、(FIDO等を使わなければ)パスワードのポリシーを決めることは避けては通れない道です。
よくある進め方として、NIST SP800-63-Bなどのガイドラインや他社の実装状況をもとにパスワードのポリシーを決めていくことになると思います。

ですが今回は、「パスワードポリシー自体を運用者・管理者が変更できるようにしてくれ」というオーダーがありました。そうなると、大文字小文字混在~とか、定期変更が~とか、思いついたポリシーについて検討していけば良いとはならず、パスワードのポリシーとして決めなくてはならない項目を網羅した上で、一つ一つの項目を設定できるようにする必要があります。

色々調べているうちに、SCIM Password Management Extensionという仕様を発見しました。

SCIM Password Management Extension について

SCIM Password Management Extension は、 SCIM と呼ばれるプロトコル・スキームの拡張仕様です。

SCIMとは、System for Cross-domain Identity Management の略で、システム間でアイデンティティ情報を連携する際に用いられるプロトコルとスキーマの定義です。

ユーザーのアイデンティティ情報のCRUD操作を定義したプロトコル
ユーザーのアイデンティティ情報スキーマを定義したスキーマ
などがあります。

そのSCIMのスキーマを拡張した仕様として、SCIM Password Management Extensionがドラフト版として、提示されています。
バージョンが 00 のみで、最終更新日も2015年なので、いまいち怪しい気もしますが、参考にします。

このRFCでは、パスワードに関する以下の拡張スキーマが定義されています。

  • Password Schema Extension
    • ユーザのパスワード状態を管理するスキーマ urn:ietf:params:scim:schemas:uniid:2.0:Password
  • Password Policy
    • システム全体のパスワードポリシーを管理するスキーマ urn:ietf:params:scim:schemas:core:2.0:policy:Password

他にも、パスワードリセット時の仕様等を定義しています。

  • Management Requests
    • PasswordResetRequest
    • PasswordValidateRequest
    • UsernameValidateRequest
    • UsernameGenerateRequest
    • UsernameRecoverRequest

では、実際にパスワードポリシーのスキーマを見ていきましょう。
SCIM Password Management Extension では、以下の35項目が定義されています。

# 名前 説明
1 name ポリシー名
2 description 説明
3 maxLength 最大文字数
4 minLength 最小文字数
5 minAlphas 最小アルファベット数
6 minNumerals 最小数字数
7 minAlphaNumerals 最小英数字数
8 minSpecialChars 最小特殊文字数
9 maxSpecialChars 最大特殊文字数
10 minUpperCase 最小大文字数
11 minLowerCase 最小小文字数
12 minUniqueChars 最小ユニーク文字数
13 maxRepeatedChars 最大同一文字繰り返し回数
14 startsWithAlpha アルファベット始まりの強制
15 minUnicodeChars Unicode文字数
16 firstNameDisallowed 名の利用不可
17 lastNameDisallowed 姓の利用不可
18 userNameDisallowed ユーザーIDの利用不可
19 minPasswordAgeInDays 最小パスワード変更禁止期間(日)
20 warningAfterDays パスワード変更警告表示日数
21 expiresAfterDays パスワード有効期間(日)
22 requiredChars 必須文字
23 disallowedChars 禁止文字
24 disallowedSubStrings 禁止文字列
25 dictionaryLocation ブラックリストのURL
26 passwordHistorySize 利用禁止過去パスワード数
27 maxIncorrectAttempts 最大リトライ回数
28 lockOutDuration ロックアウトの時間
29 challengesEnabled 秘密の質問を利用するか
30 defaultQuestions デフォルトの質問
31 minQuestionCount 最小設定質問数
32 minAnswerCount 最小回答質問数
33 allAtOnce 全質問表示
34 minResponseLength 最小回答文字数
35 maxIncorrectAttempts 最大リトライ回数

つまり、最初の2つを除く、33個のルールを変数として設定できるような仕組みを作れば、世の中の大体のパスワードポリシーを設定レベルで実装できるということですね(辛い)

終わりに

OpenID Connect の IdP をスクラッチで実装したときに悩まされた点を一部書きました。どのフローを選択するかとか、state、nonce、at_hash警察などの記事は多いので、それ以外の点で書こうと思ったのですが、何のまとまりもない文章になってしまいました。(ちなみに今回のシステムではstateは必須にするように実装しています。)

ここまで考えなければいけないケースは少ないかもしれませんが、IdP実装はやはり考えることが多いので、サービスなり製品なりを使ったほうが無難かもしれません。サービスや製品選定の際にこういう視点もあるんだなと参考になれば幸いです。

13
9
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
9