Firebase App Check Web SDK API 全解析:@firebase/app-check 的函数、类与接口详解
Firebase App Check Web SDK API 全解析:@firebase/app-check 的函数、类与接口详解
导读
本文以 Firebase JS SDK 中 @firebase/app-check 模块的官方 API 参考文档为主体,系统梳理 Firebase App Check Web SDK 的完整公开 API 面:包括 initializeAppCheck()、getToken()、getLimitedUseToken()、onTokenChanged()、setTokenAutoRefreshEnabled() 五个核心函数,ReCaptchaV3Provider、ReCaptchaEnterpriseProvider、CustomProvider 三个 Provider 类,以及 AppCheck、AppCheckOptions、AppCheckToken 等接口与类型。在继承全部 API 签名与参数说明的基础上,结合仓库源码(packages/app-check/src)深入讲解 token 获取与缓存、主动刷新、指数退避、调试模式等底层机制,帮助你在 Web 应用中正确接入 App Check 并理解其运行原理。
App Check Web SDK 概览与使用前提
@firebase/app-check 是 Firebase JS SDK 的 App Check 组件,官方定位是"Firebase App Check Web SDK"。App Check 用于保护你的 Firebase 后端资源(如 Cloud Storage、Cloud Functions、Realtime Database)不被未授权客户端滥用:它通过设备与环境的证明(attestation)机制生成 App Check token,帮助 Firebase 服务识别请求是否来自真实可信的应用实例。
Node.js 环境的重要限制
根据 API 文档(docs-devsite/app-check.md)与源码入口注释(packages/app-check/src/index.ts)的明确说明:
Firebase App Check 在 Node.js 环境中不适用于
ReCaptchaV3Provider或ReCaptchaEnterpriseProvider,但如果使用CustomProvider并自行编写证明(attestation)方法,则可以在 Node.js 中使用。
原因是 reCAPTCHA 依赖浏览器环境加载 https://www.google.com/recaptcha/api.js 脚本并执行 DOM 渲染(见 packages/app-check/src/recaptcha.ts),Node.js 中没有对应环境;而 CustomProvider 完全由你提供 getToken 函数,不依赖浏览器 API,因此可以在服务端场景使用。
函数详解:App Check 的五个公开 API
@firebase/app-check 对外暴露的全部函数分为两组:function(app, ...) 即初始化函数 initializeAppCheck();function(appCheckInstance, ...) 即基于 App Check 实例操作的四个函数。所有函数的 TypeScript 声明实现在 packages/app-check/src/api.ts。
initializeAppCheck(app, options):激活 App Check
为给定的 FirebaseApp 激活 App Check,每个 app 只能调用一次。
签名:
export declare function initializeAppCheck(
app: FirebaseApp | undefined,
options: AppCheckOptions
): AppCheck;
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| app | FirebaseApp | undefined | 要为其激活 App Check 的 FirebaseApp 实例 |
| options | AppCheckOptions | App Check 初始化选项 |
返回值: AppCheck
源码行为(packages/app-check/src/api.ts):
app参数默认为getApp(),即未传时可自动使用默认 Firebase 实例;- 通过
_getProvider(app, 'app-check')获取组件 Provider; - 首次调用时触发
initializeDebugMode(app)(仅执行一次); - 若处于调试模式,会异步打印调试 token 到控制台;
- 重复初始化校验:若
provider.isInitialized()为真,且传入的isTokenAutoRefreshEnabled与provider.isEqual()判断的 Provider 均与首次初始化相同,则直接返回已有实例;否则抛出appCheck/already-initialized错误。从源码看,重复调用必须"等价"才能复用实例。
内部 _activate()(packages/app-check/src/api.ts)完成真正的激活工作:创建状态对象、从 IndexedDB 读取缓存 token、设置 isTokenAutoRefreshEnabled(与全局 automaticDataCollectionEnabled 做与运算)并调用 state.provider.initialize(app)。
注意:若
isTokenAutoRefreshEnabled为 true 但初始化 FirebaseApp 时将automaticDataCollectionEnabled设为 false,SDK 会发出logger.warn警告提示自动刷新被阻止,但不会抛错。
getToken(appCheckInstance, forceRefresh):获取当前 App Check token
获取当前 App Check token。若 forceRefresh 为 false,函数先检查内存中的有效 token,再检查本地持久化存储(IndexedDB);若未找到,或 forceRefresh 为 true,则向 App Check 端点请求新 token;该请求会挂接到当前已在进行中的请求(in-flight request)之上,避免并发重复请求。
签名:
export declare function getToken(
appCheckInstance: AppCheck,
forceRefresh?: boolean
): Promise<AppCheckTokenResult>;
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| appCheckInstance | AppCheck | App Check 服务实例 |
| forceRefresh | boolean | 为 true 时总是尝试获取新 token;为 false 时若存储中存在缓存 token 则直接使用 |
返回值: Promise<AppCheckTokenResult>
源码行为(packages/app-check/src/api.ts): 公开的 getToken 是对内部 getToken 的薄封装,内部实现位于 packages/app-check/src/internal-api.ts,完整流程见下文"token 获取与缓存机制"一节。若结果携带 error 或 internalError 字段,公开 API 会直接抛出该错误。
getLimitedUseToken(appCheckInstance):获取限用 token
请求一个 Firebase App Check token。此方法仅在你需要向非 Firebase 后端授权请求时使用。它返回限用(limited-use)token,适用于启用了 Replay Protection(重放保护)的非 Firebase 后端端点。此方法不会影响 getToken() 的 token 生成行为。
签名:
export declare function getLimitedUseToken(
appCheckInstance: AppCheck
): Promise<AppCheckTokenResult>;
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| appCheckInstance | AppCheck | App Check 服务实例 |
返回值: Promise<AppCheckTokenResult> —— 限用 token。
源码行为(packages/app-check/src/api.ts): 直接委托内部实现 getLimitedUseTokenInternal(packages/app-check/src/internal-api.ts)。与普通 getToken 的关键区别是:跳过所有缓存与状态逻辑,直接调用底层 Provider 的 getToken(true)(isLimitedUse = true),在请求体中携带 limited_use: true(见 packages/app-check/src/providers.ts 与 packages/app-check/src/client.ts 的请求构造)。
onTokenChanged(appCheckInstance, observer / onNext, onError, onCompletion):监听 token 变化
注册 token 状态变化监听器。同一时间可以为一个或多个 App Check 实例注册多个监听器;每当该实例关联的当前 token 发生变化时,监听器会在 UI 线程被回调。
该函数有两个重载:
重载一(Observer 对象形式):
export declare function onTokenChanged(
appCheckInstance: AppCheck,
observer: PartialObserver<AppCheckTokenResult>
): Unsubscribe;
重载二(回调函数形式):
export declare function onTokenChanged(
appCheckInstance: AppCheck,
onNext: (tokenResult: AppCheckTokenResult) => void,
onError?: (error: Error) => void,
onCompletion?: () => void
): Unsubscribe;
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| appCheckInstance | AppCheck | App Check 服务实例 |
| observer | PartialObserver <AppCheckTokenResult> | 含 next、error、complete 属性的对象。next 在 token 变化时携带 AppCheckTokenResult 被调用;error 可选,在监听器(next 函数)抛出错误时被调用;complete 未被使用,因为 token 流是永不结束的 |
| onNext | (tokenResult: AppCheckTokenResult) => void | token 变化时调用,参数为 AppCheckTokenResult |
| onError | (error: Error) => void | 可选。监听器(onNext 函数)抛出错误时被调用 |
| onCompletion | () => void | 当前未使用,因为 token 流永不结束 |
返回值: Unsubscribe —— 一个用于取消该监听器的函数。
源码行为(packages/app-check/src/api.ts): 实现会将两种调用形式统一归一化为 next / error 回调(Observer 的 next / error 方法会被 bind 到 observer 对象上),然后调用内部 addTokenListener(appCheckInstance, ListenerType.EXTERNAL, nextFn, errorFn);返回的取消函数则调用 removeTokenListener(appCheckInstance.app, nextFn)。注意 onCompletion 在注释中明确说明"永远不会被调用,仅为了与 JS API 中 Observer 模式保持一致而保留"。
内部 addTokenListener(packages/app-check/src/internal-api.ts)会:将观察者追加到状态数组;若内存中已有有效 token,则异步立即回调一次;待 cachedTokenPromise 解析后启动 token 刷新器(Refresher)。而 removeTokenListener(packages/app-check/src/internal-api.ts)在监听器全部移除后会停止刷新器。
setTokenAutoRefreshEnabled(appCheckInstance, isTokenAutoRefreshEnabled):开关自动刷新
设置 App Check 是否按需自动刷新 token。
签名:
export declare function setTokenAutoRefreshEnabled(
appCheckInstance: AppCheck,
isTokenAutoRefreshEnabled: boolean
): void;
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| appCheckInstance | AppCheck | App Check 服务实例 |
| isTokenAutoRefreshEnabled | boolean | 为 true 时 SDK 按需自动刷新 App Check token。此值会覆盖 initializeAppCheck() 时设置的任何值 |
返回值: void
源码行为(packages/app-check/src/api.ts): 若状态中已存在 token 刷新器(state.tokenRefresher),为 true 时调用 refresher.start(),为 false 时调用 refresher.stop();同时更新状态字段 isTokenAutoRefreshEnabled。由于内部刷新器的启动条件同时要求 state.isTokenAutoRefreshEnabled 为 true,该函数是运行时动态切换自动刷新的唯一入口。
类详解:三种 App Check Provider
三个 Provider 类的共同点是都实现了内部 AppCheckProvider 接口(packages/app-check/src/types.ts),该接口要求实现 getToken(isLimitedUse?) 与 initialize(app) 两个方法。完整实现位于 packages/app-check/src/providers.ts。
ReCaptchaV3Provider:reCAPTCHA V3 Provider
能够获取 reCAPTCHA V3 token 并将其兑换为 App Check token 的 Provider。
签名:
export declare class ReCaptchaV3Provider implements AppCheckProvider
构造函数:
constructor(_siteKey: string);
_siteKey:ReCAPTCHA V3 的 site key(字符串)。
源码要点(packages/app-check/src/providers.ts):
initialize(app)中保存 app 引用、获取 heartbeat 服务 Provider,并调用initializeRecaptchaV3(app, this._siteKey)加载 reCAPTCHA V3 脚本并渲染 widget(初始化失败被静默吞掉);getToken()先检查节流状态,然后通过getReCAPTCHAToken获取 attestation token(失败抛appCheck/recaptcha-error),再通过getExchangeRecaptchaV3TokenRequest构造兑换请求(packages/app-check/src/client.ts)调用exchangeToken;- 兑换端点请求体为
{ recaptcha_v3_token: <token> },URL 形如${BASE_ENDPOINT}/projects/${projectId}/apps/${appId}:exchangeRecaptchaV3Token?key=${apiKey}; isEqual()仅当另一 Provider 同为ReCaptchaV3Provider且 siteKey 相同时返回 true,这是initializeAppCheck()判断"等价重复初始化"的依据。
ReCaptchaEnterpriseProvider:reCAPTCHA Enterprise Provider
能够获取 reCAPTCHA Enterprise token 并将其兑换为 App Check token 的 Provider。
签名:
export declare class ReCaptchaEnterpriseProvider implements AppCheckProvider
构造函数:
constructor(_siteKey: string);
_siteKey:reCAPTCHA Enterprise 的 score-based site key。
源码要点(packages/app-check/src/providers.ts): 与 ReCaptchaV3Provider 结构完全对称:初始化时加载 https://www.google.com/recaptcha/enterprise.js 脚本(packages/app-check/src/recaptcha.ts),兑换请求体为 { recaptcha_enterprise_token: <token> }(packages/app-check/src/client.ts),同样具备节流与 isEqual 逻辑。
CustomProvider:自定义 Provider
自定义 Provider 类,允许你完全自行实现证明(attestation)流程,也是 Node.js 环境下使用 App Check 的唯一途径。
签名:
export declare class CustomProvider implements AppCheckProvider
构造函数:
constructor(_customProviderOptions: CustomProviderOptions);
_customProviderOptions:CustomProviderOptions。
源码要点(packages/app-check/src/providers.ts):
getToken()调用你提供的_customProviderOptions.getToken()拿到AppCheckToken,然后尝试从 token 中提取 JWTiat(issued-at)字段(JWT 时间戳以秒为单位):若iat存在、小于当前时间且大于 0,则换算为毫秒作为issuedAtTimeMillis,否则使用当前时间。该时间戳是内部主动刷新调度(见下文)的重要依据;isEqual()通过比较getToken函数的toString()字符串判断两个 CustomProvider 是否等价;initialize(app)仅保存 app 引用,不做任何网络或脚本加载操作。
接口与类型别名:理解返回结构与配置项
AppCheck 接口
Firebase App Check 服务接口。
export interface AppCheck
属性:
| 属性 | 类型 | 说明 |
|---|---|---|
| app | FirebaseApp | 此 AppCheck 实例所关联的 FirebaseApp |
AppCheckOptions 接口
App Check 初始化选项,是 initializeAppCheck() 的 options 参数类型。
export interface AppCheckOptions
属性:
| 属性 | 类型 | 说明 |
|---|---|---|
| provider | CustomProvider | ReCaptchaV3Provider | ReCaptchaEnterpriseProvider | reCAPTCHA V3 Provider、reCAPTCHA Enterprise Provider 或自定义 Provider |
| isTokenAutoRefreshEnabled | boolean | 设为 true 时启用 App Check token 的后台自动刷新(可选) |
AppCheckToken 接口
Provider 返回的 token。
export interface AppCheckToken
属性:
| 属性 | 类型 | 说明 |
|---|---|---|
| token | string | token 字符串 |
| expireTimeMillis | number | token 过期后的本地时间戳(只读) |
CustomProvider 的 getToken 函数需要返回此结构。
AppCheckTokenResult 接口
getToken() 返回的结果。
export interface AppCheckTokenResult
属性:
| 属性 | 类型 | 说明 |
|---|---|---|
| token | string | JWT 格式的 token 字符串(只读) |
需要注意的是:公开的 AppCheckTokenResult(packages/app-check/src/public-types.ts)只暴露 token 字段,而内部类型 AppCheckTokenResult(packages/app-check/src/types.ts)还包含可选的 error 与 internalError 字段,供内部库(storage、functions 等)识别错误场景——当发生错误时公开 API 会直接抛出,而内部消费者则收到携带 dummy token 与错误字段的结果。
CustomProviderOptions 接口
创建 CustomProvider 时的选项。
export interface CustomProviderOptions
属性:
| 属性 | 类型 | 说明 |
|---|---|---|
| getToken | () => Promise<AppCheckToken> | 通过自定义 Provider 服务获取 App Check token 的函数 |
AppCheckTokenListener 类型别名
每当 App Check token 变化时被调用的监听器。
export type AppCheckTokenListener = (token: AppCheckTokenResult) => void;
源码级原理:token 生命周期与刷新机制
token 获取与缓存流程
内部 getToken(packages/app-check/src/internal-api.ts)实现了文档所述的完整查找链:
- 内存检查:读取
state.token,若存在但已过期(expireTimeMillis - Date.now() <= 0,见isValidinternal-api.ts)则清空; - 持久化检查:无有效内存 token 时等待
state.cachedTokenPromise(该 Promise 在激活时通过readTokenFromStorage从 IndexedDB 读取,见 packages/app-check/src/storage.ts);缓存 token 有效则直接返回,无效则清除持久化内容; - 返回缓存:
forceRefresh为 false 且 token 有效时直接返回,不发网络请求; - 调试模式分支:若处于调试模式且无缓存,用调试 token 调用
exchangeDebugToken端点获取 token; - 正常兑换:否则调用
state.provider.getToken()请求新 token。关键设计是 in-flight 请求合并:若state.exchangeTokenPromise已存在(已有请求在途),后续调用直接 await 同一 Promise,从而"挂接到最近的进行中请求";请求 settle 后置空该字段; - 写入存储:成功拿到新 token 后写入内存状态与 IndexedDB(internal-api.ts),并通知所有监听器。
当兑换失败时,内部实现返回 makeDummyTokenResult(internal-api.ts):token 字段填充一个 base64 编码的 { error: 'UNKNOWN_ERROR' } dummy token(formatDummyToken,internal-api.ts),错误放入 error 字段。这保证了"此函数总是 resolve"的契约,内部消费者可据此降级处理。
主动刷新(proactive refresh)与节流退避
自动刷新由 Refresher 类(packages/app-check/src/proactive-refresh.ts)驱动,其调度参数定义在 packages/app-check/src/constants.ts:
- 成功后的下次刷新时间:
issuedAtTimeMillis + (expireTimeMillis - issuedAtTimeMillis) * 0.5 + 5 分钟,即"签发时间 + 50% TTL + 5 分钟",且上限不超过expireTimeMillis - 5 分钟(internal-api.ts); - 失败重试退避:首次重试等待 30 秒(
RETRIAL_MIN_WAIT),每次失败翻倍,最大 16 分钟(RETRIAL_MAX_WAIT); - HTTP 状态码特殊处理:兑换请求返回 403(attestation 失败 / API key 错误 / 项目已删除)或 404(URL 畸形)时,
setBackoff会直接封禁重试 1 天(ONE_DAY);其他错误走指数退避(providers.ts)。处于节流期时getToken会抛出appCheck/throttled或appCheck/initial-throttle错误(providers.ts)。
刷新器与监听器联动:addTokenListener 之后(含内部库注册的 ListenerType.INTERNAL 监听器,见 factory.ts)会启动刷新器;当 isTokenAutoRefreshEnabled 为 true 时,initializeAppCheck 会主动注册一个内部监听器,实现"预热"——提前取好 token,避免内部库首次请求时产生延迟(api.ts)。
调试模式(Debug Mode)
调试模式由全局变量 FIREBASE_APPCHECK_DEBUG_TOKEN 触发(packages/app-check/src/debug.ts):可设为字符串(直接指定调试 token)或 true(从 IndexedDB 读取或通过 crypto.randomUUID() 生成新 token)。调试模式下 initializeAppCheck() 会在控制台打印 Firebase App Check debug token: ...,并提示通过 Firebase Console 或 CLI(firebase appcheck:debugtokens:create / delete)注册该 token(storage.ts)。
组件注册与内部服务
@firebase/app-check 通过 _registerComponent 注册两个组件(packages/app-check/src/index.ts):
app-check(PUBLIC):公开服务,实例化模式为EXPLICIT,仅在initializeAppCheck()显式调用时创建;实例创建完成后自动初始化内部组件app-check-internal;app-check-internal:提供给其他 Firebase SDK(如 Storage、Functions)的内部接口FirebaseAppCheckInternal(packages/app-check/src/types.ts),封装了getToken、getLimitedUseToken、addTokenListener、removeTokenListener。
服务实现类 AppCheckService(packages/app-check/src/factory.ts)在 _delete() 时会移除全部 token 监听器。
错误码参考
SDK 的错误统一由 ErrorFactory('appCheck', 'AppCheck', ERRORS) 生成(packages/app-check/src/errors.ts),常见错误码如下:
| 错误码 | 触发场景 |
|---|---|
appCheck/already-initialized |
以不同选项重复调用 initializeAppCheck() |
appCheck/use-before-activation |
在调用 initializeAppCheck() 之前使用 App Check |
appCheck/fetch-network-error |
网络连接失败 |
appCheck/fetch-status-error |
兑换端点返回非 200 HTTP 状态 |
appCheck/fetch-parse-error |
响应解析失败或 ttl 字段不符合 Protobuf Duration 格式 |
appCheck/storage-open / storage-get / storage-set |
IndexedDB 存储操作失败 |
appCheck/recaptcha-error |
reCAPTCHA token 获取失败 |
appCheck/initial-throttle / appCheck/throttled |
请求被节流(含 403/404 封禁 1 天与指数退避两种情况) |
实践建议与限制说明
- 安装与引入:
@firebase/app-check的 README(packages/app-check/README.md)明确提示该包不面向直接使用,应通过官方firebase聚合包接入;包当前版本为0.13.1(packages/app-check/package.json),peerDependency 为@firebase/app。 - Web 端标准接入:浏览器中优先使用
ReCaptchaV3Provider或ReCaptchaEnterpriseProvider,配合isTokenAutoRefreshEnabled: true让 SDK 自动维护 token; - Node.js 场景:必须使用
CustomProvider,自行实现后端签发AppCheckToken(含token与expireTimeMillis)的证明逻辑; - 非 Firebase 后端:需要重放保护时使用
getLimitedUseToken(),它绕开缓存与刷新状态、直接向端点索取一次性 token; - token 变更感知:需要及时感知 token 更新(例如刷新上游 SDK 的 Authorization 头)时注册
onTokenChanged监听器,并妥善处理返回的取消函数。