アクションを使用してダイレクトする

認証トランザクションの前に、ログイン後のアクションを使ってユーザーをリダイレクトすることができます。カスタム認証フローを実装して、標準のログインフォームにはない追加のユーザー操作に対応できるようになります。

リダイレクトは、Auth0でカスタムの多要素認証(MFA)を実行するためによく使用されますが、以下の目的にも使用できます。

  • カスタムのプライバシーポリシーへの同意、利用規約、データ開示のフォームを使用できるようにする

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

  • Microsoft Entra IDのリモートユーザーがパスワードを変更できるようにする

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

  • サインアップ時にユーザーが提供したよりも多くのユーザー情報を集める

概要

リダイレクトのアクションは通常、以下のように動作します。

  1. アクションがURLへリダイレクトを送信します。

  2. そのアクションの実行が完了すると、Actionsパイプラインが中断されます。

  3. ユーザーがstateパラメーターとURLともににリダイレクトされます。

  4. 外部フローが終了すると、外部サイトがstateパラメーターとともにユーザーを/continueエンドポイントにリダイレクトします。

  5. リダイレクトを行った同じアクションからActionsパイプラインが再開します。

リダイレクトを開始する

以下のようにapi.redirect.sendUserTo()機能を呼び出します。

/**
* @param {Event} event - Details about the user and the context in which they are logging in.
* @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
*/
exports.onExecutePostLogin = async (event, api) => {
  api.redirect.sendUserTo("https://my-app.exampleco.com");
};

Was this helpful?

/

Actionsがこのアクションの実行を終了し、Actionsパイプラインを一時停止してユーザーをhttps://my-app.exampleco.comに送信します。つまり、このリダイレクトを行ったアクションの後にログイン後トリガーが実行される場合、そのログイン後トリガーにバインドされているアクションはすべて、認証フローが再開するまで実行されないことになります。リダイレクトのルールに慣れているのであれば、リダイレクトのアクションとリダイレクトのルールがこの点で大きく違うことに注意してください。

アクションの実行が完了すると、Auth0がユーザーをapi.redirect.sendUserTo()関数でURL指定された場所にリダイレクトします。Auth0はそのURLにstateパラメーターも渡します。例:

https://my-app.exampleco.com/?state=abc123

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

認証フローを再開する

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

例:

https://{yourAuth0Domain}/continue?state=THE_ORIGINAL_STATE

この例では、THE_ORIGINAL_STATEはAuth0が生成し、リダイレクトURLに送信した値です。たとえば、アクションがhttps://my-app.exampleco.com/にリダイレクトすると、Auth0はhttps://my-app.exampleco.com/?state=abc123と同様にリダイレクトURLを使用し、abc123THE_ORIGINAL_STATEにします。認証トランザクションを再開するには、以下にリダイレクトします。

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

ユーザーが/continueエンドポイントにリダイレクトされると、ActionsパイプラインがonContinuePostLogin関数を呼び出して、リダイレクトの呼び出しと同じアクションで再開します。リダイレクトが正しく動作するには、リダイレクトを行った同じアクションに以下の署名を含む関数が必要です。

/**
* @param {Event} event - Details about the user and the context in which they are logging in.
* @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
*/
exports.onExecutePostLogin = async (event, api) => {
  api.redirect.sendUserTo("https://my-app.exampleco.com");
};

/**
* @param {Event} event - Details about the user and the context in which they are logging in.
* @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
*/

exports.onContinuePostLogin = async (event, api) => {
}

Was this helpful?

/

データを外部のサイトへ渡す

データを外部サイトに渡す場合は、そのデータを署名済みのJWTでエンコードすることをお勧めします。そうすれば、アプリケーションは送信中にデータが改ざんされていないことを確信できます。Actionsではapi.redirect.encodeTokenおよびapi.redirect.sendUserTo関数を使用してこれを実行できます。

/**
* @param {Event} event - Details about the user and the context in which they are logging in.
* @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
*/
exports.onExecutePostLogin = async (event, api) => {
  const YOUR_AUTH0_DOMAIN = event.secrets.YOUR_AUTH0_DOMAIN || event.request.hostname

  // Craft a signed session token
  const token = api.redirect.encodeToken({
    secret: event.secrets.MY_REDIRECT_SECRET,
    expiresInSeconds: 60, 
    payload: {
      // Custom claims to be added to the token
      email: event.user.email,
      externalUserId: 1234,
      continue_uri: `https://${YOUR_AUTH0_DOMAIN}/continue`
    },
  });

  // Send the user to https://my-app.exampleco.com along
  // with a `session_token` query string param including
  // the email.
  api.redirect.sendUserTo("https://my-app.exampleco.com", {
    query: { session_token: token }
  });
}

Was this helpful?

/

上のコードは、リダイレクトで使用されるURLにsession_tokenクエリ文字列パラメーターを(Auth0が自動的に追加するstateパラメーターに加えて)追加します。このトークンには以下が含まれます。

トークンの要素 説明
sub ユーザーのAuth0のuser_idです。
iss Auth0テナントドメインのホスト名(example.auth0.comなど)です。
exp expiresInSecondsパラメーターで指定される秒単位の有効期限です。トークンが再利用されないように、できるだけ短くします。デフォルトは900秒(15分)です。
ip 認証要求元のIPアドレスです。
email payload.emailパラメーターで値が指定されているカスタムクレームです。
externalUserId payload.externalUserIdパラメーターで値が指定されているカスタムクレームです。
signature トークンは、上記で指定のシークレットを使用して、HS256アルゴリズムで署名されます。

トークンが改ざんされていないことを確認する

外部のシステムは、このトークンが送信中に改ざんされていないことを検証しなければなりません。これを実現するために、リモートシステムはトークンの署名が有効であることを確認し、該当する場合は、外部システム内のセッションがトークンのsubクレームで渡される同じAuth0ユーザーに属していることを確認しなければなりません。

データをAuth0へ戻す

ユーザーが外部サイトでカスタムフローを完了すると、/continueエンドポイントにリダイレクトされます。状況によっては、データをAuth0に戻して、そのユーザーの認証フローや認可フロー(たとえば、CAPTCHA認証やカスタムMFAなど)に影響を与える必要があるかもしれません。

できるだけアプリのメタデータを使用する

可能であれば、リモートシステムはAuth0 Management APIを使用して、カスタム情報をAuth0ユーザープロファイルのアプリケーションメタデータとして保管する必要があります。Auth0アクションフローが再開されると、この情報はevent.user.app_metadataオブジェクトで利用できるようになります。この方法では、機密情報をフロントチャネルでAuth0に渡す必要がなくなります。

Auth0のユーザープロファイルにはデータを選択して保管する

Auth0のプロファイルに保管するデータは多すぎないようにします。このデータは認証および認可の目的で使用されるものです。Auth0のメタデータや検索機能は、マーケティング調査などのように、頻繁に検索や更新されることを想定して設計されたものではありません。Auth0をそのような目的で使用すると、ほぼ確実にシステムの拡張性や性能に問題が生じます。

アプリケーションにかなりの量のユーザーデータが必要な場合には、データを外部システムに保管して、Auth0に外部キー(ユーザーID)を保管すると、バックエンドシステムが必要に応じてデータを取得できるようになります。

データをフロントチャネルに送信する

情報をフロントチャネルでやり取りすると、悪意のある行為者の攻撃対象になる領域を広げることになります。情報をフロントチャネルに送信しなければならない場合には、以下のガイダンスを考慮してください。

情報をアクションへ戻す

機密情報をAuth0に送信して戻すには、必ず署名済みのセッショントークンを使用します。このトークンは、アクション内で以下のコードを使って手軽に検証することができます。

/**
 * @param {Event} event - Details about the user and the context in which they are logging in.
 * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
 */
exports.onContinuePostLogin = async (event, api) => {
  const payload = api.redirect.validateToken({
    secret: event.secrets.PRECONFIGURED_SECRET,
    tokenParameterName: 'my_token',
  });

  // use the data encoded in the token, such as: 
  api.idToken.setCustomClaim('color', payload.favorite_color);
}

Was this helpful?

/

トークンは以下を確実にするために検証されます。

  • 署名が有効である

  • トークンの有効期限が切れていない

  • トークン内のstateクレームは、リダイレクトの一部として使用されるstateパラメーターと一致します。

トークンの要素 説明
sub ユーザーのAuth0のuser_idです。
iss リダイレクト対象のアプリケーションです。
exp トークンが再利用されないように、できるだけ短くします。
state リダイレクトの一部としてリモートサイトに送信される状態パラメーターです。リプレイ攻撃を防ぐために、トークンに含まれる必要があります。
other 上のコードでpayloadとして公開されるその他すべてのカスタムクレームです。
signature トークンはHS256アルゴリズムで署名されなければなりません。

リプレイ攻撃を回避するには、/continueエンドポイントにPOST要求を行って、トークンをAuth0に送り返す必要があります。コード内のtokenParameterNameオプションを使用すると、トークンを含むフィールドの名前を指定できます。

カスタム認証方式

ログインパイプラインでリダイレクトに成功したら、アクションはカスタム認証方法のイベントをユーザーセッションに記録することができます。event.authentication.methods配列には、ユーザーのブラウザーセッションが継続する間、カスタム認証方法のエントリが含まれます。この配列の各エントリーには、認証方法が記録された日時を示すタイムスタンプがあります。

必要なカスタム認証方法がevent.authentication.methods配列にない場合、またはエントリが古すぎる場合には、カスタムアクションによってリダイレクトがトリガーされることがあります。

api.redirect.sendUserTo()を使用して、カスタム認証方法を実装するページにユーザーを送信できます。exports.onContinuePostLoginハンドラーでapi.authentication.recordMethod()を使用して、完了した認証方法の記録をユーザーのセッションに保管できます。

event.authentication.methods配列に保管されるレコードには、api.authentication.recordMethod()で選択されたURLと一致するnameプロパティが含まれます。ここでキャプチャされたURLによって、現在のトランザクションで完了された認証方法を検索し、カスタム認証方法がすでに完了されているかを判断できます。

ワークフローで、ユーザーセッションの有効期間中にカスタム認証方法の定期的な再実行が必要になるかもしれません。たとえば、カスタムMFAの使用では、指定された時間が経過すると、ユーザーの再検証が必要になることがあります。

以下の例では、カスタム認証方法を返す契機を決めるために、既存のレコードのタイムスタンプを照合します。

const CUSTOM_METHOD_URL = "https://path.to.prompt";
const PROMPT_TTL = 1000 * 60 * 60 * 24; // 24h

/**
 * Handler that will be called during the execution of a PostLogin flow.
 *
 * @param {Event} event - Details about the user and the context in which
 * they are logging in.
 * @param {PostLoginAPI} api - Interface whose methods can be used to
 * change the behavior of the login.
 */
exports.onExecutePostLogin = async (event, api) => {
  // Search authentication method records for an entry representing our
  // custom method.
  const methodRecord = event.authentication?.methods.find((record) =>
    validateCustomRecord(record, CUSTOM_METHOD_URL, PROMPT_TTL)
  );

  if (!methodRecord) {
    const sessionToken = api.redirect.encodeToken({
      payload: {
        user_id: event.user.user_id,
      },
      secret: event.secrets.SESSION_TOKEN_SECRET,
    });

    // We didn't find a valid record, so we send the user to the
    // URL that implements the custom method with the signed
    // data we encoded in `sessionToken`.
    api.redirect.sendUserTo(CUSTOM_METHOD_URL, {
      query: { session_token: sessionToken },
    });
  }
};

/**
 * Handler that will be invoked when this action is resuming after an
 * external redirect. If your onExecutePostLogin function does not perform
 * a redirect, this function can be safely ignored.
 *
 * @param {Event} event - Details about the user and the context in which
 * they are logging in.
 * @param {PostLoginAPI} api - Interface whose methods can be used to
 * change the behavior of the login.
 */
exports.onContinuePostLogin = async (event, api) => {
  const payload = api.redirect.validateToken({
    secret: event.secrets.SESSION_TOKEN_SECRET,
    tokenParameterName: "session_token",
  });

  if (!validateSessionToken(payload)) {
    return api.access.deny("Unauthorized");
  }

  // Record the completion of our custom authentication method.
  // THIS NEW API IS ONLY AVAILABLE IN `onContinuePostLogin`.
  api.authentication.recordMethod(CUSTOM_METHOD_URL);
};

function validateCustomRecord(record, url, ttl) {
  if (!record) {
    // No record means it isn't valid.
    return false;
  }

  if (record.url !== url) {
    // This isn't a record of our custom method.
    return false;
  }

  // Timestamps are rendered as ISO8601 strings.
  const timestamp = new Date(record.timestamp);

  // The record is valid if it was recorded recently enough.
  return timestamp.valueOf() >= Date.now() - ttl;
}

function validateSessionToken(payload) {
  // Custom validation logic for the data returned by the
  // custom method goes here.
  return true;
}

Was this helpful?

/

api.authentication.recordMethod() APIは、exports.onContinuePostLoginハンドラーでのみ使用できます。リダイレクトの完了後にカスタム認証方法が記録されるため、ログインが悪用される可能性が軽減されます。

制約と制限

リダイレクトのアクションは以下では動作しません。

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

リソース所有者パスワードフローでAuthentication APIのトークン取得エンドポイントを呼び出す場合、リダイレクトアクションは使用できません。そもそもユーザーがリダイレクトフローにいないため、アクションでリダイレクトできるユーザーもいません。

prompt=noneのフロー

prompt=noneの目的は、ユーザーに入力を求めるシナリオを回避することであるため、リダイレクトするとerror=interaction_requiredが発生します。

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

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

リフレッシュトークン

リフレッシュトークンを使用するには、Authentication APIのトークン取得エンドポイントへのバックチャネル呼び出しが必要であるため、リダイレクトしようとするとこれも失敗します。

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

アクションでapi.redirect.sendUserTo()が呼び出されるときに、prompt=noneが渡された場合、error=interaction_requiredで認可が失敗しますが、アクションが失敗してもユーザーのセッションが作成されるため、ユーザーがリダイレクトチャレンジに合格したことを信頼できず、トークンを取得する方法としてprompt=noneを使用することはできません。

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

もっと詳しく