メインコンテンツへスキップ
prompt=loginの仕組みは、ユーザーのエージェント(ブラウザー)から渡されるパラメーターを取り除くだけで動作不能にできます。これは証明書利用者(RP)が以下のようなものを表示したい場合に、UXヒントをプロバイダー(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パラメーターが使用できると定義されています。
prompt任意:スペースで区切った、大文字・小文字の区別があるASCII文字列値のリストで、認証サーバーがエンドユーザーに対して再認証と同意を要求するかどうかを指定します。定義された値は:login認可サーバーがエンドユーザーに対して再認証を要求します。エンドユーザーを再認証できなかった場合はエラー(通常はlogin_required)を返さなければなりません。
ただし、このパラメーターで再認証を確実にするには問題があります。 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
ASで認証が成功すると、RPは以下のIDトークンを受け取ります。
JSON
{
  "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
}
ASが返す信頼されたIDドキュメントには、 最終ログインの時期を確認するクレームがありません 。これは、当初の認可要求がエンドユーザーのブラウザーから302のリダイレクトとして送信された場合に問題となります。RPが要求した再認証の手順を悪意のある行為者が迂回したい場合、prompt=loginパラメーターを削除するだけで済むため、IDトークンに含まれるフィールドではRPがその違いを判別できません。 下の図は、prompt=loginパラメーターを用いた暗黙フローを簡単に説明したものです。
Force Re-Authentication OIDC Implicit Flow
エンドユーザーがprompt=loginパラメーターを削除するたけで、再認証がスキップできることに注意してください。
Simplified Implicit Flow Remove prompt=login
上の最初のフローで返されたトークンは、下のフローで返されたトークンと同じです。再認証が行われたことを確認するのに、RPには仕様で定義された方法がないため、prompt=loginが実際に再認証を生じさせたと信頼することはできません。

max_age認証要求パラメーター

prompt=loginと違って、max_age認証要求パラメーターには、指定の間隔内に再認証が行われたことをRPが確実に確認できる方法があります。OIDCの仕様には以下が定義されています。
max_age任意:最大認証時間。OpenIDプロバイダーが最後に有効なエンドユーザー認証を行ってからの、許可される経過時間を秒単位で指定します。経過時間がこの値より大きい場合、OpenIDプロバイダーは、エンドユーザーを有効に再認証しようとしなければなりません(max_age要求パラメーターは、OpenID 2.0 PAPEのmax_auth_age要求パラメーターに対応します)。 max_ageが使用された場合、返されるIDトークンにはauth_timeクレーム値が含まれなければなりません。
定義にある最後の文章が最も重要な部分です。RPがmax_ageを要求した場合、RPにauth_timeクレームを渡す必要があります。つまり、max_ageの用途には以下の2つがあります。
  • セッションのフレッシュネスを指定する :アプリがユーザーに1日1回の再認証を要求する場合には、max_ageに値を指定すると、セッションの有効期間を長くすることができます。これは秒単位で定義します。
  • 即座の再認証を強制する :アプリがユーザーにアクセス前の再認証を要求する場合には、max_ageパラメーターの値を0に設定すると、ASが新たにログインを強制します。
下の図はこの要件を説明したものです。
OIDC re-authentication max_age flow
再認証が行われたかを確認するために、適切な情報量のあるトークンをRPが受け取っていることに注意してください。RPはIDトークンのauth_timeクレームを確認し、max_ageパラメーターでの要求が満たされたかを判断できます。こうすることで、max_age=0パラメーターには、prompt=loginパラメーターの動作を妨げるのと同じようなクライアントの改ざんが通用しなくなります。
適切なauth_timeを含むIDトークンを受信していることを検証するのはRPのみであることに注意してください。この検証は、アプリケーション作成者と、max_ageパラメーターを使用しているフレームワークが行います。

auth_timeクレームを使用する

OIDCの仕様により、再認証の実行を確実に確認する方法として、max_ageパラメーターは使用できても、prompt=loginが使用できないことを説明しました。これは、再認証を強制したい場合に安全なオプションを提供しません。
  • imprompt=loginpromptのみを含み、ASが実際に再認証したことは確認しません。
  • prompt=login & max_age=999999auth_timeクレームが使用されるように、任意のmax_ageを含めます。再認証が行われたことは確認できますが、パラメーターが乱雑になります。
  • max_age=0max_ageパラメーターのみを使用して、ログインプロンプトを強制的に表示します。最近更新された仕様ではこのパラメーターをさらに明確化し、実質的にはprompt=loginと同等だとしています。これは、UXパラメーターであるべきものをセッション維持パラメーターと混合させるため、適切ではありません。
そうではなく、Auth0はprompt=login要求パラメーターへの応答で、IDトークンにauth_timeクレームを含めて送信するようにしています。つまり、prompt=loginを使用すると同時に、再認証が行われたことを確認することができます。

auth_time確認の例

再認証が行われたことを確認する検証を必ず実装してください。適切なauth_timeが返されたことを検証しなければなりません。
以下の例では、passport-auth0-openidconnectモジュールを使用して、再認証の確認方法を説明します。最初の(最も簡単な)例としては、max_age=0オプションをAuth0OidcStrategyに追加して確認する方法です。
JavaScript
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);
  });
ストラテジーがすでにmax_ageパラメーターの確認を処理しているため、確認の手順がこれ以上必要にならないことに注意してください。
JavaScript
// 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'));
}
同じコンテキストでprompt=loginを使うこともできますが、規格としてauth_timeはIDトークン応答の付随的な発生を要求しないため、手動で確認を処理しなければなりません。このストラテジーコンストラクターは以下のようになります。
JavaScript
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);
  });
max_age=0とは違って、クライアントは手動でauth_timeパラメーターの確認を処理する必要があります。詳細については、「auth_timeクレームを使用する」をお読みください。
上の例は、簡略化された概念実証です(最後の10秒以内に認証されていなければなりません)。再認証が行われたことを検証したい場合は、理想的には、次のような作業が必要になります。
  1. 最初の認証要求が行われた時刻を記録する
  2. 認証応答時に、要求の送信時刻を取得する
  3. 最初の認証要求の時刻をauth_timeクレームと比較して、auth_timeのタイムスタンプの方が後であることを確認する
この例で使用している方法を運用システムで実行することは推奨されません。

既知の問題

Auth0が保証できるのは、アップストリームのIDプロバイダーと交換が行われることだけです。これは、ユーザーが実際にサードパーティーのIDプロバイダーにサインインしたり、ユーザーに既存のセッションがあるため再サインインの必要がなかったりすることによって行われます。いずれにしても、Auth0がアップストリームのIDプロバイダーと交換し、結果的にauth_timeが更新されます。 アップストリームのIDプロバイダーで再認証を強制することは、すべてのプロバイダーがこれに対応しているわけではないため、Auth0の対応外になります。 下の図は、フェデレーション接続で再認証するユーザーのフローの例を説明したものです。
Federated connections do not force re-authentication diagram
この方法ではデータベース接続の使用を想定しています。外部のIDプロバイダーは再認証の強制に対応していても、していなくても構いません。prompt=loginまたはprompt=consentの使用は、一般的に外部の(ソーシャル)IDプロバイダーにユーザーの再認証を指示するもので、Auth0はそれを強制できません。
機密性の高い操作を防ぐため、クライアント側(ブラウザー)で行われるIDトークンまたはauth_timeの検証には頼らないでください。

もっと詳しく

I