ルール内でユーザーをリダイレクトする

認証トランザクションの完了前に、 Auth0ルールを使用してユーザーをリダイレクトすることができます。これによって、カスタム認証フローを実装して、標準のログインフォームにはない追加のユーザー操作に対応できるようになります。リダイレクトルールは一般的に、Auth0でカスタムの多要素認証(MFA)を実行するために使用されますが、以下にも使用できます。

  • カスタムのプライバシーポリシーへの同意、利用規約、データ開示のフォーム。

  • 追加で必要なプロファイルデータを安全に一度だけ収集する。

  • Active Directoryのリモートユーザーがパスワードを変更できるようにする。

  • ユーザーが未知の場所からログインする際に、追加で検証を求める。

  • サインアップ時にユーザーが提供した以上のユーザー情報を集める。

認証フローごとに、ユーザーを1回リダイレクトできます。ユーザーをリダイレクトするルールが1つある場合、後でユーザーをリダイレクトする2つ目のルールを呼び出すことはできません

詳細については、「Auth0での多要素認証」をご覧ください。

リダイレクトを開始して認証を再開する

context.redirectプロパティを以下のように設定します。

function (user, context, callback) {
  context.redirect = {
    url: "https://example.com/foo"
  };
  return callback(null, user, context);
}

Was this helpful?

/

すべてのルールの実行が完了したら、Auth0はcontext.redirect.urlプロパティで指定されたURLにユーザーをリダイレクトします。また、Auth0はそのURLでstateパラメーターも渡します。例:

https://example.com/foo?state=abc123

Was this helpful?

/

認証トランザクションを再開するには、リダイレクトURLがstateパラメーターを抽出し、それをAuth0に送り返す必要があります。状態は不透明な値で、クロスサイトリクエストフォージェリ(CSRF)攻撃を防ぐために使用されます。

リダイレクト後、ユーザーを/continueエンドポイントにリダイレクトして認証を再開し、受け取ったstateパラメーターをURLに含めます。元の状態を/continueエンドポイントに送り返さない場合、Auth0はログイントランザクションのコンテキストを失うことになり、ユーザーはinvalid_requestエラーのためにログインできなくなります。

例:

https://{yourDomain}/continue?state={originalState}

Was this helpful?

/

カスタムドメインを使用している場合:

https://{yourAuth0CustomDomain}/continue?state={originalState}

Was this helpful?

/

THE_ORIGINAL_STATEはAuth0が生成した値で、リダイレクトURLに送信されます。たとえば、ルールがhttps://example.com/fooにリダイレクトする場合、Auth0はhttps://example.com/foo?state=abc123に類似したリダイレクトURLを使用します。つまり、abc123THE_ORIGINAL_STATEです。認証トランザクションを再開するには、以下にリダイレクトします。

https://{yourDomain}/continue?state=abc123

Was this helpful?

/

ユーザーが/continueエンドポイントにリダイレクトされた場合:

  • すべてのルールが再度実行されますが、context.redirectは無視され、認証を続行することができます。

  • ユーザーオブジェクトへの変更は、/continue エンドポイントを呼び出す前のリダイレクト中に行われます。たとえば、Auth0 Management APIを介した更新は、トランザクションの続行後に使用できます。

再開したログインを確認する

ユーザーが開始したログインと再開したログインのフローを区別するには、context.protocolを確認します。

function (user, context, callback) {
    if (context.protocol === "redirect-callback") {
        // User was redirected to the /continue endpoint
    } else {
        // User is logging in directly
    }
}

Was this helpful?

/

パスワード変更の強制の例

特定の条件下で、ユーザーにパスワード変更を強制する場合、次の動作のルールを作成できます。

  1. ユーザーがログインを試み、パスワードを変更する必要がある。

  2. クエリ文字列にJWTがあるアプリケーション固有のページにユーザーがリダイレクトされる。このJWTにより、このユーザーのパスワードだけが変更でき、アプリケーションによって検証されなければならないことが保証される。

  3. アプリケーションがAuth0 Management API

  4. ユーザーがパスワードを変更したら、アプリケーションは検証済みのデコードされたJWTからauthorize_againクレームを抽出し、そのURLにユーザーをリダイレクトして、新しいパスワードでサインインできるようにする。

function(user, context, callback) {
   /*
   * Prerequisites:
   * 1. Implement a `mustChangePassword` function
   * 2. Set configuration variables for the following:
   *    - CLIENT_ID
   *    - CLIENT_SECRET
   *    - ISSUER
   */

  const url = require('url@0.10.3');
  const req = context.request;

  function mustChangePassword() {
    // TODO: implement function
    return true;
  }

  if (mustChangePassword()) {
    // User has initiated a login and is forced to change their password
    // Send user's information and query params in a JWT to avoid tampering
    function createToken(clientId, clientSecret, issuer, user) {
      const options = {
        expiresInMinutes: 5,
        audience: clientId,
        issuer: issuer
      };
      return jwt.sign(user, clientSecret, options);
    }

    const token = createToken(
      configuration.CLIENT_ID,
      configuration.CLIENT_SECRET,
      configuration.ISSUER,
      {
        sub: user.user_id,
        email: user.email,
        authorize_again: url.format({
          protocol: 'https',
          hostname: auth0.com,
          pathname: '/authorize',
          query: req.query
        })
      }
    );

    context.redirect = {
      url: `https://example.com/change-pw?token=${token}`
    };
  }

  return callback(null, user, context);
}

Was this helpful?

/

データの保管場所

Auth0のプロファイルに保管するデータは多すぎないようにします。このデータは認証および認可の目的で使用されるものです。Auth0のメタデータと検索機能は、市場調査や高い検索・更新の頻度が必要なものを想定して設計されていません。Auth0をそのような目的で使用すると、ほぼ確実にシステムの拡張性や性能に問題が生じます。データを外部システムに保管して、Auth0にポインター(ユーザーID)を保管した方が、バックエンドシステムが必要に応じてデータを取得できます。従うべきルールはシンプルです。トークンに追加したり決定したりするためにルールで使用する予定のアイテムだけを保管します。

セキュリティに関する考慮事項

情報をフロントチャネルでやり取りすると、悪意のある行為者の攻撃対象になる領域を広げることになります。これは必ず、ルールで対処しなければならない場合(UnauthorizedErrorでの認可試行の拒否など)にのみ行う必要があります。

ただし、直接Auth0に通信を返し、アクセスを制限する指示を与える必要がある場合(CAPTCHA認証やカスタムMFAを実装する場合など)には、その操作の要件が実行されたことをAuth0に安全に伝える方法を確保する必要があります。同様に、リダイレクト先のアプリケーションに情報を渡す必要がある場合、転送された情報が改ざんされていないことを保証する安全な方法を確保する必要があります。

アプリが確実に同じユーザーにログインするようにする

アプリケーションは、ユーザーをリダイレクトしてAuth0テナントに戻します。そのため、そのユーザーに関連するあらゆるデータを、アプリケーションに戻されるIDトークンを介して収集することができます。ただし、その間にいかなる改ざんも行われていないことを保証するため、リダイレクト元と同じユーザーにアプリケーションが確実にログインする必要があります。そのため、要求と一緒にトークンを送信する必要があるかもしれません。

アプリに送信されるトークンには、次の要件があります。

トークン要素 説明
sub ユーザーのAuth0でのuser_idです。
iss ルール自体を特定する識別子です。
aud リダイレクト先のアプリケーションです。
jti ランダムに生成された文字列で、確認のためにユーザーオブジェクト内に保管されます(ルールコードでuser.jti = uuid.v4();を設定し、作成したトークンのjtiとして追加します)。 /continueが呼び出されて、ルールが再び実行されるときにも、まだuser.jtiは設定されたままです。これは仕様に沿ったものです。
exp できる限り短くして、トークンが再利用されないようにします。
other 渡す必要のある他のカスタムクレーム情報です。
signature アプリケーションが安全にシークレットを保管できる場合には、HS256署名を使用することができます。これは、ソリューションの複雑性を大幅に軽減します。また、返されるトークンにも署名が必要なため、これはソリューションの必須条件でもあります。RS256を使うこともできますが、証明書を作成し、期限が切れると更新する必要があります。ルールに直接情報を返さない場合は、この中間アプリにSPAを使用し、RS256を使うようにすれば、アプリケーションでその情報を保管する必要がなくなります。トークンを検証する手段として、イントロスペクションエンドポイントを使用するか、パブリックなJWKSエンドポイントを使用する必要があります。

情報をルールへ戻す

大半のシナリオでは、ルールからアプリケーションに情報を渡します。アプリケーションは、必要なストレージにかかわらず、情報を安全に保管できるはずです。アプリやユーザーメタデータをAuth0で更新することが目的であっても、Management APIを使用できます。ユーザー情報の更新は、ユーザーを/continueエンドポイントにリダイレクトで戻す前に更新が完了する限り、達成されます。ルール自体が情報を取得する必要があり、その情報がこの特定のサインインだけに関連する場合にのみ、セッションが情報をルールに戻すようにします。

/continueエンドポイントに情報を戻す際、渡されるトークンは次の要件に従う必要があります。

トークン要素 説明
sub ユーザーのAuth0 user_idです。
iss リダイレクト先のアプリケーションです。
aud ルール自体を識別する何らかの識別子です。
jti アプリケーションに渡されたトークンに含まれていたのと同じJTIです(注意:user.jtiと一致しなければ失敗します)。
exp トークンの再利用を防ぐため、できる限り短くします。
other 渡す必要がある他のカスタムクレーム情報です。
signature アプリケーションにシークレットを保管する安全な場所がある場合は、HS256での署名を使用できます。これによりソリューションの複雑さが大幅に軽減されます。また、返されるトークンも署名が必要なため、このソリューションでは必須です。RS256も使用できますが、証明書を作成し、期限が切れると証明書を更新しなければなりません。

クエリパラメーターとして渡すのではなく、POSTを使用して送信し、context.request.body.token(または類似のもの)で取得する必要があります。これは、認証のform-postメソッドに似ています。

/continueエンドポイントに情報を戻さないのであれば、有効期間が短く、リプレー攻撃がほぼ不可能な場合を除き、 JTIを拒否リストに登録することをお勧めします。

制約と制限

リダイレクトルールは、以下では動作しません。

context.protocolを確認することで、上記のケースを検出できます。

  • パスワードの交換:context.protocol === 'oauth2-password'

  • リフレッシュトークンの交換:context.protocol === 'oauth2-refresh-token'

  • リソースオーナーのログイン:context.protocol === 'oauth2-resource-owner'

セッションタイムアウト

リダイレクトルールのセッションは、[Login Session Management(ログインセッションの管理)]設定でより短いタイムアウトを設定した場合を除き、通常3日間有効です。これらの設定は、テナントの高度な設定で確認できます。

リソース所有者 のエンドポイント

/oauth/tokenをリソース所有者のパスワード付与で直接呼び出す場合、リダイレクトルールを使用することはできません。そもそもユーザーがリダイレクトフローにいないため、ルールでユーザーをリダイレクトすることはできません。context.redirectを設定しようと試みると、ログイン試行は失敗し、interaction_requiredエラーになります。

prompt=noneの場合のフロー

prompt=noneは、ユーザーが入力を求められるシナリオを避けることを目的としているため、あらゆるリダイレクトの結果はerror=interaction_requiredになります。

ルールは認証セッションの作成後に実行されるため、特定の条件(カスタムMFA、ログインでのCAPTCHAなど)でトークンへのアクセスを阻止しようとするリダイレクトルールがある場合、prompt=noneは使用できません。

prompt=noneの場合、トークンへのアクセスを阻止し、リダイレクトルールをバイパスするリダイレクトフローは作成できません。これは、最初の実行でルールが失敗してもユーザーの認証セッションが作成されるため、試行が失敗すると、ユーザーがprompt=noneで再び呼び出してトークンを取得できるからです。

リフレッシュトークン

リフレッシュトークンの使用には/oauth/tokenへのバックチャネル呼び出しが必要なため、context.redirectを設定した場合、これも失敗します。

ログインに関するどのような制約も、適用されたかを安全に検証することは困難です。MFAチャレンジに成功したユーザーなど、セッションに関する情報を集めるのに使用可能なコンテキストでは、恒常的なセッションIDはありません。そのため、prompt=noneはまったく使用できません。

ルールにcontext.redirect が設定されている場合は常に、prompt=noneが渡された場合、認可はerror=interaction_requiredで失敗します。ところが、ルールが失敗の場合でもユーザーのセッションは作成されるため、ユーザーがすべてのcontext.redirectチャレンジを渡したことを信頼できません。そのため、トークンの取得方法としてprompt=noneは使用できません。

この特有なケースには、リフレッシュトークンの排他的な使用をお勧めします。リフレッシュトークンの生成にチャレンジが必要な場合に、ユーザーがチャレンジに成功したことを確認できるからです。

もっと詳しく