Internet Identity 身份认证
这是什么
Internet Identity(II)是互联网计算机(Internet Computer)的原生身份认证系统。用户通过设备上存储的 passkey 或 OpenID 账户(例如 Google、Apple、Microsoft)登录由 II 提供认证支持的应用——无需用户名或密码。每个用户在每个应用上都会获得唯一的 principal,从而防止跨应用追踪。
前置条件
@icp-sdk/auth@^10 与 @icp-sdk/core@^6 需要一起固定主版本号。auth 10 的 peer 依赖是 @icp-sdk/core@^6,auth 9 的 peer 依赖是 ^5,因此像“auth >= 9”这样没有上限的写法会解析到 10,然后在 core 5 的安装环境下因 ERESOLVE 失败。
Motoko 后端示例需要:mo:identity-attributes >= 0.4.0(mops)——这个 mixin 会注入两个登录方法并为你验证 bundle。它会引入 mo:core >= 2.5.0,并且 include mixin 需要 moc >= 1.6.0。
Canister ID
Internet Identity(后端)canister ID 为 rdmx6-jaaaa-aaaaa-aaadq-cai,用于管理用户密钥和认证逻辑。
Internet Identity(前端)canister ID 为 uqzsh-gqaaa-aaaaq-qaada-cai,URL 为 https://id.ai,用于提供 II Web 应用,身份提供方 URL 指向这里。
会破坏构建的错误
为环境使用错误的 II URL。authorizeUrl 必须指向前端 canister(uqzsh-gqaaa-aaaaq-qaada-cai),而不是后端。主网使用 https://id.ai/authorize。本地 II(在 icp.yaml 中设置 ii: true 时)使用 http://id.ai.localhost:8000/authorize。两个 canister ID 都是众所周知的,在主网和本地副本上完全相同——应硬编码,而不是动态查询。
delegation 处理、principal 按应用隔离是该认证系统的核心机制。每个用户会为每个应用获得唯一的 principal,这意味着同一个用户在不同应用上表现为不同的身份,无法被跨应用关联追踪。
delegation 用于让前端代表用户向后端发起已签名的调用,其签名所用的 delegation 是短期的,并由客户端自动替换。
身份属性(Attributes)
email 与 verified_email 是不同的键。email 是用户 II 关联账户的原始邮箱字符串,II 不检查它,应当作用户输入对待。verified_email 是同一个邮箱,但仅在来源 OpenID 提供方(如 Google)将其标记为已验证且 II 将该信号透传时才会出现。任何访问控制(管理员白名单、能力检查)应使用 verified_email;email 仅用于联系信息或邮件列表等软用途。建议同时请求两者以实现回退行为:来源提供方标记邮箱已验证时两者返回相同值,否则只返回 email。
传给 requestAttributes 的 nonce 必须来自后端 canister 调用。前端生成的 nonce 会破坏重放保护。后端应从 _internet_identity_sign_in_start 铸造并返回 nonce(Motoko 中由 mo:identity-attributes mixin 提供;Rust 中需自行实现),并在用户调用 _internet_identity_sign_in_finish 时对照 bundle 的 implicit 字段检查它。
读取属性数据前必须验证签名者。IC 验证签名,但不验证签名者的身份——任何 canister 都能生成有效 bundle。受信任的签名者是 rdmx6-jaaaa-aaaaa-aaadq-cai(Internet Identity)。Motoko 中使用 mo:identity-attributes mixin,include IdentityAttributes({ onVerified }) 会验证签名者、来源、nonce 和新鲜度,并仅在通过验证的 bundle 上运行 onVerified。Rust 中尚无 CDK 封装,必须在读取 msg_caller_info_data() 之前始终用 msg_caller_info_signer() 对照受信任的发行者 principal 进行检查。
本地开发中使用 II
默认做法:从本地网络使用主网 II。从 icp-cli >= 0.2.4 开始,本地网络(pocket-ic,由 icp-cli-network-launcher 启动)被配置为信任主网子网的 BLS 签名。由 https://id.ai 签发的 delegation 会被本地副本接受,因此登录流程和对本地部署后端的已认证调用都能正常工作——无需在 icp.yaml 中额外配置,无需管理本地 II canister,UI 也是用户将看到的真实 UI。构造客户端时完全不传 identityProvider:默认就是主网 Internet Identity。
回退做法:本地部署 II
仅在你需要完全离线开发或想针对特定 II 构建进行测试时使用。在 icp.yaml 的本地网络中添加 ii: true。这会在本地网络启动时自动部署 II canisters。II 前端将可通过 http://id.ai.localhost:8000 访问,客户端构造时使用 identityProvider: { authorizeUrl: 'http://id.ai.localhost:8000/authorize', canisterId: 'rdmx6-jaaaa-aaaaa-aaadq-cai' }——canister id 在本地相同,因为系统 canister 在本地网络上保留其主网 id。项目 canister 列表中无需添加 II 条目——II 不属于你项目的 canister。
客户端通过一个默认对照主网根密钥验证响应的 agent 调用该 canister 来铸造自己的 delegation。应通过 agentOptions 传入来自 ic_env cookie 的本地根密钥,否则仪式会完成,随后铸造会以 TrustError: Certificate verification error("Invalid signature")失败。不要设置 host。
本地 II 也必须足够新才能铸造:@icp-sdk/auth 9.x 及更高版本调用其 app_prepare_delegation / app_get_delegation 方法,而较旧的网络启动器打包的 II 没有这些方法。运行 icp network update 获取最新启动器,然后重启本地网络。
前端:Vanilla JavaScript/TypeScript 登录流程
此流程与框架无关,可将 DOM 操作适配到你的框架。示例代码从 @icp-sdk/auth/client 导入 AuthClient,从 @icp-sdk/core/agent 导入 HttpAgent 和 Actor,从 @icp-sdk/core/agent/canister-env 导入 safeGetCanisterEnv。先用 safeGetCanisterEnv() 读取 ic_env cookie(由前端 canister 或 Vite 开发服务器设置),其中包含根密钥和 canister ID,在本地和生产环境都可用。主网 Internet Identity 是默认值,因此无需 identityProvider;pocket-ic(icp-cli >= 0.2.4)信任主网子网签名,所以从本地开发环境也能工作。仅在本地 II(icp.yaml 中 ii: true)或另一部署时才传 { authorizeUrl, canisterId };两部分必须一起提供,单独的 URL 字符串会抛错。derivationOrigin 以及用于一键登录的 openIdProvider('google' | 'apple' | 'microsoft')也是构造器选项。多个客户端可能共享同一 origin 并读取同一登录状态,因此应在需要处构造一个,并在该视图消失时销毁。signIn() 直接返回新的 Identity,并在用户关闭弹窗或认证失败时 reject。
该技能资料还涉及会话生命周期、maxTimeToLive / maxTimeToIdle 语义、shouldFetchRootKey / fetchRootKey() 的风险、getIdentity() 抛出 SessionNotHeldError、后端不应把 principal 作为函数参数传递等细节。其余关于 ii-app-metadata 发布、CORS、logo 校验以及 OpenID 各提供方配置的完整内容因原文在此截断,暂未完整提供。