> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# JavaScript 快速入门：Didit 身份认证接入

> 在 Didit 中，将已启用的身份认证应用接入托管登录，支持 PKCE、回调校验、会话检查和退出登录。

> [!WARNING]
>
> 身份验证功能是新功能：请在已开启身份验证功能的应用中使用此托管流程示例。`@didit-protocol/auth` 包尚未发布；下方的包 API 仅为预览，并非可安装的快速入门。

## 配置托管登录

在您的沙盒应用中，注册准确的回调 URL 和您的 Web 来源。从控制台复制其登录源（`https://<slug>.sandbox.auth.didit.me`），具体说明见[概览](https://docs.didit.me/zh/authentication/overview.md)。本示例使用浏览器的加密 API，要求使用 HTTPS；在沙盒中可使用 localhost。关于需要注册的内容，请参阅[本地开发](https://docs.didit.me/zh/authentication/overview.md#本地开发)。

应用负责管理 `state` 和 PKCE 验证器。托管登录负责密码、邮箱、通行密钥、电话、SSO 以及必需的 MFA 页面。此代码中不得包含 API 密钥。

```javascript auth.js theme={null}
// Replace both values with your registered application configuration.
const AUTH_BASE = "https://YOUR_SLUG.sandbox.auth.didit.me/";
const REDIRECT_URI = "https://YOUR_APP_ORIGIN/auth/callback";
const PENDING = "didit.auth.pending";
let tokens = null;

function base64url(bytes) {
  return btoa(String.fromCharCode(...bytes))
    .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}

export async function signIn() {
  const verifier = base64url(crypto.getRandomValues(new Uint8Array(32)));
  const state = base64url(crypto.getRandomValues(new Uint8Array(32)));
  const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
  sessionStorage.setItem(PENDING, JSON.stringify({ verifier, state, startedAt: Date.now() }));
  const url = new URL("sign-in", AUTH_BASE);
  url.search = new URLSearchParams({
    redirect_uri: REDIRECT_URI,
    state,
    code_challenge: base64url(new Uint8Array(digest)),
    code_challenge_method: "S256",
  }).toString();
  window.location.assign(url.href);
}

export async function handleCallback() {
  const query = new URLSearchParams(window.location.search);
  const saved = sessionStorage.getItem(PENDING);
  sessionStorage.removeItem(PENDING);
  history.replaceState(null, "", window.location.pathname);
  if (!saved) throw new Error("Start sign-in again.");
  const pending = JSON.parse(saved);
  if (query.get("state") !== pending.state || Date.now() - pending.startedAt > 600000) {
    throw new Error("Invalid or expired sign-in. Start again.");
  }
  if (query.has("error") || !query.get("code")) throw new Error("Sign-in was not completed.");
  const response = await fetch(new URL("token/", AUTH_BASE), {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      grant_type: "authorization_code",
      code: query.get("code"),
      code_verifier: pending.verifier,
      redirect_uri: REDIRECT_URI,
    }),
  });
  if (!response.ok) throw new Error("Code exchange failed. Start sign-in again.");
  tokens = await response.json();
  return getSession();
}

export async function getSession() {
  if (!tokens) return null;
  const response = await fetch(new URL("session/", AUTH_BASE), {
    headers: { Authorization: `Bearer ${tokens.access_token}` },
    cache: "no-store",
  });
  if (!response.ok) {
    tokens = null;
    throw new Error("Session check failed. Sign in again.");
  }
  return response.json();
}

export async function signOut() {
  const current = tokens;
  tokens = null;
  if (!current) return;
  const response = await fetch(new URL("session/", AUTH_BASE), {
    method: "DELETE",
    headers: { Authorization: `Bearer ${current.access_token}` },
  });
  if (response.status !== 204) throw new Error("Local state cleared; server logout failed.");
}
```

从登录按钮调用 `signIn()`。在已注册的回调页面上，调用一次 `handleCallback()`，显示加载状态，如果失败则显示错误。使用 `getSession()` 获取当前用户，使用 `signOut()` 退出登录。回调会先从地址栏中移除其代码，然后再发出请求。

此最简示例将令牌保存在内存中，页面刷新或访问令牌过期后会重新登录。若需要持久会话，请使用您的后端以及安全 Cookie，或实现序列化的[刷新令牌轮换](https://docs.didit.me/zh/authentication/tokens-and-jwks.md)。切勿将刷新令牌放入 URL 或日志中。前端的会话检查仅用于控制展示；您的后端必须在每个受保护的请求中验证调用方。

## 身份验证 SDK 发布预览

计划中的 `@didit-protocol/auth` 核心包将提供 `createDiditAuth({publishableKey})`、`signIn()`、`handleRedirectCallback()`、`getSession()`、`getToken()`、`onAuthStateChange` 和 `signOut()`，身份信息上带有 `vendorData`。计划增加自动设备信号，以及带有 `verifyToken()` 的服务端入口。

可发布密钥为 `pk_live_`（生产应用）或 `pk_test_`（沙盒应用），后面接应用来源的 base64url 编码（不带填充）。SDK 会将其 API 调用发送至该来源。该密钥是公开的：它用于标识应用，并非机密。

### 预览中的 step-up 与重新认证

* `stepUp({ action, amount?, currency?, redirectUri? })` 调用您应用的 `POST step-up/`，询问 `action` 是否需要身份检查。`action` 可为 `payout`、`bank` 或 `contact`，其中 payout 需要 `amount`。当您的某条 step-up 规则适用时，它会跳转到 Didit 返回的验证页面。
* `handleStepUpCallback(url?)` 在用户返回的页面上运行。它读取 `?step_up=<id>`，将其从地址栏中移除，并使用 `GET step-up/{id}/` 读取结果。
* `reauthenticate({ method? })` 让同一用户再次登录：可在托管页面上进行（`method: "redirect"`，默认值），或使用通行密钥进行（`method: "passkey"`，仅限您应用的 Didit 登录域名）。托管页面完成后，使用 `handleRedirectCallback()` 结束流程。如果由其他人完成，则会因 `step_up_user_mismatch` 而拒绝，原会话保持不变。

两种 step-up 方法都会以 `{ id, status, approved, rule, method }` 结果返回，其中 `status` 为 `not_required`、`pending`、`approved`、`review`、`declined` 或 `expired`。`redirectUri` 默认为客户端的重定向地址，并且必须与您应用已注册的重定向地址完全一致：需要检查时，Didit 会拒绝任何其他地址。您的后端在根据 step-up 执行操作之前，必须使用用户的访问令牌再次通过 `GET step-up/{id}/` 读取该 step-up，因为浏览器报告的结果可能发生变化。

无头登录在您应用的保护规则标记该登录时，也可能返回 `protection_required`。用户随后可通过 Didit 发送到其邮箱的验证码完成（`protection.verify({ code })`），或使用通行密钥完成（`protection.passkey()`）。`methods` 列出两种方式中用户可以使用的方式。

请勿安装猜测的版本，也不要基于猜测的回调选项编写代码。在替换托管示例之前，请先确认已发布包的导出、颁发者和密钥配置。密码、邮箱验证码、魔法链接、通行密钥、电话、MFA、社交登录、step-up 和智能体方法，将取决于该版本支持的 API。

## 添加其他产品

您的后端需验证访问令牌（参见[令牌与 JWKS](https://docs.didit.me/zh/authentication/tokens-and-jwks.md)）并读取其 `vendor_data`。在[创建验证会话](https://docs.didit.me/zh/sessions-api/create-session.md)或[签发交易令牌](https://docs.didit.me/zh/transaction-monitoring/sdk-transaction-submission.md)时，将该值用作 `vendor_data`。登录成功并不代表用户已通过任一产品的审核。

上线前，请在已启用的沙盒应用中测试注册、再次登录、取消、state 不匹配、验证码重复使用、忘记密码、MFA、过期和退出登录。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.