---
title: "额外身份验证"
description: "应用在高风险操作前将用户带回 KAI Auth 完成一次新的身份验证，再通过标准 OAuth 回调和签名令牌取得验证结果。"
url: "https://auth.kai.com/docs/step-up"
language: "zh-CN"
version: "Better Auth 1.7.1"
updated: "2026-09-22"
---

# 额外身份验证

应用在高风险操作前将用户带回 KAI Auth 完成一次新的身份验证，再通过标准 OAuth 回调和签名令牌取得验证结果。

## 应用能看到什么

应用只能取得验证结果和本次验证时间，不会取得用户实际采用的高等级认证方式。

| 结果               | 对外表示                              | 应用应如何处理                                 |
| ------------------ | ------------------------------------- | ---------------------------------------------- |
| 未完成认证         | OAuth 错误，或始终没有收到有效回调    | 不执行受保护操作；允许用户重新发起             |
| 仅密码验证完成     | `kai_step_up.status: "password_only"` | 仅在业务明确允许密码结果时继续                 |
| 高等级身份认证完成 | `kai_step_up.status: "verified"`      | 完成令牌、用户和时间校验后，由业务判断是否继续 |

当前版本支持 TOTP 和 Passkey，其成功结果均为 `verified`；手机批准不在本次发布范围内。不会通过 `amr`、`method`、设备字段或不同的 `acr` 向应用区分实际方式。KAI 内部保留验证证据，用于正确执行策略、撤销和审计。

应用不能指定用户必须采用哪一种方式。KAI 根据账号当前配置提供验证选项：存在已配置的高等级方式时，只能使用这些方式；只有没有配置高等级方式且已有密码时，才允许额外密码验证。设备离线、浏览器不支持某种方式、验证失败或限流，都不会自动开放密码降级。账号没有可用方式时，本次验证无法完成。

`verified` 是 KAI 的结果分类，不单独承诺某个 NIST 等级、抗钓鱼能力、硬件保护能力或具体多因素组合。业务如果只接受高等级结果，应明确要求返回值为 `verified`。

## 发起请求

在已有业务登录会话下，创建一笔新的 Authorization Code + S256 PKCE 事务。保存这次事务的随机 `state`、`nonce`、PKCE verifier、预期用户的 `iss` / `sub` 和过期时间；不要复用首次登录的事务。

读取根路径 Discovery 的 `authorization_endpoint`，添加唯一的额外验证策略值：

```text
GET /api/auth/oauth2/authorize
  ?client_id=YOUR_CLIENT_ID
  &response_type=code
  &redirect_uri=https%3A%2F%2Fapp.example.com%2Foauth%2Fcallback
  &scope=openid
  &state=NEW_RANDOM_STATE
  &nonce=NEW_RANDOM_NONCE
  &code_challenge=BASE64URL_SHA256_NEW_VERIFIER
  &code_challenge_method=S256
  &acr_values=urn%3Akai%3Aacr%3Astep-up
```

`openid` 必须包含在 scope 中，请求也必须包含该应用登记的全部 `requiredScopes`。上例适用于仅将 `openid` 设为必选权限的应用；若另有必选 `profile` 或 `email`，也要一并请求，不需要的可选权限无需加入。`redirect_uri` 仍须是该应用登记的地址，public 客户端使用 PKCE，confidential 客户端在兑换令牌时按登记方式认证。需要业务 API 时另行申请对应 scope / resource，并遵守现有资源授权规则；额外验证本身不会扩展权限。

`acr_values=urn:kai:acr:step-up` 请求的是一次新的 KAI 额外验证。普通登录、已有高等级会话、`prompt=login` 或 `max_age` 本身都不能替代这笔请求的额外验证证明。可以同时使用 `prompt=login` 或 `max_age`；KAI 会保留原始请求绑定并完成恢复流程。

当前只支持上述单一 `acr_values` 值触发额外验证，不支持用 `claims.id_token.acr` 的 essential 请求代替，也不要拼接多个 ACR 值或具体因子名称。

已有 KAI 登录会话的用户进入独立验证页面，完成后沿原授权流程返回应用。验证保留当前会话，不额外创建一个登录会话，也不会更新原会话的认证时间。本次结果的 `auth_time` 独立记录实际证明完成时间。

验证交互从发起起最长有效十分钟；当前会话、原始签名请求或安全状态先失效时，交互也会提前失效。完成证明不会延长该期限。`max_age` 不能替代应用对最终证明时间的校验；应用应使用返回的 `auth_time` 检查自己的操作时效。

## 接收与校验结果

回调只返回标准 OAuth 授权码或错误。不要以 URL 中自行添加的 `verified=true`、页面提示或浏览器跳转成功作为验证证明。

1. 校验回调 `state`、issuer 和本地事务有效期；有 `error` 时结束本次流程。
2. 使用原始 `redirect_uri` 和 PKCE verifier，向 Discovery 的 `token_endpoint` 兑换一次性授权码。
3. 按 [ID token 校验规则](/docs/authorization-code#验证身份)验证签名、算法、`iss`、`aud`、`exp` 和本次 `nonce`。仅解码 JWT 不构成验证。
4. 核对令牌的 `iss` / `sub` 与发起操作时保存的业务账号完全一致。用户换了账号时，不能给原账号的操作放行。
5. 校验下面三个额外验证字段，并按业务规定的新鲜度窗口判断 `auth_time`。

```json
{
  "acr": "urn:kai:acr:step-up",
  "auth_time": 1790000000,
  "kai_step_up": {
    "status": "verified"
  }
}
```

| 字段                 | JSON 类型                         | 必须检查的内容                                               |
| -------------------- | --------------------------------- | ------------------------------------------------------------ |
| `acr`                | string                            | 必须为 `urn:kai:acr:step-up`                                 |
| `auth_time`          | number，非负整数                  | 本次实际验证的秒级 Unix 时间；符合业务的新鲜度和时钟偏差要求 |
| `kai_step_up`        | object                            | 必须存在，不能用普通登录结果代替                             |
| `kai_step_up.status` | `"verified"` 或 `"password_only"` | 高等级要求只接受 `verified`；缺失或未知值不得视为成功        |

这些字段出现在本次授权的 ID token、适用的 JWT access token，以及活跃 access token 的 Introspection 结果中。JWT 的查询结果保留令牌中的声明，opaque token 的查询结果从原授权记录读取。refresh token 自身的查询结果不提供此证明；UserInfo 用于用户资料查询，也不作为额外验证结果接口。详细令牌适用范围见 [claims](/docs/claims)。

业务应把通过校验的结果关联到原来的待处理操作，限制其有效时间和使用次数。身份验证结果不等于用户批准了某笔转账、金额或其他业务参数；这些内容仍须由应用展示、确认和授权。

## 未完成、取消与重试

| 情形                               | 预期行为                                                             |
| ---------------------------------- | -------------------------------------------------------------------- |
| 用户主动取消                       | 返回已登记的回调，携带 OAuth 错误，不产生成功验证证明                |
| 用户关闭页面、断网或事务过期       | 可能没有回调；应用应结束等待并按未完成处理                           |
| `prompt=none`，用户未登录          | 返回 `login_required`，不弹出交互页面                                |
| `prompt=none`，缺少本次有效证明    | 返回 `interaction_required`，不能把旧会话当作新证明                  |
| 过程中安全配置、会话或权限发生变化 | 旧流程可能失效，重新发起验证；不要自动改用密码                       |
| 授权提交遇到临时并发冲突           | KAI 页面要求重新检查或重试；应用只有拿到并校验最终成功结果后才能继续 |

本次证明只属于原始应用、用户、浏览器会话和授权事务。改变 `state`、`nonce`、PKCE、resource 或回调等参数，不能借用旧证明。授权码只能消费一次，证明也不能用于签发第二个授权码。

## 刷新与其他登录流程

刷新令牌保留原验证结果和原 `auth_time`，不会执行新的验证，也不会延长验证证明的新鲜度。需要更新证明时，应新建一笔额外验证事务。后来在同一浏览器完成验证，不会把以前的普通授权或 Device 授权升级为 `verified`。

[Device Authorization](/docs/device-authorization) 用于 CLI 等设备取得登录授权；目前不能通过给 Device 请求添加 `acr_values` 来获得本页的额外验证结果。[Auth CLI](/docs/auth-cli#应用侧的额外身份验证) 使用独立的 `oauth step-up` 命令发起本页的授权码流程，并执行相同的回调、签名、用户和时间校验。

`/oauth/step-up` 页面与 `/api/oauth/step-up/*` 是 KAI 自己的浏览器交互入口，使用当前浏览器会话和同源保护。第三方应用不得拿 OAuth access token 调用这些接口、抓取 Cookie 或代替用户提交验证凭据；第三方集成入口始终是 Discovery 中的授权端点。
