临时预览账户允许您在向 Cloudflare 进行身份验证之前部署和测试 Workers。随后,您可以认领该账户以保留其部署和支持的资源。
Cloudflare Drop ↗ 为静态站点演示了这种预览和认领生命周期。平台可以使用 REST API 为生成的应用程序提供类似的体验。
有关设计背景,请参阅 Temporary Cloudflare Accounts for AI agents ↗。
根据谁控制账户配置来选择集成方式:
| 集成方式 | 适用场景 | 配置行为 |
|---|---|---|
使用 wrangler deploy --temporary 的 Wrangler |
AI 代理或工具运行 Wrangler | Wrangler 创建或复用账户并打印认领 URL |
api.cloudflare.com/client/v4/provisioning/previews 处的 REST API |
您的平台后端控制部署体验 | 您的后端接收临时凭据和认领 URL |
对于生产和持续集成与持续部署(CI/CD),请使用永久 Cloudflare 账户。通过 wrangler login 或 Cloudflare API token 进行身份验证。
当 AI 代理或工具运行部署命令时,请使用 Wrangler。Wrangler 管理工作量证明挑战、凭据和认领 URL。
Wrangler 4.102.0 或更高版本会打印指导信息,提示您使用 --temporary 重新运行未经身份验证的部署。
-
安装或更新 Wrangler 至 4.102.0 或更高版本。
有关安装说明,请参阅安装和更新。
-
为您的 AI 代理提供部署提示。
例如:
Make a very simple Hello World Cloudflare Worker in TypeScript and deploy it using the Wrangler CLI. Do not ask me questions. -
让代理运行
wrangler deploy。在未进行身份验证的非交互式会话中,Wrangler 会打印类似以下内容的输出:
To continue without logging in, rerun this command with `--temporary`. Wrangler will use a temporary account and print a claim URL.此输出告知代理应使用
--temporary重新运行该命令。 -
使用
--temporary重新运行部署。npx wrangler deploy --temporaryyarn wrangler deploy --temporarypnpm wrangler deploy --temporaryWrangler 会打印类似以下内容的输出:
Continuing means you accept Cloudflare's Terms of Service (https://www.cloudflare.com/terms/) and Privacy Policy (https://www.cloudflare.com/privacypolicy/). Temporary account ready: Account: example-name (created) Claim within: 60 minutes Claim URL: https://dash.cloudflare.com/claim-preview?claimToken=<CLAIM_TOKEN> Uploaded example-worker Deployed example-worker triggers https://example-worker.example-name.workers.dev -
(可选)在认领账户之前重新部署更改。
在凭据和认领 URL 仍然有效期间,Wrangler 会缓存并复用该账户。输出会标识 Wrangler 是创建了账户还是复用了现有账户。
当您运行
wrangler login或wrangler logout时,Wrangler 会清除缓存的账户。Wrangler 将这些临时值存储在当前操作系统用户的全局配置目录中。请勿在平台用户之间共享此目录。
当您的平台后端控制部署时,请使用 REST API。后端在用户进行身份验证之前配置账户,然后部署支持的资源。
所有配置和部署调用都应从您的后端发起。配置响应包含敏感凭据和认领 URL。
以下图表展示了平台如何在后端保留临时凭据,同时用户预览并认领部署:
flowchart LR
accTitle: 平台预览与认领架构
accDescr: 用户接受 Cloudflare 的政策并在平台 UI 中请求预览。受信任的平台后端创建临时账户,将 account.apiToken 保持私有,部署 Worker,并仅向 UI 返回预览 URL 和认领 URL。认领 URL 是仅向目标用户展示的 bearer 凭据。用户在 Cloudflare 仪表板中认领账户。未来的平台部署需要单独的 OAuth 流程。
USER((User))
subgraph PLATFORM["Platform"]
direction TB
UI["Platform UI<br/>No temporary API token"]
BACKEND["Trusted platform backend<br/>Stores account.apiToken"]
UI -->|"2. Request preview"| BACKEND
BACKEND -->|"10. Preview URL and bearer claim URL only"| UI
end
subgraph CLOUDFLARE["Cloudflare"]
direction TB
API["Cloudflare API"]
DASHBOARD["Cloudflare dashboard<br/>Claims the account"]
end
USER -->|"1. Accept policies and generate application"| UI
BACKEND -->|"3. Request challenge"| API
API -->|"4. Challenge parameters"| BACKEND
BACKEND -->|"5. Solve challenge locally"| BACKEND
BACKEND -->|"6. Create temporary account with solution"| API
API -->|"7. Account ID, API token, and claim URL"| BACKEND
BACKEND -->|"8. Deploy Worker and request subdomain"| API
API -->|"9. workers.dev subdomain"| BACKEND
UI -->|"11. Show live preview and intended-user-only claim link"| USER
USER -->|"12. Sign in and complete claim"| DASHBOARD
DASHBOARD -.->|"Optional after claim"| OAUTH["Separate OAuth flow<br/>for future platform deployments"]
在创建临时账户之前,请求工作量证明挑战:
curl "https://api.cloudflare.com/client/v4/provisioning/previews/challenge" \
-X POST \
-H "Content-Type: application/json" \
--data '{}'响应包含挑战令牌、种子和难度参数:
{
"success": true,
"result": {
"challengeToken": "<CHALLENGE_TOKEN>",
"seed": "<BASE64URL_32_BYTE_SEED>",
"k": 8000,
"g": 2000
},
"errors": [],
"messages": []
}通过计算顺序 SHA-256 检查点链来求解挑战:
- 将
seed解码为 Base64URL。解码结果必须为 32 字节。 - 计算
checkpoint[0] = SHA-256(seed)。 - 对于从
0到k - 1的每个分段,从前一个检查点开始计算g次顺序 SHA-256 哈希,然后追加结果。 - 拼接所有
k + 1个检查点。每个检查点为 32 字节。 - 使用标准 Base64 对拼接后的字节进行编码。将该值作为
solution.checkpoints发送。
在求解挑战之前,要求 k 和 g 为正整数。如果 seed 未解码为 32 字节,或 k * g 超过 64,000,000,则拒绝该挑战。
以下 Node.js 示例应用这些边界,并返回创建请求所需的对象:
import { createHash } from "node:crypto";
function sha256(value) {
return createHash("sha256").update(value).digest();
}
export function solvePreviewChallenge({ challengeToken, seed, k, g }) {
const seedBytes = Buffer.from(seed, "base64url");
if (seedBytes.length !== 32) {
throw new Error("seed must decode to 32 bytes");
}
if (!Number.isInteger(k) || k <= 0) {
throw new Error("k must be a positive integer");
}
if (!Number.isInteger(g) || g <= 0) {
throw new Error("g must be a positive integer");
}
if (k * g > 64_000_000) {
throw new Error("k * g must not exceed 64,000,000");
}
const checkpoints = [];
let hash = sha256(seedBytes);
checkpoints.push(hash);
for (let segment = 0; segment < k; segment++) {
for (let iteration = 0; iteration < g; iteration++) {
hash = sha256(hash);
}
checkpoints.push(hash);
}
return {
challengeToken,
solution: {
checkpoints: Buffer.concat(checkpoints).toString("base64"),
},
};
}import { createHash } from "node:crypto";
type PreviewChallenge = {
challengeToken: string;
seed: string;
k: number;
g: number;
};
function sha256(value: Uint8Array): Buffer {
return createHash("sha256").update(value).digest();
}
export function solvePreviewChallenge({
challengeToken,
seed,
k,
g,
}: PreviewChallenge) {
const seedBytes = Buffer.from(seed, "base64url");
if (seedBytes.length !== 32) {
throw new Error("seed must decode to 32 bytes");
}
if (!Number.isInteger(k) || k <= 0) {
throw new Error("k must be a positive integer");
}
if (!Number.isInteger(g) || g <= 0) {
throw new Error("g must be a positive integer");
}
if (k * g > 64_000_000) {
throw new Error("k * g must not exceed 64,000,000");
}
const checkpoints: Buffer[] = [];
let hash = sha256(seedBytes);
checkpoints.push(hash);
for (let segment = 0; segment < k; segment++) {
for (let iteration = 0; iteration < g; iteration++) {
hash = sha256(hash);
}
checkpoints.push(hash);
}
return {
challengeToken,
solution: {
checkpoints: Buffer.concat(checkpoints).toString("base64"),
},
};
}在创建账户之前,要求用户接受 Cloudflare 的服务条款 ↗和隐私政策 ↗。仅在用户接受两者之后,才将 acceptTermsOfService 设置为 "yes"。
然后发送工作量证明解决方案以及必需的政策字段:
curl "https://api.cloudflare.com/client/v4/provisioning/previews" \
-X POST \
-H "Content-Type: application/json" \
--data '{
"termsOfService": "https://www.cloudflare.com/terms/",
"privacyPolicy": "https://www.cloudflare.com/privacypolicy/",
"acceptTermsOfService": "yes",
"challengeToken": "<CHALLENGE_TOKEN>",
"solution": {
"checkpoints": "<BASE64_CHECKPOINTS>"
}
}'响应包含临时凭据和认领 URL:
{
"success": true,
"result": {
"account": {
"id": "<TEMPORARY_ACCOUNT_ID>",
"name": "<TEMPORARY_ACCOUNT_NAME>",
"type": "standard",
"apiToken": "<TEMPORARY_ACCOUNT_API_TOKEN>",
"tokenId": "<TEMPORARY_TOKEN_ID>",
"expiresAt": "<ACCOUNT_EXPIRES_AT>"
},
"claim": {
"token": "<CLAIM_TOKEN>",
"url": "https://dash.cloudflare.com/claim-preview?claimToken=<CLAIM_TOKEN>",
"expiresAt": "<CLAIM_EXPIRES_AT>"
}
},
"errors": [],
"messages": []
}在使用响应之前,确认 success 为 true。验证 account.id、account.apiToken、account.expiresAt、claim.url 和 claim.expiresAt 是否存在。
将 account.id 和 account.apiToken 与支持 Cloudflare API 端点配合使用。临时值仅用于支持的资源操作。
临时账户令牌不授予永久账户 API 的所有权限。不支持的操作会返回授权错误。
以下示例使用 Workers Script Upload API 上传并部署 Worker,然后检索账户的 workers.dev 子域名。
curl "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/workers/scripts/$SCRIPT_NAME" \
-X PUT \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-F 'metadata={"main_module":"worker.mjs","compatibility_date":"<YYYY-MM-DD>"};type=application/json' \
-F '[email protected];type=application/javascript+module'使用临时凭据调用 Get Subdomain endpoint:
curl "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/workers/subdomain" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN"对于可在 workers.dev 上访问的脚本,将 result.subdomain 与脚本名称组合以创建部署 URL:https://<SCRIPT_NAME>.<SUBDOMAIN>.workers.dev。
配置临时账户后,使用 Cloudflare TypeScript SDK ↗ 执行支持的资源操作:
import Cloudflare from "cloudflare";
export async function deployWorker(
accountId: string,
apiToken: string,
scriptName: string,
compatibilityDate: string,
scriptContent: string,
) {
const client = new Cloudflare({ apiToken });
const workerModule = new File([scriptContent], "worker.mjs", {
type: "application/javascript+module",
});
await client.workers.scripts.update(scriptName, {
account_id: accountId,
metadata: {
main_module: "worker.mjs",
compatibility_date: compatibilityDate,
},
files: [workerModule],
});
const { subdomain } = await client.workers.subdomains.get({
account_id: accountId,
});
return `https://${scriptName}.${subdomain}.workers.dev`;
}将部署 URL 和 claim.url 提供给目标用户。
目标用户必须在 60 分钟内完成认领。在截止时间之前打开认领 URL 并不足够。
用户打开 URL,登录 Cloudflare 或创建账户,然后完成仪表板提示。
如果用户未完成认领,Cloudflare 会删除该账户及其资源。
使用 Wrangler 时,如果临时凭据或认领 URL 过期,请重新运行 wrangler deploy --temporary。Wrangler 会配置新账户并打印新的认领 URL。
对于 REST 集成,如果在认领之前 account.expiresAt 或 claim.expiresAt 已过期,请请求新的挑战和账户。
认领后,Worker 和支持的资源会保留在已认领的账户中。
若要继续使用 Wrangler,请运行 wrangler login,然后不使用 --temporary 进行部署。认领不会向平台授予对该账户的永久访问权限。
对于后续部署,通过您正常的已身份验证流程连接已认领的账户,例如 Cloudflare OAuth client。
下表总结了支持的功能和限制。临时凭据不授予这些资源的所有操作权限。
| 支持的产品或资源 | 支持的功能或限制 |
|---|---|
| Workers | 在 workers.dev 上的部署 |
| Workers Static Assets | 最多 1,000 个文件,每个资源最大 5 MiB |
| Workers KV | 创建、列出、重命名和删除命名空间;put、get、list 和 delete 键;bulk put、get 和 delete |
| D1 | 一个数据库,每个数据库最多 100 MB,总计 100 MB |
| Durable Objects | 部署带有 Durable Object 绑定(binding)和迁移的 Workers |
| Hyperdrive | 最多两个数据库配置和 10 个连接 |
| Queues | 最多 10 个队列 |
| mTLS and CA certificates | wrangler cert 上传、列出和删除操作 |
account.apiToken授权支持的资源操作。切勿在浏览器响应或客户端代码中暴露它。- 将
claim.url视为 bearer 凭据。任何拥有该 URL 的人都可以认领临时账户的所有权。 - 仅在后端存储或限定于目标用户的服务器端会话存储中保存这两个值。仅向该用户提供
claim.url。 - 从日志、分析和支持遥测中排除这两个值。在不再需要时删除存储的副本,且不得晚于任一返回的过期时间。
- Cloudflare 在创建账户之前要求工作量证明检查。Wrangler 会处理该检查,而 REST 集成必须提交解决方案。
- Cloudflare 对临时账户创建进行速率限制。请等待后再重试,或使用永久账户进行身份验证。
--temporary仅支持未经身份验证的使用。现有的 OAuth、API token 或 global API key 凭据会导致错误。--temporary不是全局标志。只有支持临时凭据的命令才包含此选项。- 临时账户配置仅通过默认公共 API 端点提供。FedRAMP High API 端点不提供此功能。
- Cloudflare 可能会拒绝未通过额外滥用防护检查的请求。