OIDCで再認証を強制する
prompt=login
の仕組みは、ユーザーのエージェント(ブラウザー)から渡されるパラメーターを取り除くだけで動作不能にできます。これは証明書利用者(RP)が以下のようなものを表示したい場合に、UXヒントをOpenIDプロバイダー(OP)に提供するためだけに使用されるべきです:
「Joshさん、こんにちは。お客様ではありませんか?こちらをクリックしてください。」
ただし、これは新たに行われた認証の確認に利用するべきではありません。それを避けるために、再認証を理由に
が要求された場合には、クライアントはmax_age
auth_time
クレームを使用して、再認証が行われたことを確認する必要があります。このクレームは、認証要求でprompt-login
またはmax_age=0
が渡されると、自動的にIDトークンに含められます。
max_age
パラメーターをAuthorization APIの/authorize
エンドポイントに渡す必要があります。Auth0.jsまたはLockを使用している場合には、ライブラリーの適切なオプションでパラメーターを設定することができます。
再認証の実装方法は特定のユースケースに依存します。機密性の高い操作に対して、再認証とステップアップ(多要素認証)は区別しなければなりません。それらは両方とも有効なセキュリティ対策です。前者ではエンドユーザーにパスワードの再入力を要求するの対し、後者ではあらかじめ構成済みの多要素認証も併せて要求します。
prompt=loginパラメーターの制限事項
OIDCの仕様では、再認証UI(通常はログインプロンプト)をトリガーするのにprompt=login
パラメーターが使用できると定義されています。
ただし、このパラメーターで再認証を確実にするには問題があります。RPには、再認証の操作が行われたことを確認する方法がありません。なぜそうなのかを理解するために、トラフィックを調べてみましょう。RPからの認証要求のフローは以下のようになります。
https://mydomain.auth0.com/authorize?
client_id=abcd1234
&redirect_uri= https://mydomain.com/callback
&scope=openid profile
&response_type=id_token
&prompt=login
Was this helpful?
ASで認証が成功すると、RPは以下のIDトークンを受け取ります。
{
"nickname": "user",
"name": "user@mydomain.auth0.com",
"updated_at": "2019-04-01T14:43:03.445Z",
"iss": "https://jcain0.auth0.com/",
"sub": "auth0|l33t",
"aud": "abcd1234",
"iat": 1554129793,
"exp": 1554165793
}
Was this helpful?
ASが返す信頼されたIDドキュメントには、最終ログインの時期を確認するクレームがありません。これは、当初の認可要求がエンドユーザーのブラウザーから302のリダイレクトとして送信された場合に問題となります。RPが要求した再認証の手順を悪意のある行為者が迂回したい場合、prompt=login
パラメーターを削除するだけで済むため、IDトークンに含まれるフィールドではRPがその違いを判別できません。
下の図は、prompt=login
パラメーターを用いた暗黙フローを簡単に説明したものです。

エンドユーザーがprompt=login
パラメーターを削除するたけで、再認証がスキップできることに注意してください。

上の最初のフローで返されたトークンは、下のフローで返されたトークンと同じです。再認証が行われたことを確認するのに、RPには仕様で定義された方法がないため、prompt=login
が実際に再認証を生じさせたと信頼することはできません。
max_age認証要求パラメーター
prompt=login
と違って、max_age
認証要求パラメーターには、指定の間隔内に再認証が行われたことをRPが確実に確認できる方法があります。OIDCの仕様には以下が定義されています。
定義にある最後の文章が最も重要な部分です。RPがmax_age
を要求した場合、RPにauth_time
クレームを渡す必要があります。つまり、max_age
の用途には以下の2つがあります。
セッションのフレッシュネスを指定する:アプリがユーザーに1日1回の再認証を要求する場合には、
max_age
に値を指定すると、SSOセッションの有効期間を長くすることができます。これは秒単位で定義します。即座の再認証を強制する:アプリがユーザーにアクセス前の再認証を要求する場合には、
max_age
パラメーターの値を0に設定すると、ASが新たにログインを強制します。
下の図はこの要件を説明したものです。

再認証が行われたかを確認するために、適切な情報量のあるトークンをRPが受け取っていることに注意してください。RPはIDトークンのauth_time
クレームを確認し、max_age
パラメーターでの要求が満たされたかを判断できます。こうすることで、max_age=0
パラメーターには、prompt=login
パラメーターの動作を妨げるのと同じようなクライアントの改ざんが通用しなくなります。
auth_timeクレームを使用する
OIDCの仕様により、再認証の実行を確実に確認する方法として、max_age
パラメーターは使用できても、prompt=login
が使用できないことを説明しました。これは、再認証を強制したい場合に安全なオプションを提供しません。
imprompt=login:
prompt
のみを含み、ASが実際に再認証したことは確認しません。prompt=login & max_age=999999:
auth_time
クレームが使用されるように、任意のmax_age
を含めます。再認証が行われたことは確認できますが、パラメーターが乱雑になります。max_age=0:
max_age
パラメーターのみを使用して、ログインプロンプトを強制的に表示します。最近更新された仕様ではこのパラメーターをさらに明確化し、実質的にはprompt=login
と同等だとしています。これは、UXパラメーターであるべきものをセッション維持パラメーターと混合させるため、適切ではありません。
そうではなく、Auth0はprompt=login
要求パラメーターへの応答で、IDトークンにauth_time
クレームを含めて送信するようにしています。つまり、prompt=login
を使用すると同時に、再認証が行われたことを確認することができます。
auth_time確認の例
以下の例では、passport-auth0-openidconnectモジュールを使用して、再認証の確認方法を説明します。最初の(最も簡単な)例としては、max_age=0
オプションをAuth0OidcStrategy
に追加して確認する方法です。
var strategy = new Auth0OidcStrategy(
{
domain: process.env.AUTH0_DOMAIN,
clientID: process.env.AUTH0_CLIENT_ID,
clientSecret: process.env.AUTH0_CLIENT_SECRET,
callbackURL: process.env.AUTH0_CALLBACK_URL || 'http://localhost:5000/callback',
max_age: 0
},
function(req, issuer, audience, profile, accessToken, refreshToken, params, cb) {
// No extra validation required!
return cb(null, profile);
});
Was this helpful?
ストラテジーがすでにmax_age
パラメーターの確認を処理しているため、確認の手順がこれ以上必要にならないことに注意してください。
// https://openid.net/specs/openid-connect-basic-1_0.html#IDTokenValidation - check 8.
if (meta.params.max_age && (!jwtClaims.auth_time || ((meta.timestamp - meta.params.max_age) > jwtClaims.auth_time))) {
return self.error(new Error('auth_time in id_token not included or too old'));
}
Was this helpful?
同じコンテキストでprompt=login
を使うこともできますが、規格としてauth_time
はIDトークン応答の付随的な発生を要求しないため、手動で確認を処理しなければなりません。このストラテジーコンストラクターは以下のようになります。
var strategy = new Auth0OidcStrategy(
{
domain: process.env.AUTH0_DOMAIN,
clientID: process.env.AUTH0_CLIENT_ID,
clientSecret: process.env.AUTH0_CLIENT_SECRET,
callbackURL: process.env.AUTH0_CALLBACK_URL || 'http://localhost:5000/callback',
prompt: 'login'
},
function(req, issuer, audience, profile, accessToken, refreshToken, params, cb) {
const tenSecondsAgo = (Date.now() / 1000) - 10;
if (isNaN(profile.auth_time) || profile.auth_time < tenSecondsAgo) {
return cb('prompt=login requested, but auth_time is greater than 10 seconds old', null);
}
return cb(null, profile);
});
Was this helpful?
max_age=0
とは違って、クライアントは手動でauth_time
パラメーターの確認を処理する必要があります。詳細については、「auth_timeクレームを使用する」をお読みください。
既知の問題
Auth0が保証できるのは、アップストリームのIDプロバイダーと交換が行われることだけです。これは、ユーザーが実際にサードパーティーのIDプロバイダーにサインインしたり、ユーザーに既存のセッションがあるため再サインインの必要がなかったりすることによって行われます。いずれにしても、Auth0がアップストリームのIDプロバイダーと交換し、結果的にauth_time
が更新されます。
アップストリームのIDプロバイダーで再認証を強制することは、すべてのプロバイダーがこれに対応しているわけではないため、Auth0の対応外になります。
下の図は、フェデレーション接続で再認証するユーザーのフローの例を説明したものです。

この方法ではデータベース接続の使用を想定しています。外部のIDプロバイダーは再認証の強制に対応していても、していなくても構いません。prompt=login
またはprompt=consent
の使用は、一般的に外部の(ソーシャル)IDプロバイダーにユーザーの再認証を指示するもので、Auth0はそれを強制できません。