ユーザーアカウントをリンクする
ユーザーアカウントをリンクして、プライマリとセカンダリの関係を形成します。リンクが成功すると、エンドポイントはプライマリアカウントIDの新しい配列を返します。
Auth0は、様々なIDプロバイダーのユーザーアカウントのリンクをサポートします。これにより、ユーザーはどのアカウントからでも認証でき、アプリによって認識され、同じユーザープロファイルに関連付けられます。
利用可能性はAuth0プランとログイン方式によって異なる
この機能が利用できるかどうかは、使用しているログイン実装とAuth0プラン(または契約)によります。詳細については、「価格設定」をお読みください。
Auth0 は、デフォルトですべてのIDを個別のものとして扱います。たとえば、ユーザーが最初にAuth0データベースにログインし、次にGoogle またはFacebook経由でログインした場合、Auth0ではこれら2つの試行は2人の別々のユーザーとして表示されます。
アカウントのリンクには3つの方法があります。
アカウントリンク拡張機能の使用
Management APIの使用
Auth0.jsの使用
各オプションの詳細を確認するには、下のタブを使用してください。
アカウントリンク拡張機能
Dashboardのアカウントリンク拡張機能をインストールして構成すると、2つ目のアカウントを作成したユーザーの初回ログイン時に、古いアカウントと新しいアカウントをリンクするよう求めることができます。ユーザーは、2つのアカウントをリンクするか、別々にしておくかを選択できます。
Auth0 Management APIが提供するユーザーアカウントのリンクエンドポイントは、次の2つの方法で呼び出すことができます:
- ユーザーが開始するアカウントリンクで、
update:current_user_identities
スコープを含むアクセストークンを使用する - サーバー側のアカウントリンクで、
update:users
スコープを含むアクセストークンを使用する
ユーザーが開始するクライアント側のアカウントリンク
クライアント側のコードからユーザーが開始するアカウントリンクの場合は、ペイロードに次の項目を含んでいるアクセストークンを使用します:
update:current_user_identites
スコープ- URLの一部としてプライマリアカウントの
user_id
- セカンダリアカウントのIDトークンが
RS256
で署名されていること、クライアントを識別するaud
クレームが要求アクセストークンのazp
クレームの値と一致していること。update:current_user_identities
スコープを含むアクセストークンは、現在ログインしているユーザーの情報を更新する場合のみ使用できます。そのため、このメソッドは、ユーザーがリンクプロセスを開始する場合に適しています。
{
"method": "POST",
"url": "https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities",
"httpVersion": "HTTP/1.1",
"headers": [{
"name": "Authorization",
"value": "Bearer ACCESS_TOKEN"
},
{
"name": "content-type",
"value": "application/json"
}],
"postData" : {
"mimeType": "application/json",
"text": "{\"link_with\":\"SECONDARY_ACCOUNT_ID_TOKEN\"}"
}
}
Was this helpful?
サーバー側のアカウントリンク
サーバー側のアカウントリンクの場合は、ペイロードに次の項目を含んでいるアクセストークンを使用します:
update:users
スコープ- URLの一部としてプライマリアカウントの
user_id
- セカンダリアカウントの
user_id
- セカンダリアカウントのIDトークンが
RS256
で署名されていること、クライアントを識別するaud
クレームが要求アクセストークンのazp
クレームの値と一致していること。update:users
スコープを含むアクセストークンは、任意のユーザーの情報更新に使用できます。そのため、このメソッドは、サーバー側コードでの使用のみを意図しています。
{
"method": "POST",
"url": "https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities",
"httpVersion": "HTTP/1.1",
"headers": [{
"name": "Authorization",
"value": "Bearer ACCESS_TOKEN"
},
{
"name": "content-type",
"value": "application/json"
}],
"postData" : {
"mimeType": "application/json",
"text": "{\"provider\":\"SECONDARY_ACCOUNT_PROVIDER\", \"user_id\": \"SECONDARY_ACCOUNT_USER_ID\"}"
}
}
Was this helpful?
SECONDARY_ACCOUNT_USER_ID
とSECONDARY_ACCOUNT_PROVIDER
は、ユーザーの一意のIDによって推測できます。たとえば、ユーザーIDがgoogle-oauth2|108091299999329986433
の場合、要求でgoogle-oauth2
の部分をprovider
、108091299999329986433
の部分をuser_id
として設定します。provider
とuser_id
の代わりに、セカンダリアカウントのIDトークンをペイロードの一部として送信できます:
{
"method": "POST",
"url": "https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities",
"httpVersion": "HTTP/1.1",
"headers": [{
"name": "Authorization",
"value": "Bearer ACCESS_TOKEN"
},
{
"name": "content-type",
"value": "application/json"
}],
"postData" : {
"mimeType": "application/json",
"text": "{\"link_with\":\"SECONDARY_ACCOUNT_ID_TOKEN\"}"
}
}
Was this helpful?
Auth0.jsライブラリーを使用できます。まず、Management APIの呼び出しに使用できるアクセストークンを取得しなければなりません。それには、Auth0.jsを初期化する際にhttps://{yourDomain}/api/v2/
オーディエンスを指定します。認証フローの一部としてアクセストークンを取得します。代わりに、checkSession
メソッドを使用することもできます。アクセストークンを取得したら、そのアカウントのAuth0ドメインとアクセストークンを渡して新しいauth0.Management
インスタンスを作成することができます。詳細については、Auth0.js > ユーザー管理をご覧ください。
ルールを使用して不足している情報を追加する
ユーザーがログインすると、アプリはプライマリIDからユーザー情報を受け取ります。Auth0は、セカンダリIDの情報を使用して、不足しているプロファイルフィールドを自動的に補完しようとしません。たとえば、プライマリIDがデータベース接続から取得され、given_name
プロパティとfamily_name
プロパティが欠落しており、セカンダリIDがユーザーの姓名を含むGoogleソーシャル接続から取得されている場合、アプリケーションは2番目のIDに含まれるデータを受信しません。
セカンダリIDの情報を使用してプライマリIDに不足している情報を追加するには、次の例のようなルールを記述します。
function(user, context, callback) {
const propertiesToComplete = ["given_name", "family_name", "name"];
// go over each property and try to get missing
// information from secondary identities
for(var property of propertiesToComplete) {
if (!user[property]) {
for(var identity of user.identities) {
if (identity.profileData && identity.profileData[property]) {
user[property] = identity.profileData[property];
break;
}
}
}
}
callback(null, user, context);
}
Was this helpful?
Actionsを使ったアカウントリンク
Auth0 Actionsを使用して、Management APIを呼び出してユーザーアカウントをリンクできます。Auth0 は、アカウントのリンク後に正しいプライマリユーザーに自動的に変更されないため、アカウントのリンクが成功したらアクションコード内で変更してください。
Actionsを使ったアカウントリンク
ActionsはAuth0機能を柔軟に拡張できますが、ユーザーアカウントをリンクするときには注意が必要です。
アカウントリンクが安全でないと、悪意のあるアクターが正当なユーザーアカウントにアクセスできるようになってしまいます。
以下の点を常に念頭に置いてください:
アカウントを手動でリンクする際には毎回、ユーザーに資格情報の入力を求めようにします。リンクさせる前には必ず、テナントが両方のアカウントに認証を要求するようにします。
Actionsを使ったアカウントリンクの例
基本的なアカウントリンクの実装は次のとおりです。
アクションにリンクする可能性のあるユーザーアカウントを識別します。
アクションのリダイレクト機能とトークンを使用して、外部リンクアプリにリダイレクトします。
リンクするすべてのアカウントに対して、ユーザーの資格情報を使用して認証することを要求します。
署名済みJWTにエンコードされた認証の結果を使用してアクションにリダイレクトし、そのトークンの信頼性と内容を検証します。
結果に基づいて、Management API呼び出しでアカウントリンクを実行します。
アクションを使用して、トランザクションの残りの部分をプライマリユーザーに切り替えます。
これらの手順を実行するために、ログイン後のアクションの例には次の内容が含まれます。
// @ts-check
const { ManagementClient } = require("auth0");
// High-level Workflow for Performing Account Linking in Actions
// --------------------------------------------------------------------------------------------
//
// The goal of this workflow is to systematically process all users for potential account
// linking. We want to detect situations where an end-user
// may have other identities in Auth0. These other identities would be discovered through
// matching verified email addresses. The Auth0 Action will ensure that all users are processed
// for account linking.
//
// A redirect app will be hosted by the customer to which we will redirect the user's browser
// when account linking might be available. The customer's account linking app is responsible
// authenticating that the current user owns the candidate linking identities. It will actually
// perform the account linking via Management API before redirecting back to the login flow's
// `/continue?state` endpoint.
//
// The Action will pick up here and update the primary user for the login if necessary and mark
// the user as having been successfully processed.
//
// Here are the details of the workflow:
//
// 1. Check if the user has been processed for account linking. The state for this is encoded in
// the user's `app_metadata`. If the user has already been processed, exit this flow.
// 2. Discover other user identities that are candidates for account linking. If candidates are
// found:
// 1. Encode the current user and any candidate identities into a signed session token to be
// passed to the account linking app.
// 2. Redirect the user's browser to the account linking app with the signed session token.
// 3. The account linking app should challenge the user to authenticate using the candidate
// identities.
// 4. If the user choses to proceed with account linking and successfully authenticates with
// candidate identities, determine the primary user under which to consolidate identities.
// 5. Use the management API to perform account linking and to map any user or app metadata
// from the other user accounts into the new primary user account.
// 6. Redirect back to `/continue?state` with a new signed token encoding the primary user
// that is the outcome of the account linking app.
// 7. Validate the returned session token in the `onContinuePostLogin` entrypoint of the
// Action. If a change of primary user is required, change the primary user via
// `api.authentication.setPrimaryUser(newPrimaryUserId)`.
// 3. Mark the user as having been processed for account linking by storing a flag in the
// user's `app_metadata`.
const LINKING_STATE_KEY = 'account_linking_state';
/**
* 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) => {
if (!event.user.email_verified) {
// We won't process users for account linking until they have verified their email address.
// We might consider rejecting logins here or redirecting users to an external tool to
// remind the user to confirm their email address before proceeding.
//
// In this example, we simply won't process users unless their email is verified.
return;
}
const accountLinkingState = event.user.app_metadata[LINKING_STATE_KEY];
if (accountLinkingState) {
// Account linking has already been processed and completed for this user. No further work
// to be done in this Action.
return;
}
const token = await getManagementApiToken(event, api);
const domain = event.secrets.TENANT_DOMAIN;
const management = new ManagementClient({ domain, token });
// Search for other candidate users
const { data: candidateUsers } = await management.usersByEmail.getByEmail({
email: event.user.email,
});
if (!Array.isArray(candidateUsers)) {
return;
}
const candidateIdentities = candidateUsers.flatMap((user) => user.identities);
if (!candidateIdentities.length) {
// No candidate users for linking so mark the user as processed.
api.user.setAppMetadata(LINKING_STATE_KEY, Date.now());
}
// Encode the current user and an array of their
const sessionToken = api.redirect.encodeToken({
payload: {
current_user: event.user,
candidate_identities: candidateIdentities,
},
secret: event.secrets.REDIRECT_SECRET,
expiresInSeconds: 20,
});
api.redirect.sendUserTo('https://url.for/account/linking/service', {
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) => {
// Validate the session token passed to `/continue?state` and extract the `user_id` claim.
const { user_id } = api.redirect.validateToken({
secret: event.secrets.REDIRECT_SECRET,
tokenParameterName: 'session_token',
});
if (user_id !== event.user.user_id) {
// The account linking service indicated that the primary user changed.
api.authentication.setPrimaryUser(user_id);
}
// Mark the user as having been processed for account linking
api.user.setAppMetadata(LINKING_STATE_KEY, Date.now());
};
Was this helpful?