mcpField
I
internet-identity

internet-identity

github.com/dfinityセキュリティ39337 回インストール今日更新
リポジトリ

internet-identity について

Internet Computer アプリに Internet Identity 認証を実装するためのリファレンススキルです。IC SDK を使う開発者向けに、サインイン、セッション、SSO、ID 属性の適切な扱いを説明します。

主な機能

  1. 01パスキーと OpenID のサインイン手順
  2. 02組織 SSO の設定ガイド
  3. 03セッションとサブドメイン共有の方法
  4. 04ID 属性の検証ガイド
  5. 05ローカル Internet Identity 開発環境の設定

活用シーン

  1. 01IC アプリへの Internet Identity サインイン導入
  2. 02兄弟サブドメイン間でのサインイン共有設定
  3. 03バックエンドで検証済み ID 属性を読み取る

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/dfinity/icskills --skill internet-identity

インストール後、Claude Code で/internet-identityを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
app-metadata.md
enterprise-sso.md
older-api.md
rust-identity-attributes.md
shared-sessions.md

詳細

Internet Identity による認証

概要

Internet Identity(II)は、Internet Computer ネイティブの認証システムです。ユーザーはパスキー、OpenID アカウント(Google、Apple、Microsoft)、または所属組織独自の SSO を使って、II 対応アプリにサインインできます。ユーザー名やパスワードは不要です。ユーザーごとにアプリのオリジン単位で一意のプリンシパルが割り当てられ、アプリ間でのトラッキングを防ぎます。

このスキルの参考資料は references/ にあります。

  • references/shared-sessions.md:隣接するサブドメイン間で1つのサインインを共有する方法を、最初から最後まで説明します。
  • references/app-metadata.md:/.well-known/ii-app-metadata のすべての検証ルールです。
  • references/enterprise-sso.md:組織側での SSO の設定(OIDC クライアント、ディスカバリーファイル、制限、キャッシュ)です。
  • references/rust-identity-attributes.md:identity-attributes クレートを使った、アイデンティティ属性用の Rust バックエンドです。
  • references/older-api.md:@icp-sdk/auth 10.x 以前との違いです。

前提条件

  • @icp-sdk/auth@^11 と @icp-sdk/core@^6。両方のメジャーバージョンをそろえてください。 auth 11 と 10 は @icp-sdk/core@^6 に依存し、auth 9 は ^5 に依存します。
  • Motoko バックエンドの場合:mo:identity-attributes >= 0.4.0(mops)。2つのサインインメソッドを追加し、属性バンドルを検証するミックスインです。mo:core >= 2.5.0 が取り込まれ、include に moc >= 1.6.0 が必要です。

Canister ID

CanisterIDURL用途
Internet Identity(バックエンド)rdmx6-jaaaa-aaaaa-aaadq-cai委任を発行し、属性バンドルに署名します
Internet Identity(フロントエンド)uqzsh-gqaaa-aaaaq-qaada-caihttps://id.aiサインイン UI を提供します。authorizeUrl はここを指します

両方の ID はメインネットとローカルネットワークで同一です。ハードコードしてください。

ビルドを壊す間違い

  1. 環境に合わない II の URL を使う。 authorizeUrl はフロントエンドの canister を指します。メインネットでは https://id.ai/authorize、ローカル II(ii: true)では http://id.ai.localhost:8000/authorize です。URL はそのまま使用されるため、/authorize を含めてください。https://id.ai では II のホームページが開くだけで、委任は返されません。

  2. identityProvider に文字列、またはその一部だけを渡す。 これはオブジェクト { authorizeUrl, canisterId } で、両方がそろっている必要があります。文字列や URL を渡すと TypeError が発生します。メインネットの II を使うには、このオプションを省略してください。ほとんどのアプリではこれが適切です。

  3. フロントエンドコードでトップレベルの await を使う。 Vite 6 以前はデフォルトで es2020 世代のブラウザーを対象にしており、トップレベルの await が使えないため、ビルドに失敗します。Vite 7 では使用できます。セットアップを async function init()(またはフレームワークのライフサイクルフック)で囲んでください。この方法で書けば、どのバージョンでもビルドできます。コンパイルを通すために build.target を esnext に変更してはいけません。

  4. maxTimeToLive を署名鍵の有効期間として扱う。 これは II でのセッションの期間を制限します。また、maxTimeToIdle は使われないセッションを終了します。呼び出しの署名に使われる委任は有効期間が短く、クライアントによって置き換えられます。アプリ独自のポリシーがない限り、どちらも設定しないでください。II はアイドル状態が7日間、合計30日間でセッションを終了します。

  5. signIn() を await しない、または try/catch を省略する。 ユーザーがポップアップを閉じた場合や認証に失敗した場合、signIn() は拒否されます。

  6. ブラウザーコードで shouldFetchRootKey や fetchRootKey() を使う。 フロントエンド canister または Vite 開発サーバーが設定する ic_env Cookie には、IC_ROOT_KEY としてルートキーが含まれています。これを HttpAgent.create() の rootKey として渡し、host は設定しないでください。実行時にルートキーを取得すると、メインネットで中間者が偽のキーを差し込めるようになります。

  7. プリンシパルとして 2vxsx-fae を取得する。 これは匿名プリンシパルです。認証がひそかに失敗しています。よくある原因は、誤った authorizeUrl(特に /authorize の付け忘れ)、処理されていない signIn() の拒否、または signIn() が解決する前にアイデンティティを読み取ることです。このオリジンが認証情報を保持していないサインインが存在する場合、getIdentity() は匿名アイデンティティではなく SessionNotHeldError をスローします。

  8. 主体を引数として渡すこと。 バックエンドは IC プロトコルから呼び出し元を読み取ります。Motoko では msg.caller、Rust では ic_cdk::api::msg_caller() です。アクセス制御のパターンについては、canister-security スキルを参照してください。

  9. ゲートウェイドメインに derivationOrigin または ii-alternative-origins を追加すること。 II は ic0.app、icp0.io、icp.net を同じ形式に正規化するため、これらのいずれかで提供される canister はすべて同じプリンシパルを取得します。これを変更する設定を追加すると、認証が機能しなくなります。そこで異なるプリンシパルが使われる場合は、別のパスキーまたはデバイスが使われています。本当に別のオリジン(カスタムドメイン)がある場合は、設定が必要です。「複数のオリジンでアプリを提供する」を参照してください。

  10. フロントエンドで属性のノンスを生成すること。 requestAttributes に渡すノンスはバックエンド(_internet_identity_sign_in_start)から取得する必要があります。そうしないと、canister はバンドルの implicit:nonce が自ら発行したものか確認できず、リプレイ保護が失われます。

  11. 署名者を検証せずに属性データを読み取ること。 IC が検証するのは署名であり、署名者ではありません。どの canister でも有効なバンドルを生成できます。信頼できる署名者は rdmx6-jaaaa-aaaaa-aaadq-cai です。trusted_attribute_signers と frontend_origins を設定したうえで、identity-attributes ライブラリ(Motoko では mo:identity-attributes ミックスイン、Rust では #[identity_attributes] 属性)を使用してください。独自に実装する Rust コードでは、msg_caller_info_data() を読み取る前に msg_caller_info_signer() を確認する必要があります。確認しないと、攻撃者の canister が email = "[email protected]" を偽造できてしまいます。

  12. Microsoft のスコープ付きキーのプレフィックスで {tid} を置き換えること。 microsoft プロバイダーの URL はリテラルの https://login.microsoftonline.com/{tid}/v2.0 です。キーは正確に openid:https://login.microsoftonline.com/{tid}/v2.0:email のようになります。テナント ID を埋め込むと、すべての検索に一致しなくなります。

  13. email を検証済みとして扱うこと。 email はユーザーの II にリンクされたアカウントから取得した未加工のアドレスです。II はこれを確認しないため、ユーザー入力として扱ってください。verified_email が存在するのは、II がユーザーによるアドレスの所有を確認できた場合のみです。これは、OpenID プロバイダー(例:Google)が検証済みとして示した場合、またはユーザーがそのメールアドレスを II に直接リンクして検証した場合です。アクセスの許可には verified_email のみを使用してください。sso: の下に verified_email はありません。 組織の SSO ではこれを生成できません。sso:<domain>:email はその組織独自のプロバイダーによって申告されるため、一覧に含めたドメインについてのみ信頼してください。

  14. 共有 derivationOrigin を設定せずに Cookie ドメインを共有すること。 サブドメイン間でサインインを共有するには、同じプリンシパルを共有する必要があります。すべてのサブドメインで許可された導出オリジンが設定されていない場合、共有レコードには読み取り元オリジンが決して保持できないアカウントが記録され、/reauth はユーザーをいつまでもリダイレクトし続けます。references/shared-sessions.md を参照してください。

  15. デフォルトのトランスポートを使う、hint を指定しない、または宣言されていないコールバックを指定して、無言で再発行すること。 II に複数のセッションがある場合、hint を指定しない prompt: 'none' は InteractionRequiredError(reason は account_selection_required)で失敗します。これはユーザー操作なしでページ読み込み時に実行されるため、専用ルートで transport: 'redirect' を指定する必要があります。また、そのルートをオリジンの /.well-known/ii-auth-callbacks で宣言しないと、リダイレクトから戻れません。

  16. /.well-known/ii-app-metadata を誤ったオリジンで提供する、CORS を設定しない、またはフィールドを誤って設定すること。 II は導出オリジン(設定されている場合)、またはリクエスト元のオリジンからこれを読み取ります。ドキュメントとロゴの両方に CORS が必要で、無効なフィールドが1つでもあるとドキュメント全体が無効になります。references/app-metadata.md を参照してください。

  17. 互換性のないメジャーバージョンをインストールすること。 auth 11 は @icp-sdk/core@^6 をピア依存関係として要求します。慣例で core を ^5 に固定すると、次のエラーが発生します。

    npm error ERESOLVE unable to resolve dependency tree
    npm error Found: @icp-sdk/[email protected]
    npm error peer @icp-sdk/core@"^6" from @icp-sdk/[email protected]
    

    --legacy-peer-deps や --force で解決しようとしないでください。@icp-sdk/auth@^11 と @icp-sdk/core@^6 を固定するか、別の理由で core 5 を使い続ける必要がある場合は @icp-sdk/auth@^9 を使ってください。

  18. agentOptions を指定せずにローカル II にサインインする。 クライアントは、デフォルトでメインネットのルートキーを検証するエージェントを通じて II canister を呼び出し、委任を発行します。ローカル II では認証処理が完了した後、発行に失敗し、TrustError: Certificate verification error("Invalid signature")が発生します。ic_env Cookie から agentOptions: { rootKey } を渡し、host は未設定のままにします。古いネットワークランチャーのローカル II には発行メソッドがありません。icp network update を実行し、ネットワークを再起動してください。

  19. 委任の有効期限に基づいてログアウトをスケジュールする。 委任の getIdentity() が署名するIDは有効期間が短く、時間の経過とともに置き換えられるため、identity.getDelegation() の有効期限を基にしたタイマーでは、数分後にユーザーがサインアウトされます。セッションの終了は expired ステータスとして通知されます。これをサブスクライブし、isAuthenticated() が false になったらサインイン済みのビューを終了してください。

  20. ブラウザーから SSO ドメインを確認する。 fetch('https://<domain>/.well-known/ii-openid-configuration') を呼び出したり、isValidSsoDomain(11.x で削除)を探したりしないでください。ssoDomain を指定してクライアントを作成し、getSsoStatus() を読み取ります。II はサインイン時と同じルールでドメインを確認するため、組織側で CORS ヘッダーを設定する必要はありません。

  21. SSO の確認が完了するまで signIn() を待機する、またはドメインのためにコンストラクターを try/catch で囲む。 Safari にポップアップをブロックされないよう、クリックハンドラー内で signIn() を同期的に呼び出す必要があります。invalid を含むすべての SSO 状態で処理が続行されます(II 独自の画面に進行状況やエラーが表示されます)。どの状態でも、ユーザーがポップアップを閉じるか認証に失敗すると拒否されるため、拒否ハンドラーを設定してください。ドメインの形式が正しくなくても、コンストラクターが例外をスローすることはありません。invalid を報告します。

  22. 既存のクライアントで ssoDomain または openIdProvider を変更する。 他のすべてのオプションと同様、どちらもコンストラクターでのみ指定でき、セッターも signIn() ごとの上書き方法もありません。ドメインを変更する場合は、新しいクライアントを作成し、置き換えられるクライアントで dispose() を呼び出してください。

ローカル開発中に II を使用する

デフォルト:ローカルネットワークからメインネットの II を使用します。 icp-cli >= 0.2.4 では、ローカルネットワークがメインネットのサブネットの署名を信頼するため、https://id.ai からの委任をローカルにデプロイしたバックエンドで使用できます。identityProvider を指定せずにクライアントを作成してください。

代替手段:ローカル II。 完全オフラインで開発する場合、または特定の II ビルドを使用する場合に限ります。icp.yaml のローカルネットワークに ii: true を追加してください。

networks:
  - name: local
    mode: managed
    ii: true

II は同じ canister ID で http://id.ai.localhost:8000 に配信されます。プロジェクトに canister エントリーを追加する必要はありません。ローカルのルートキーを渡してください。

const authClient = new AuthClient({
  identityProvider: {
    authorizeUrl: "http://id.ai.localhost:8000/authorize",
    canisterId: "rdmx6-jaaaa-aaaaa-aaadq-cai",
  },
  agentOptions: { rootKey: canisterEnv?.IC_ROOT_KEY },
});

フロントエンド:サインインフロー

フレームワークに依存しません。DOM 関連の部分は適宜調整してください。

import { AuthClient } from "@icp-sdk/auth/client";
import { HttpAgent, Actor } from "@icp-sdk/core/agent";
import { safeGetCanisterEnv } from "@icp-sdk/core/agent/canister-env";

// The ic_env cookie carries the root key and canister IDs, locally and on mainnet.
const canisterEnv = safeGetCanisterEnv();

// Mainnet II is the default. derivationOrigin, openIdProvider, ssoDomain,
// stateStorage, transport, prompt, and hint are also constructor options.
const authClient = new AuthClient();

async function signIn() {
  try {
    // Optional session bounds: signIn({ maxTimeToIdle, maxTimeToLive }), in ns.
    return await authClient.signIn();
  } catch (error) {
    console.error("Sign-in failed:", error);
    throw error;
  }
}

// Ends the session at II: every tab of this origin is signed out.
async function signOut() {
  await authClient.signOut();
}

// No host: the default resolves correctly locally, on mainnet, and on custom domains.
async function createAuthenticatedActor(identity, canisterId, idlFactory) {
  const agent = await HttpAgent.create({ identity, rootKey: canisterEnv?.IC_ROOT_KEY });
  return Actor.createActor(idlFactory, { agent, canisterId });
}

// Wrapped in a function: Vite 6 and earlier reject top-level await by default.
async function init() {
  // isAuthenticated() is sync; getIdentity() is async.
  if (authClient.isAuthenticated()) {
    const identity = await authClient.getIdentity();
    const actor = await createAuthenticatedActor(identity, canisterId, idlFactory);
  }

  // getStatus(): 'signed-in' | 'signed-in-elsewhere' | 'expired' | 'signed-out'.
  // subscribe() fires on any change, including in another tab.
  authClient.subscribe(() => render(authClient.getStatus()));
}

init();

ライフサイクル。 ページにクライアントを 1 つ作成する場合も、コンポーネントごとに作成する場合も、同じサインイン状態を読み書きします。ビューが所有するクライアントは、ビューとともに破棄してください。dispose() はリスナー、サブスクリプション、スケジュール済みの更新を解放しますが、サインアウトはしません。

ワンクリックサインイン

openIdProvider('google'、'apple'、'microsoft')または ssoDomain(組織のドメイン)を指定すると、II の方式選択画面をスキップします。これらは相互に排他的で(型エラーになり、構築時に例外がスローされます)、コンストラクターでのみ指定できます。

const google = new AuthClient({ openIdProvider: "google" });
const acme = new AuthClient({ ssoDomain: "acme.com" });
組織のドメインを確認する

ssoDomainで構築されたクライアントは、構築されるとすぐにIIを通じて独自のドメインを確認し、セッションと同様に結果を報告します。getSsoStatus()から同期スナップショットを取得でき、subscribe()から変更通知を受け取れます。例外やAbortSignalはありません。

type SsoStatus =
  | { state: "checking" }
  | { state: "available"; name?: string }      // name from the org's file: "Continue with Acme Corp"
  | { state: "invalid"; reason: string }       // not a domain: show reason, the user fixes the input
  | { state: "unavailable"; retryAfter?: Date; reason?: string }; // no SSO there, or failing; reason is II's, when given

ユーザーの入力中に確認する場合:

let client;
let timer;

input.addEventListener("input", () => {
  client?.dispose();  // also stops the old client's check
  client = undefined; // Continue never signs in to the old domain
  showSpinner();

  clearTimeout(timer); // debounce: one client per pause, not per keystroke
  timer = setTimeout(() => {
    const next = new AuthClient({ ssoDomain: input.value }); // never throws for the domain
    next.subscribe(() => render(next.getSsoStatus()));
    render(next.getSsoStatus());
    client = next;
  }, 300);
});

function render(sso) {
  switch (sso.state) {
    case "checking":    return showSpinner();
    case "available":   return enableContinue(sso.name);
    case "invalid":     return showNotADomain(sso.reason);
    case "unavailable": return showUnavailable(sso.retryAfter, sso.reason);
  }
}

// Synchronous in the click handler: never await the check first.
continueButton.addEventListener("click", () => {
  client?.signIn().then(showApp, showSignInError);
});
  • 古いクライアントは結果を公開する前に破棄されるため、古い結果が表示されることはありません。上記のように入力をデバウンスしてください。キー入力ごとにクライアントを構築すると、クライアントや確認処理が頻繁に作り直されます。
  • refreshSsoStatus()は、「再試行」ボタンから確認を再実行します。retryAfterが未来の時刻である間、IIは新たなフェッチを行わず、すぐに再びunavailableを返すため、ボタンを無効にし、カウントダウン(「2分後に再試行」など)を表示してください。invalidのクライアントでは何も行いません。ユーザーが入力を修正すると、新しいクライアントが構築されます。クライアントが自動で再確認することはありません。
  • signIn()はどの状態でも続行します。IIがドメインを独自に解決し、invalidの値の場合も含めて、独自の読み込み画面とエラー画面を表示します。ポップアップが閉じられた場合や認証に失敗した場合は、どの状態でも拒否されます。
  • invalidはII独自のルールに従います。1~63文字の英字、数字、ハイフンで構成されるラベルが2つ以上あること、ラベルの先頭と末尾にハイフンがないこと、ラベルの3文字目と4文字目に--がないこと(xn--のpunycodeは除く)、全体が253文字以内であること、ポートを指定できるのはループバックホスト(localhost、127.0.0.1、または.localhost配下の名前)のみであることが条件です。reasonには検出された問題がすべて示されます。
  • この確認でIIのキャッシュがウォームアップされるため、同じクライアントでのsignIn()はすばやく開始します。

組織側の設定方法(OIDCクライアント、ii-openid-configurationファイル、アプリごとのアクセス、ファイルを拒否する制限、キャッシュ)については、references/enterprise-sso.mdを参照してください。

ID属性

バックエンドでプリンシパル以外の情報(確認済みメールアドレスなど)が必要な場合、IIは委任情報とともに署名付き属性バンドルを返します。バックエンドには2つのメソッドが公開されます。_internet_identity_sign_in_startはnonceを生成し、_internet_identity_sign_in_finishはバンドルを検証します。identity-attributesライブラリは、Motokoのmo:identity-attributesミックスインと、Rustの#[identity_attributes]属性(references/rust-identity-attributes.md)の両方を提供します。どちらも同じCandid型を返すため、フロントエンドはどちらを使う場合も同じです。

キー

requestAttributes({ keys, nonce })では両方が必須です。デフォルトのキーセットはありません。

キー意味用途
nameユーザーのII連携アカウントに登録された表示名パーソナライズ
emailユーザーのII連携アカウントに登録された未加工のアドレス。IIは確認しません連絡先情報、メーリングリスト
verified_emailIIがユーザーによるアドレスの管理を確認した場合にのみ存在します(OpenIDプロバイダーが確認済みとしてマークした場合、またはユーザーがIIで確認した場合)アクセス制御。認可に使う唯一のメールアドレス

フォールバック用にemailとverified_emailの両方をリクエストしてください。確認済みの場合は両方に同じ値が含まれ、そうでない場合はemailのみが返されます。

scopedKeysは、ユーザーがサインインするアカウントにキーをスコープするため、追加のプロンプトなしで同じ手順で共有されます。

呼び出しキーデフォルトのkeys
scopedKeys({ openIdProvider: 'google' })openid:https://accounts.google.com:<key>name、email、verified_email
scopedKeys({ openIdProvider: 'apple' })openid:https://appleid.apple.com:<key>同じ
scopedKeys({ openIdProvider: 'microsoft' })openid:https://login.microsoftonline.com/{tid}/v2.0:<key>({tid}はリテラル)同じ
scopedKeys({ ssoDomain: 'acme.com' })sso:acme.com:<key>name、email(verified_emailなし)

キーを絞り込むには、keysを渡します。例:scopedKeys({ openIdProvider: 'google', keys: ['name', 'verified_email'] })。

フロントエンド
import { AuthClient } from "@icp-sdk/auth/client";
import { AttributesIdentity } from "@icp-sdk/core/identity";
import { HttpAgent, Actor } from "@icp-sdk/core/agent";
import { safeGetCanisterEnv } from "@icp-sdk/core/agent/canister-env";
import { Principal } from "@icp-sdk/core/principal";

const II_PRINCIPAL = "rdmx6-jaaaa-aaaaa-aaadq-cai";
const rootKey = safeGetCanisterEnv()?.IC_ROOT_KEY;

async function signInWithAttributes(authClient, canisterId, idl) {
  // Anonymous handle, used only to mint the nonce.
  const anonymousActor = Actor.createActor(idl, { agent: await HttpAgent.create({ rootKey }), canisterId });

  // In parallel, one interaction for the user. `nonce` is a function the client
  // calls when it needs the value, so the request is in flight while II opens.
  const signInPromise = authClient.signIn();
  const attributesPromise = authClient.requestAttributes({
    keys: ["name", "verified_email"],
    nonce: () => anonymousActor._internet_identity_sign_in_start(),
  });

  const identity = await signInPromise;
  const attributes = await attributesPromise;

  // The bundle travels as sender_info on each call made with this identity.
  const verifiedAgent = await HttpAgent.create({
    identity: new AttributesIdentity({
      inner: identity,
      attributes,
      signer: { canisterId: Principal.fromText(II_PRINCIPAL) },
    }),
    rootKey,
  });
  const verifiedActor = Actor.createActor(idl, { agent: verifiedAgent, canisterId });

  const result = await verifiedActor._internet_identity_sign_in_finish();
  if ("err" in result) {
    throw new Error(`Attribute verification failed: ${JSON.stringify(result.err)}`);
  }
  return identity;
}

ワンクリックサインインを実現するには、クライアントを openIdProvider または ssoDomain で構築し、@icp-sdk/auth/client のインポートに scopedKeys を追加して、代わりに scopedKeys({ openIdProvider: "google", keys: ["name", "verified_email"] }) または scopedKeys({ ssoDomain: "acme.com" }) をリクエストします。それ以外は変更ありません。

すべてのバンドルには、バックエンドが必ず検証しなければならない3つの暗黙的なフィールドが含まれます。implicit:nonce(発行済みで、まだ消費されていないもの)、implicit:origin(信頼できるフロントエンドのオリジン)、implicit:issued_at_timestamp_ns(有効期限内。通常は5分以内)。

バックエンド:Motoko
[dependencies]
identity-attributes = "0.4.1"
core                = "2.5.0"

[toolchain]
moc = "1.6.0"

include IdentityAttributes({ onVerified }) は両方のメソッドを追加し、署名者、オリジン、nonce、有効期限のチェックに合格したバンドルに対してのみ onVerified を実行します。バンドルを { name : ?Text; email : ?Text; sso : ?Text } に解決します。

  • OpenID およびスコープなしのソース:email は verified_email(または openid:<provider>:verified_email)から取得されます。そのため、フロントエンドは verified_email をリクエストします。
  • SSO ソース:name と email は sso:<domain>:name/sso:<domain>:email から取得されます。trusted_sso_domains に含まれるドメインの場合にのみ受け入れられ、sso はそのドメインになります。それ以外の場合、sso は null です。
import IdentityAttributes "mo:identity-attributes";
import Map "mo:core/Map";
import Principal "mo:core/Principal";

persistent actor {
  type Profile = { name : ?Text; email : ?Text; sso : ?Text };

  let profiles = Map.empty<Principal, Profile>();

  include IdentityAttributes({
    onVerified = func(caller, attrs) {
      profiles.add(caller, attrs);
    };
  });

  public query func getProfile(userId : Principal) : async ?Profile {
    profiles.get(userId)
  };
};
canisters:
  - name: backend
    settings:
      environment_variables:
        # Required. A local II (`ii: true`) has the same principal.
        trusted_attribute_signers: "rdmx6-jaaaa-aaaaa-aaadq-cai"
        # Required, comma-separated.
        frontend_origins: "https://your-app.icp.net"
        # Optional, comma-separated; omit to reject all sso:* keys.
        trusted_sso_domains: "your-org.com"

trusted_attribute_signers が未設定の場合、すべてのバンドルが信頼できないものとして拒否されます。frontend_origins が未設定の場合、_internet_identity_sign_in_finish は #err(#FrontendOriginsNotConfigured) を返します。エラーの種類(#NoAttributes、#MalformedCandid、#FrontendOriginMismatch、#Stale、#UnknownNonce、#AmbiguousAttribute、#UntrustedSsoSource、#MixedSsoSources)により、フロントエンドは新しいnonceで再試行するか、バグとして通知するかを判断できます。

複数のオリジンでアプリを提供する

IIはオリジンごとにプリンシパルを導出するため、https://<canister-id>.icp.net と https://shop.example.com は別々のユーザーになります。導出元となるオリジンを1つ選び(カスタムドメインは変更される可能性がありますが、canisterアドレスは変更されません)、それ以外を代替オリジンとして登録します。オリジンにユーザーが存在する前に固定してください。既存のサインインがあるオリジンを別のものに向け直すと、そのオリジンで作成されたすべてのアカウントが孤立します。

  1. 代替オリジンでは derivationOrigin を指定し、プライマリオリジンでは指定しません。

    const authClient = new AuthClient({ derivationOrigin: "https://<canister-id>.icp.net" });
    
  2. 導出元オリジンで /.well-known/ii-alternative-origins を提供します(静的サイトのビルドでは dir/.well-known/ii-alternative-origins)。

    { "alternativeOrigins": ["https://shop.example.com"] }
    

    エントリーは最大100件で、オリジンのみを指定します(末尾のスラッシュやパスは含めません)。上限を超えると、IIはリスト全体を拒否します(「has too many entries: To prevent misuse at most 100 alternative origins are allowed」)。超過分だけでなく、すべての代替オリジンで認証できなくなります。

  3. @dfinity/static-site では、_headers ブロックを追加します(ファイルに拡張子はなく、認証済みアセットではデフォルトでCORSヘッダーが設定されません)。

    /.well-known/ii-alternative-origins
      Content-Type: application/json
      Access-Control-Allow-Origin: *
    

同じサインインをサブドメイン間で共有する方法については、references/shared-sessions.md を参照してください。

サインイン画面のアプリメタデータ

導出元オリジンで /.well-known/ii-app-metadata({ "name", "description", "logo" }。すべて任意)を提供し、ドキュメントとロゴにCORSを設定すると、IIはオリジンの横にアプリ名、タグライン、ロゴを表示します。name は最大40コードポイント、description は最大120コードポイントです。logo は同一オリジンのラスター画像を指す相対URLです(SVGは使用できません)。無効なフィールドが1つでもあると、ドキュメント全体が無効になります。すべてのルールについては、references/app-metadata.md を参照してください。

バックエンド:アクセス制御

匿名プリンシパルの拒否、ロールガード、非同期関数での呼び出し元のバインドはII固有のものではありません。canister-security スキルを参照してください。

コメント