internet-identity
Internet Identity Authentication
What This Is
Internet Identity (II) is the Internet Computer's native authentication system. Users authenticate into II-powered apps either with passkeys stored in their devices or thorugh OpenID accounts (e.g., Google, Apple, Microsoft) -- no login or passwords required. Each user gets a unique principal per app, preventing cross-app tracking.
Prerequisites
@icp-sdk/auth(>= 5.0.0),@icp-sdk/core(>= 5.0.0)
Canister IDs
| Canister | ID | URL | Purpose |
|---|---|---|---|
| Internet Identity (backend) | rdmx6-jaaaa-aaaaa-aaadq-cai |
Manages user keys and authentication logic | |
| Internet Identity (frontend) | uqzsh-gqaaa-aaaaq-qaada-cai |
https://id.ai |
Serves the II web app; identity provider URL points here |
Mistakes That Break Your Build
-
Using the wrong II URL for the environment. The identity provider URL must point to the frontend canister (
uqzsh-gqaaa-aaaaq-qaada-cai), not the backend. Local development should usehttp://id.ai.localhost:8000. Mainnet must usehttps://id.ai(which resolves to the frontend canister). Both canister IDs are well-known and identical on mainnet and local replicas -- hardcode them rather than doing a dynamic lookup. -
Setting delegation expiry too long. Maximum delegation expiry is 30 days (2_592_000_000_000_000 nanoseconds). Longer values are silently clamped, which causes confusing session behavior. Use 8 hours for normal apps, 30 days maximum for "remember me" flows.
-
Not handling auth callbacks. The
authClient.login()call requiresonSuccessandonErrorcallbacks. Without them, login failures are silently swallowed. -
Using
shouldFetchRootKeyorfetchRootKey()instead of theic_envcookie. Theic_envcookie (set by the asset canister or the Vite dev server) already contains the root key asIC_ROOT_KEY. Pass it via therootKeyoption toHttpAgent.create()— this works in both local and production environments without environment branching. See the icp-cli skill'sreferences/binding-generation.mdfor the pattern. Never callfetchRootKey()— it fetches the root key from the replica at runtime, which lets a man-in-the-middle substitute a fake key on mainnet. -
Getting
2vxsx-faeas the principal after login. That is the anonymous principal -- it means authentication silently failed. Common causes: wrongidentityProviderURL, missingonSuccesscallback, or not extracting the identity fromauthClient.getIdentity()after login. -
Passing principal as string to backend. The
AuthClientgives you anIdentityobject. Backend canister methods receive the caller principal automatically via the IC protocol -- you do not pass it as a function argument. The caller principal is available on the backend viashared(msg) { msg.caller }in Motoko oric_cdk::api::msg_caller()in Rust. For backend access control patterns, see the canister-security skill. -
Adding
derivationOriginorii-alternative-originsto handleicp0.iovsic0.app. Internet Identity automatically rewritesicp0.iotoic0.appduring delegation, so both domains produce the same principal. Do not addderivationOriginorii-alternative-originsconfiguration to handle this — it will break authentication. If a user reports getting a different principal, the cause is almost certainly a different passkey or device, not the domain.
Using II during local development
You have two choices for local development:
- Starting with
icp-cli >= 0.2.4the local network can validate signatures from https://id.ai, so you can use mainnet identities with the local network. - You can configure your local network to deploy internet identity to the local network which makes it accessible at http://id.ai.localhost:8000 by default.
icp.yaml Configuration
Add ii: true to the local network in your icp.yaml to enable Internet Identity locally:
networks:
- name: local
mode: managed
ii: true
This deploys the II canisters automatically when the local network is started. By default, the II frontend will be available at http://id.ai.localhost:8000
No canister entry needed — II is not part of your project's canisters.
For the full icp.yaml canister configuration, see the icp-cli and asset-canister skills.
Frontend: Vanilla JavaScript/TypeScript Login Flow
This is framework-agnostic. Adapt the DOM manipulation to your framework.
import { AuthClient } from "@icp-sdk/auth/client";
import { HttpAgent, Actor } from "@icp-sdk/core/agent";
import { safeGetCanisterEnv } from "@icp-sdk/core/agent/canister-env";
// Module-scoped so login/logout/createAuthenticatedActor can access it.
let authClient;
// Read the ic_env cookie (set by the asset canister or Vite dev server).
// Contains the root key and canister IDs — works in both local and production.
const canisterEnv = safeGetCanisterEnv();
// Determine II URL based on environment.
// The identity provider URL points to the frontend canister which gets mapped to http://id.ai.localhost,
// not the backend (rdmx6-jaaaa-aaaaa-aaadq-cai). Both are well-known IDs, identical on
// mainnet and local replicas.
function getIdentityProviderUrl() {
const host = window.location.hostname;
const isLocal = host === "localhost" || host === "127.0.0.1" || host.endsWith(".localhost");
if (isLocal) {
return "http://id.ai.localhost:8000";
}
return "https://id.ai";
}
// Login
async function login() {
return new Promise((resolve, reject) => {
authClient.login({
identityProvider: getIdentityProviderUrl(),
maxTimeToLive: BigInt(8) * BigInt(3_600_000_000_000), // 8 hours in nanoseconds
onSuccess: () => {
const identity = authClient.getIdentity();
const principal = identity.getPrincipal().toText();
console.log("Logged in as:", principal);
resolve(identity);
},
onError: (error) => {
console.error("Login failed:", error);
reject(error);
},
});
});
}
// Logout
async function logout() {
await authClient.logout();
// Optionally reload or reset UI state
}
// Create an authenticated agent and actor.
// Uses rootKey from the ic_env cookie — no shouldFetchRootKey or environment branching needed.
async function createAuthenticatedActor(identity, canisterId, idlFactory) {
const agent = await HttpAgent.create({
identity,
host: window.location.origin,
rootKey: canisterEnv?.IC_ROOT_KEY,
});
return Actor.createActor(idlFactory, { agent, canisterId });
}
// Initialization — wraps async setup in a function so this code works with
// any bundler target (Vite defaults to es2020 which lacks top-level await).
async function init() {
authClient = await AuthClient.create();
// Check if already authenticated (on page load)
const isAuthenticated = await authClient.isAuthenticated();
if (isAuthenticated) {
const identity = authClient.getIdentity();
const actor = await createAuthenticatedActor(identity, canisterId, idlFactory);
// Use actor to call backend methods
}
}
init();
Backend: Access Control
Backend access control (anonymous principal rejection, role guards, caller binding in async functions) is not II-specific — the same patterns apply regardless of authentication method. See the canister-security skill for complete Motoko and Rust examples.
More from dfinity/icskills
icp-cli
Guides use of the icp command-line tool for building and deploying Internet Computer applications. Covers project configuration (icp.yaml), recipes, environments, canister lifecycle, and identity management. Use when building, deploying, or managing any IC project. Use when the user mentions icp, dfx, canister deployment, local network, or project setup. Do NOT use for canister-level programming patterns like access control, inter-canister calls, or stable memory — use domain-specific skills instead.
127asset-canister
Deploy frontend assets to the IC. Covers certified assets, SPA routing with .ic-assets.json5, content encoding, and programmatic uploads. Use when hosting a frontend, deploying static files, or setting up SPA routing on IC. Do NOT use for canister-level code patterns or custom domain setup — use custom-domains instead.
119https-outcalls
Make HTTPS requests from canisters to external web APIs. Covers transform functions for consensus, cycle cost management, response size limits, and idempotency patterns. Use when a canister needs to call an external API, fetch data from the web, or make HTTP requests. Do NOT use for EVM/Ethereum calls — use evm-rpc instead.
114stable-memory
Persist canister state across upgrades. Covers StableBTreeMap and MemoryManager in Rust, persistent actor in Motoko, and upgrade hook patterns. Use when dealing with canister upgrades, data persistence, data lost after upgrade, stable storage, StableBTreeMap, pre_upgrade traps, or heap vs stable memory. Do NOT use for inter-canister calls or access control — use multi-canister or canister-security instead.
113canister-security
IC-specific security patterns for canister development in Motoko and Rust. Covers access control, anonymous principal rejection, reentrancy prevention (CallerGuard pattern), async safety (saga pattern), callback trap handling, cycle drain protection, and safe upgrade patterns. Use when writing or modifying any canister that modifies state, handles tokens, makes inter-canister calls, or implements access control.
112certified-variables
Serve cryptographically verified responses from query calls using Merkle trees and subnet BLS signatures. Covers certified data API, RbTree/CertTree construction, witness generation, and frontend certificate validation. Use when query responses need verification, certified data, or response authenticity proofs.
109