diff --git a/evaluations/internet-identity.json b/evaluations/internet-identity.json index 7424eb4..67b4844 100644 --- a/evaluations/internet-identity.json +++ b/evaluations/internet-identity.json @@ -40,7 +40,7 @@ "Creates an HttpAgent with the identity", "Creates an actor using Actor.createActor with the agent", "All await calls are inside async functions — no bare top-level await", - "Uses rootKey from ic_env cookie (safeGetCanisterEnv) or host: window.location.origin — does NOT use shouldFetchRootKey or hardcoded host branching" + "Uses rootKey from ic_env cookie (safeGetCanisterEnv) and leaves host unset — does NOT set host: window.location.origin, use shouldFetchRootKey, or branch on a hardcoded host" ] }, { @@ -264,6 +264,27 @@ "Offers a concrete resolution: either move core to ^6 alongside @icp-sdk/auth@^10, or stay on @icp-sdk/auth@^9 which peers core ^5", "Does NOT recommend --legacy-peer-deps or --force to get past the peer conflict" ] + }, + { + "name": "Adversarial: local II sign-in without agentOptions", + "prompt": "I run Internet Identity on my local network (`ii: true` in icp.yaml) and use @icp-sdk/auth 10 with `new AuthClient({ identityProvider: { authorizeUrl: 'http://id.ai.localhost:8000/authorize', canisterId: 'rdmx6-jaaaa-aaaaa-aaadq-cai' } })`. The II popup opens and I finish the ceremony, but signIn() still fails. What am I missing? Just the fix and a short explanation, no full app.", + "expected_behaviors": [ + "Adds agentOptions to the AuthClient constructor with the root key from the ic_env cookie (IC_ROOT_KEY)", + "Explains that the client mints its delegations by calling the II canister and that, without the local root key, it verifies the local replica's response against the mainnet root key and fails", + "Mentions that an outdated local II may lack the minting methods, fixed by running `icp network update` and restarting the network", + "Does NOT set host (e.g. window.location.origin) in agentOptions, and does NOT suggest fetchRootKey() or shouldFetchRootKey", + "Fixes the local II setup rather than replacing it: switching to mainnet II is NOT presented as the fix (mentioning it as the default alternative is fine)" + ] + }, + { + "name": "Adversarial: logout timer from the delegation expiration", + "prompt": "After upgrading to @icp-sdk/auth 10 my users are logged out after a few minutes. I schedule `setTimeout(logout, expirationMs - Date.now())` from `identity.getDelegation().delegations[0].delegation.expiration`. What should I do instead? Just the fix.", + "expected_behaviors": [ + "Explains that in 9.x and later the delegation getIdentity() signs with is short-lived and replaced by the client as it ages, so its expiration is not the end of the session", + "Removes the timer", + "Reacts to the session ending through authClient.subscribe(), checking isAuthenticated() or an 'expired' status from getStatus()", + "Does NOT suggest re-scheduling the timer whenever the delegation is refreshed" + ] } ], "trigger_evals": { diff --git a/skills/internet-identity/SKILL.md b/skills/internet-identity/SKILL.md index 41e5b80..ab5501b 100644 --- a/skills/internet-identity/SKILL.md +++ b/skills/internet-identity/SKILL.md @@ -75,6 +75,10 @@ Internet Identity (II) is the Internet Computer's native authentication system. Do not clear it with `--legacy-peer-deps` — that skips the peer check and installs the mismatched pair anyway. Pin `@icp-sdk/auth@^10` with `@icp-sdk/core@^6`, or stay on `@icp-sdk/auth@^9` if something else holds you on core 5. +18. **Signing in against a local II without `agentOptions`.** The client mints its delegations by calling the II canister, through an agent that verifies responses against the mainnet root key by default. With a local II (`ii: true`), the popup opens at `http://id.ai.localhost:8000/authorize` and the ceremony completes, but the mint then fails with `TrustError: Certificate verification error` (`"Invalid signature"`). Pass `agentOptions: { rootKey }` with the root key from the `ic_env` cookie, and leave `host` unset. With mainnet II (the default), leave `agentOptions` unset. A local II from an older network launcher lacks the minting methods entirely: run `icp network update` and restart the network. See "Fallback: deploy II locally". + +19. **Scheduling a logout from the delegation's expiration.** In 9.x and later the delegation `getIdentity()` signs with is short-lived and replaced by the client as it ages, so a timer set from `identity.getDelegation()`'s expiration signs the user out after minutes, not at the end of the session. The session's end arrives as an `expired` status: subscribe, and leave the signed-in view when `isAuthenticated()` turns false. + ## Using II during local development **Default: use mainnet II from your local network.** Starting with `icp-cli >= 0.2.4`, the local network (pocket-ic, launched by `icp-cli-network-launcher`) is configured to trust the mainnet subnet's BLS signatures. Delegations signed by `https://id.ai` are accepted by your local replica, so both the sign-in flow *and* authenticated calls to a locally-deployed backend just work — no extra config in `icp.yaml`, no local II canister to manage, and the UI is the real one your users will see. @@ -94,6 +98,20 @@ networks: This deploys the II canisters automatically when the local network is started. The II frontend will be available at `http://id.ai.localhost:8000`, so the client is constructed with `identityProvider: { authorizeUrl: 'http://id.ai.localhost:8000/authorize', canisterId: 'rdmx6-jaaaa-aaaaa-aaadq-cai' }` — the canister id is the same locally, since system canisters keep their mainnet ids on the local network. No canister entry is needed in your project — II is not part of your project's canisters. For the full `icp.yaml` canister configuration, see the **icp-cli** and **static-site** skills. +The client mints its delegations by calling that canister itself, through an agent that verifies responses against the mainnet root key unless told otherwise. Pass the local root key from the `ic_env` cookie via `agentOptions`, or the ceremony completes and the mint then fails with `TrustError: Certificate verification error` (`"Invalid signature"`). Do not set `host`: the agent's default already resolves to the page origin on `localhost` (see the **icp-cli** skill's binding-generation reference). + +```javascript +const authClient = new AuthClient({ + identityProvider: { + authorizeUrl: "http://id.ai.localhost:8000/authorize", + canisterId: "rdmx6-jaaaa-aaaaa-aaadq-cai", + }, + agentOptions: { rootKey: canisterEnv?.IC_ROOT_KEY }, +}); +``` + +The local II must also be recent enough to mint: `@icp-sdk/auth` 9.x and later call its `app_prepare_delegation` / `app_get_delegation` methods, which the II bundled with older network launchers does not have. Run `icp network update` to fetch the latest launcher, then restart the local network. + ### Frontend: Vanilla JavaScript/TypeScript Sign-In Flow This is framework-agnostic. Adapt the DOM manipulation to your framework. @@ -144,10 +162,10 @@ async function signOut() { // Create an authenticated agent and actor. // Uses rootKey from the ic_env cookie — no shouldFetchRootKey or environment branching needed. +// 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, - host: window.location.origin, rootKey: canisterEnv?.IC_ROOT_KEY, });