前端开发
集成 Sumsub ID Connect(OIDC…
当需要让用户通过 OIDC「Verify with Sumsub ID」把已完成的 KYC 身份声明共享给你的应用时,按此流程端到端接入。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 59/100 ×0.15
适用场景
集成 Sumsub ID Connect特定
解决思路
- 先确认 Stage 0a 前置条件,逐项向用户核实,不要假设任何一项已完成;缺失时停下来把具体的手动步骤和操作位置交给用户(支持邮件模板、Dev Space → OIDC Settings 注册、App Token 生成页、level 创建交接),这些都是 Sumsub 控制台/支持侧操作,没有公开 API,你无法代做,必须等每一项确认后再继续。
- 前置条件确认后运行 scripts/preflight.sh 并要求退出码为 0:SUMSUB_APP_TOKEN=sbx:... SUMSUB_SECRET_KEY=... bash scripts/preflight.sh;解决所有 FAIL(必要时回到 Stage 0a),任何 FAIL 都不得继续。
- 两个门禁都通过后才开始写代码:先做前端,再做后端;如果用户在门禁通过前说「直接搭个 demo」,简要解释为什么还不行,并先带他补齐缺失的前置条件。
- 仅使用 sandbox 的 App Token/secret:在 https://cockpit.sumsub.com/checkus/home?sbx=true 通过 Connect Sumsub to your AI agent -> Build & configure -> Generate token 生成,token 和 secret 只在创建时显示一次,关闭对话框前复制两者;preflight 会用 sbx: 前缀检查强制这一点。
- Stage 0a 前置条件清单逐项确认:Sumsub 支持已为你的 workspace 启用 ID Connect(非自助开关,需先发邮件给 CSM 或 support@sumsub.com 并等待确认,邮件模板见原文);已生成 sandbox App Token + secret 并导出为 SUMSUB_APP_TOKEN / SUMSUB_SECRET_KEY;已在 Dashboard → Dev Space → OIDC Settings 注册 OIDC client 并拿到 client_id、client_secret(仅显示一次,丢失需重新生成并使现有集成失效,必须仅存服务端)以及 redirect_uri 允许列表(authorize 回调和 shareLink 回调都要,Sumsub 逐字节匹配,且必须是公开 HTTPS,裸 http://localhost:3000 不行);workspace 中至少有一个验证 level 并记下 levelName(没有则先走 sumsub-create-level);至少有一个带已验证数据的 Sumsub ID 账号用于端到端测试(仅邮箱账号会让 Stage 4 报 10521)。
- 本地开发用 ngrok:启动本地后端(示例监听 PORT=3000:PORT=3000 node examples/express-callback.js);用保留(静态)域名开隧道(ngrok http 3000 --domain=your-reserved-name.ngrok-free.app,或临时 ngrok http 3000);在 Dashboard → Dev Space → OIDC Settings 中把两个回调都按 ngrok 源逐字节加入允许列表(https://your-reserved-name.ngrok-free.app/ 和 https://your-reserved-name.ngrok-free.app/share-complete);用 PUBLIC_BASE_URL=https://your-reserved-name.ngrok-free.app PORT=3000 node examples/express-callback.js 启动并重启,浏览器打开 ngrok URL 而不是 localhost:3000;每次 URL 变化都要重新注册允许列表并更新 PUBLIC_BASE_URL。
- Stage 1 前端按钮默认用 Method 1(@sumsub/id-connect 的 createButton):自己生成 PKCE 对(b64url(crypto.getRandomValues(new Uint8Array(32))) 得到 verifier,b64url(await sha256(verifier)) 得到 challenge),把 verifier 存在自己的 sessionStorage key 下,调用 createButton({ clientId, permissions: ['openid','share','name'], container, codeChallenge: challenge, loginHint, onSuccess, onError }),在 onSuccess 中把 { code, codeVerifier, redirectUri } POST 到自己的后端路由(示例为 POST /api/sumsub/id-connect/exchange)。
- 需要自有设计系统按钮时用 Method 2:同一库,自己控制触发元素,用 openModal({...}) 打开覆盖层流程。
- CSP 禁止第三方脚本、纯 SSR 环境或维护通用 OIDC 客户端时用 Method 3 原始重定向:GET https://id.sumsub.com/api/snsId/oauth/authorize?client_id=...&response_type=code&redirect_uri=...&scope=openid+share+name&state=<随机 CSRF nonce>&code_challenge=<base64url(SHA256(code_verifier))>&code_challenge_method=S256&login_hint=...;自己生成 43–128 字符 URL-safe 的 code_verifier,存 sessionStorage,回调后转发给后端;Sumsub 只接受 S256(拒绝 plain),redirect_uri 必须逐字节匹配允许列表。
- scope 默认用 ['openid','share','name'],只有下游逻辑确实会读取时才加 email、profile 或 offline_access(refresh token),多余 scope 会在用户流程中触发额外检查。
- 注意 onSuccess 不是验证信号:它只表示用户完成了 OIDC 同意步骤且 Sumsub 签发了授权码;真正的验证结论来自 Stage 4 的 applicant.review.reviewStatus 或 applicantReviewed webhook,绝不能因为 onSuccess 触发就授予访问或解锁功能。
- Stage 2 后端用 OIDC client_secret 做 code → access_token 交换(client_secret_basic,不要 HMAC 签名):POST https://id.sumsub.com/api/snsId/oauth/token,Content-Type: application/x-www-form-urlencoded,Authorization: Basic base64(<client_id>:<client_secret>),body 为 grant_type=authorization_code&code=<前端拿到的 code>&redirect_uri=<与 Stage 1 逐字节一致的 URI>&code_verifier=<与 code_challenge 匹配的 PKCE verifier>;响应含 24 小时有效的 access_token,带 openid 时含 RS256 签名的 id_token,带 offline_access 时含 refresh_token;client_secret 只留服务端,绝不进浏览器 bundle。
- 处理 Stage 2 常见问题:Invalid code 401 的三个常见原因是 PKCE 不匹配、code 已被使用(单次使用,刷新回调页会重复触发)或 code 过期(TTL 很短,要立即交换);redirect_uri 必须与 Stage 1 逐字节一致(尾部斜杠、大小写、端口、query 都算);id_token 用成熟库(jose、pyjwt、jjwt)对照 .well-known/openid-configuration 的 jwks_uri 验证签名,绝不信任未验证的 JWT 内容。
- Stage 3a 铸造 share token:POST https://api.sumsub.com/resources/accessTokens/sumsubIdShareToken,body 为 { sumsubIdConnectToken: <Stage 2 的 access_token>, forClientId: <接收方 clientId>, ttlInSecs: 1800 };forClientId 在自接收(常见情况)时用自己 workspace 的 clientId(Dashboard 左上角),第三方接收方用其 clientId(必须已在 Dashboard 注册为 partner);响应为 { token, forClientId, sharingAllowed }。
- 始终实现 Stage 3b 同意回退:当 sharingAllowed: false 时,POST https://api.sumsub.com/resources/snsId/oauth/shareLink,带 Authorization: Bearer <access_token> 以及 X-App-Token + X-App-Access-Ts + X-App-Access-Sig,body 为 { redirectUri: <允许列表中的 shareLink 回调>, forClientId: <同一接收方>, displayMode: 'page', state: <csrf 或关联 id> };响应 { link } 5 分钟内有效;把 access_token 按 state 存在服务端(demo 可用内存 Map,生产用 Redis/加密 cookie/会话表),把用户重定向到 link;同意后 Sumsub 会以 ?token=<JWT> 重定向回 redirectUri。
- 注意 shareLink 回调的 state 在 JWT payload 里而不是顶层 ?state= 查询参数:先对照 jwks_uri 验证签名,再解码 token JWT 读取 state(payload 形如 { iss, sub, aud, iat, exp, sharingAllowed, state, forClientId }),用该 state 查回保存的 access_token,重新调用 Stage 3a,此时 share token 会返回 sharingAllowed: true。
- Stage 4 先预览再提交:GET https://api.sumsub.com/resources/api/reusableIdentity/reuse/preview?shareToken=<token>&levelName=<your-level>&userId=<externalUserId> 返回与 /reuse 相同的 ApplicantPublicDto 形状,用来在提交前暴露兼容性错误;确认后再 POST https://api.sumsub.com/resources/api/reusableIdentity/reuse?shareToken=<token>&levelName=<your-level>&userId=<externalUserId> 创建申请人;userId 是 Sumsub 存在申请人上的 externalUserId,每个真实用户稳定、对用户不透明、与你的认证系统绑定。
- 按 reuse 错误码分支处理(400 响应含 errorCode 整数和 errorName kebab-case;401 是通用 token 有效性,无 errorCode):401 Token is invalid → 用仍有效的 access_token 重新走 Stage 3a 铸造;10501 not-in-partners-list → 在 Dashboard 把接收方加为 partner(仅 Sumsub UI);10502 invalid-partner-id → 检查 forClientId 并对真实 partner 重新铸造;10503 invalid-share-token → 重新铸造;10504 non-suitable-share-token → 用匹配 scope 重新铸造并对同一 level 重试;10505 reusable-kyc-disabled → 联系 Sumsub 支持启用 Reusable KYC;10506 reusable-kyc-not-reusable → 在 Dashboard 检查 donor 或换 donor/回退 WebSDK;10507 reusable-kyc-not-approved-applicant → 等 donor KYC 通过或换已通过的 donor;10508 reusable-kyc-not-active-applicant → 换 donor;10509 reusable-kyc-not-kyc-moderation-type → donor 需标准 KYC moderation;10510 reusable-kyc-not-selfie-or-identity-doc → 换不需要该文档集的 level 或让 donor 重新验证;10511 reusable-kyc-not-selfie-liveness → 换不含 videoRequired: passiveLiveness 的 level 或让 donor 带 liveness 重新验证;10512 reusable-kyc-not-req-doc-overlap → 选所需文档是 donor 子集的接收 level;10513 reusable-kyc-not-compatible-doc-type → 用更宽松的 level 或回退 WebSDK;10514 reusable-kyc-not-actual-poi-date → donor 重新提交 POI 或放宽 POI 时效窗口;10515 reusable-kyc-not-actual-poa-date → donor 重新提交 POA 或放宽 POA 时效窗口;10516 reusable-kyc-not-acceptable-age → 无法恢复,需换用户;10517 reusable-kyc-incompatible-capture-settings → 对齐接收 level 的 captureParams 或让 donor 重新采集;10518 reusable-kyc-email-mismatch → 解决重复申请人冲突(换 externalUserId 或对齐邮箱);10519 reusable-kyc-phone-mismatch → 同上解决冲突;10521 reusable-kyc-inactive-sumsub-id-account → donor 需先在 id.sumsub.com 完成完整 KYC,测试时用 Sumsub 提供的已验证测试账号。
- 申请人落地后,把 applicantReviewed webhook 作为验证结论的权威信号,按 WebSDK 的方式接入(webhook 材料见 sumsub-integrate-websdk,sandbox webhook 订阅见 sumsub-manage-webhooks)。
- 上线前逐项检查:生产 clientId 已由 Sumsub 支持启用 ID Connect(与 sandbox 分开);生产两个 redirectUri(authorize 和 shareLink 回调)已在 prod workspace 的 Dashboard 允许列表中;后端 token 交换端点有鉴权(只有为该用户签发的 code 才能被其交换);id_token 签名对照 prod JWKS 验证;externalUserId 每个真实用户稳定(不用邮箱、不用显示名);先调 /reuse/preview 再调 /reuse,让兼容性错误体现为 UX 而非 4xx 噪音;Stage 3b shareLink 流程已实现并测试,不要发布只处理 sharingAllowed: true 的代码;shareLink 回调用的内存状态换成真实会话存储(Redis/加密 cookie/DB);App Token + secret + OIDC client_secret 放在生产密钥库,不提交、不进浏览器 bundle;refresh token(若使用)仅存服务端、静态加密、单租户。
适用边界 · 注意事项
- 不要用于独立 WebSDK / KYC widget 场景(应使用 sumsub-integrate-websdk)。
- 不要用于 donor 和 recipient 都是你自己的租户、没有用户参与的 OIDC 步骤的纯 Reusable-KYC-via-API 场景。
- 不要在 Stage 0a 前置条件全部确认且 Stage 0b preflight 退出码为 0 之前编写任何前端或后端代码、创建 demo 项目或脚手架。