認証トランザクションが完了する前にユーザーをリダイレクトするには、Auth0 Rules を使用できます。これにより、標準のログインフォームに加えて追加のユーザー操作を必要とする、カスタム認証フローを実装できます。リダイレクト Rule は、Auth0 でカスタム (MFA) を実装するためによく使用されますが、次のような用途にも使用できます。
- カスタムのプライバシーポリシーへの同意、利用規約、およびデータ開示フォーム。
- 追加で必要なユーザープロファイルデータを一度だけ安全に収集すること。
- リモートの Active Directory ユーザーがパスワードを変更できるようにすること。
- 不明な場所からログインした場合に、ユーザーに追加の確認を求めること。
- 初回サインアップ時に提供された情報に加えて、ユーザーに関する詳しい情報を収集すること。
1 回の認証フローでユーザーをリダイレクトできるのは 1 回だけです。ユーザーをリダイレクトする Rule が 1 つある場合、後で 2 つ目の Rule を呼び出してそのユーザーをリダイレクトすることは できません。
詳細については、Auth0 の多要素認証 を参照してください。
context.redirect プロパティを次のように設定します。
function (user, context, callback) {
context.redirect = {
url: "https://example.com/foo"
};
return callback(null, user, context);
}
すべてのRulesの実行が完了すると、Auth0はユーザーをcontext.redirect.urlプロパティで指定されたURLにリダイレクトします。また、そのURLにはstateパラメーターも渡されます。例:
https://example.com/foo?state=abc123
リダイレクト URL では、state パラメーターを抽出し、認証トランザクションを再開するために Auth0 に送り返す必要があります。state は不透明な値であり、クロスサイト・リクエスト・フォージェリ (CSRF) 攻撃 を防ぐために使用されます。
リダイレクト後は、ユーザーを /continue エンドポイントにリダイレクトし、受け取った state パラメーターを URL に含めて認証を再開します。元の state を /continue エンドポイントに送り返さないと、Auth0 はログイントランザクションのコンテキストを失い、ユーザーは invalid_request エラーによりログインできなくなります。
例:
を使用している場合:
https://{yourAuth0CustomDomain}/continue?state={originalState}
THE_ORIGINAL_STATE は、Auth0 が生成し、リダイレクト URL に付与して送信した値です。たとえば、Rule が https://example.com/foo にリダイレクトした場合、Auth0 は https://example.com/foo?state=abc123 のようなリダイレクト URL を使用します。つまり、abc123 が THE_ORIGINAL_STATE です。認証トランザクションを再開するには、次の URL にリダイレクトします。
ユーザーが /continue エンドポイントにリダイレクトされた場合:
- すべての Rules が再度実行されます。ただし、認証を続行できるよう、
context.redirect は無視されます。
- ユーザーオブジェクトへの変更は、
/continue エンドポイントを呼び出す前のリダイレクト中に行われます。たとえば、Auth0 の を介した更新は、トランザクションの続行後に反映されます。
ユーザー主導のログインと再開されたログインフローを区別するには、context.protocol プロパティを確認します。
function (user, context, callback) {
if (context.protocol === "redirect-callback") {
// ユーザーは /continue エンドポイントにリダイレクトされました
} else {
// ユーザーは直接ログインしています
}
}
状況によっては、特定の条件下でユーザーにパスワードの変更を強制したい場合があります。その場合は、次のように動作する Rule を作成できます。
- ユーザーがログインを試みますが、パスワードを変更する必要があります。
- ユーザーは、クエリ文字列に JWT を含むアプリケーション固有のページへリダイレクトされます。この JWT は、そのユーザー自身のパスワードのみを変更できることを保証するものであり、アプリケーションで必ず検証する必要があります。
- ユーザーは、アプリケーションが Auth0 Management API を呼び出すことで、アプリケーション固有のページ上でパスワードを変更します。
- ユーザーがパスワードの変更に成功したら、アプリケーションは検証およびデコード済みの JWT から
authorize_again クレームを取得し、その URL にユーザーをリダイレクトして、新しいパスワードでサインインできるようにします。
function(user, context, callback) {
/*
* 前提条件:
* 1. `mustChangePassword` 関数を実装する
* 2. 以下の設定変数を設定する:
* - CLIENT_ID
* - CLIENT_SECRET
* - ISSUER
*/
const url = require('url@0.10.3');
const req = context.request;
function mustChangePassword() {
// TODO: 関数を実装する
return true;
}
if (mustChangePassword()) {
// ユーザーがログインを開始し、パスワードの変更が強制されている
// 改ざんを防ぐため、ユーザー情報とクエリパラメーターをJWTで送信する
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);
}
Auth0のユーザープロファイルにあまり多くのデータを保存しないようにしてください。これらのデータは、認証と認可のために使用することを想定しています。Auth0のメタデータ機能や検索機能は、マーケティング調査のような用途や、検索や更新の頻度が高い用途向けには設計されていません。この目的でAuth0を使用すると、スケーラビリティやパフォーマンスの問題が発生する可能性があります。より適切なのは、データは外部システムに保存し、必要なときにバックエンドシステムがそのデータを取得できるよう、Auth0には参照先としてユーザーIDだけを保存する方法です。簡単な目安としては、トークンに追加したり判断に利用したりするためにRulesで使う予定のある項目だけを保存してください。
フロントチャネルで情報をやり取りすると、 に攻撃される余地が広がります。これを行うのは、Rule で何らかのアクションを実行する必要がある場合 (たとえば UnauthorizedError で認可の試行を拒否する場合) に限るべきです。
一方で、Auth0 に直接情報を返し、アクセス制限のための指示を与える必要がある場合 (キャプチャのチェックやカスタム MFA を実装している場合) は、その処理に必要な要件が満たされたことを Auth0 に安全に伝える手段が必要です。同様に、リダイレクト先のアプリケーションに情報を渡す必要がある場合も、転送される情報が改ざんされていないことを安全に保証できる手段が必要です。
アプリが同じユーザーにログインしていることを確認する
アプリケーションはユーザーを Auth0 テナントにリダイレクトして戻すため、ユーザーに関するデータは、アプリケーションに返される から取得できます。ただし、途中で何らかの改ざんが行われていないことを確認するために、アプリケーションにリダイレクトされてきたユーザーと、実際にログインしているユーザーが同一であることを検証したい場合があります。そのため、通常はリクエストにトークンを含めて送信します。
アプリに送信するトークンは、次の要件を満たす必要があります。
| トークン要素 | 説明 |
|---|
sub | ユーザーの Auth0 user_id。 |
iss | Rule 自体を識別する識別子。 |
aud | リダイレクト先の対象アプリケーション。 |
jti | 確認用としてユーザーオブジェクトに保存される、ランダムに生成された文字列 (Rule コードで user.jti = uuid.v4(); を設定し、その後、作成するトークンに jti として追加します) 。/continue が呼び出されて Rules が再実行されるときも、user.jti は引き続き設定されたままです。これは仕様に準拠しています。 |
exp | トークンの再利用を防ぐため、できるだけ短くする必要があります。 |
other | 渡す必要があるその他のカスタムクレーム情報。 |
signature | アプリケーションにシークレットを安全に保管できる場所がある場合は、HS256 署名を使用できます。これによりソリューションの複雑さを大幅に軽減できます。また、返されるトークンにも署名が必要になるため、これはこのソリューションの要件です。RS256 を使用することもできますが、その場合は証明書の作成と、有効期限切れ時の更新が必要です。Rules に直接情報を返さないのであれば、この中間アプリには SPA を使用でき、アプリケーションで情報を保持しなくて済むように RS256 を選ぶこともできます。その場合は、イントロスペクションエンドポイントまたは公開 JWKS エンドポイントを通じて、トークンを検証する方法が必要です。 |
このトークンは Bearer トークンとして扱わないでください。これはアプリケーション内で使用するための署名付き情報です。アプリケーションは引き続き Auth0 にリダイレクトして、ユーザーを認証する必要があります。
ほとんどの場合、Rule からアプリケーションへ情報を渡したいとしても、通常はアプリケーション側で必要なストレージにその情報を安全に保存できます。Auth0 のアプリメタデータやユーザーメタデータを更新したい場合でも、Management API を使って実行でき、ユーザーを /continue エンドポイントへリダイレクトして戻す前に完了していれば、ユーザー情報は更新されます。Rule 自体がその情報を必要としており、かつその情報がそのサインインセッションにのみ関連する場合に限って、情報を Rule に返すようにしてください。
/continue エンドポイントに情報を返す場合、渡すトークンは次の要件を満たしている必要があります。
| Token Element | Description |
|---|
sub | ユーザーの Auth0 user_id。 |
iss | リダイレクト先のアプリケーション。 |
aud | Rule 自体を識別する何らかの識別子。 |
jti | アプリケーションに渡したトークンに格納されていたものと同じ JTI (注: user.jti と一致しない場合は失敗する必要があります) 。 |
exp | トークンの再利用を防ぐため、できるだけ短くする必要があります。 |
other | 渡す必要があるその他のカスタムクレーム情報。 |
signature | アプリケーションにシークレットを安全に保管できる場所があることを前提に、HS256 署名を使用できます。これによりソリューションの複雑さを大幅に減らせます。また、返送するトークンにも署名が必要になるため、これはこのソリューションの要件です。RS256 を使用することもできますが、その場合は証明書の作成と、有効期限切れ時の証明書更新が必要です。 |
クエリパラメーターとして渡すのではなく、POST で送信し、context.request.body.token (または同等のもの) から取得するようにしてください。これは認証における form-post メソッドに似ています。
/continue エンドポイントに情報を返さない場合は、有効期限が十分に短くてリプレイ攻撃がほぼ不可能でない限り、JTI を denylist に追加することを検討してください。
リダイレクト Rules は、次のケースでは機能しません。
context.protocol を確認することで、上記のケースを判別できます。
- パスワード交換の場合:
context.protocol === 'oauth2-password'
- 交換の場合:
context.protocol === 'oauth2-refresh-token'
- ログインの場合:
context.protocol === 'oauth2-resource-owner'
リダイレクト Rule セッションは通常 3 日間有効です。ただし、ログインセッション管理の設定でより短いタイムアウトを構成している場合は、その設定が適用されます。これらの設定は、テナントの詳細設定 で確認できます。
Resource Owner Password Grant で /oauth/token を直接呼び出す場合、リダイレクト Rule は使用できません。ユーザーはもともとリダイレクト フローにいないため、Rule でユーザーをリダイレクトすることはできません。context.redirect を設定しようとすると、ログインは失敗し、interaction_required エラーが返されます。
prompt=none の目的は、ユーザーの入力が必要になる状況を一切発生させないことにあるため、リダイレクトが発生すると error=interaction_required になります。
Rules は認証セッションの作成後に実行されるため、特定の条件下でトークンへのアクセスをブロックしようとするリダイレクト Rule (カスタム MFA、ログイン時のキャプチャ など) がある場合は、prompt=none を使用できません。
トークンへのアクセスをブロックするリダイレクトフローを作成しつつ、prompt=none の場合にはそのリダイレクト Rule を回避させないようにすることはできません。最初の試行で Rules が失敗しても認証セッション自体は作成されるため、失敗後にユーザーが prompt=none を付けて再度呼び出すだけで、トークンを取得できてしまうからです。
リフレッシュトークンを使用するには /oauth/token へのバックチャネル呼び出しが必要なため、context.redirect を設定すると、これも失敗します。
ログインに対する制限が実際に適用されたことを安全に検証するのは困難です。コンテキスト内には一貫したセッション ID がなく、このユーザーが MFA チャレンジを通過したかどうかといった、セッションに関連する情報を収集するために利用できません。そのため、prompt=none はまったく使用できません。
Rule で context.redirect が設定されていて、prompt=none が渡されている場合、認可は error=interaction_required で失敗します。しかし、Rules が失敗してもユーザーのセッション自体は作成されるため、ユーザーが context.redirect のすべてのチャレンジを通過したと信頼することはできません。したがって、トークンを取得する手段として prompt=none は使用できません。
このケースでは、リフレッシュトークンのみを使用することをお勧めします。リフレッシュトークンの発行にそれらのチャレンジが必要であれば、ユーザーがチャレンジを通過したことを確実に確認できるためです。