dpop-adoption
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDPoP Adoption & Identity Security Architecture
DPoP 落地与身份安全架构
Demonstrating Proof-of-Possession (DPoP, RFC 9449) secures OAuth 2.0 refresh
tokens against interception and replay attacks by cryptographically binding them
to a private key held exclusively by the client. In Google's OAuth 2.0 platform,
DPoP binds the refresh token at the token endpoint, while access tokens issued
for Google APIs are standard Bearer tokens ().
token_type: "Bearer"展示Proof-of-Possession(DPoP,RFC 9449)通过将OAuth 2.0刷新令牌加密绑定到客户端独有的私钥,防止令牌被拦截和重放攻击。在Google OAuth 2.0平台中,DPoP在令牌端点绑定刷新令牌,而针对Google API颁发的访问令牌为标准Bearer令牌()。
token_type: "Bearer"1. Core Cryptographic & Architectural Invariants
1. 核心加密与架构不变量
When implementing DPoP helpers or upgrading HTTP clients, you MUST adhere to the
following strict security invariants:
在实现DPoP工具或升级HTTP客户端时,必须严格遵守以下安全不变量:
A. Universal WebCrypto & Runtime Compatibility
A. 通用WebCrypto与运行时兼容性
- In modern ES6 JavaScript (for Node 18+ and browsers), ALWAYS access
"type": "module"directly after verifying the environment context.globalThis.crypto - NEVER import legacy CommonJS modules via or reference browser-scoped
require('node:crypto'), as these cause module initialization crashes across hybrid runtimes.window.crypto
- 在现代ES6 JavaScript环境(Node 18+及浏览器的)中,验证环境上下文后,始终直接访问
"type": "module"。globalThis.crypto - 切勿通过导入传统CommonJS模块,或引用浏览器作用域的
require('node:crypto'),这会导致混合运行时环境下的模块初始化崩溃。window.crypto
B. Hardware-Backed Non-Extractable Key Persistence
B. 硬件级不可提取密钥持久化
- Generate an Elliptic Curve key pair on the SECP256R1 () curve:
P-256.{ name: 'ECDSA', namedCurve: 'P-256' } - CRITICAL SECURITY GUARDRAIL: The private key MUST be configured as
non-extractable (). This guarantees the private key can never leave the hardware cryptographic boundary (Secure Enclave, Android KeyStore, or JS sandbox memory), thwarting XSS and dependency token theft attacks.
extractable: false - The public key MUST remain exportable () to allow emitting JSON Web Keys (JWKs).
extractable: true
- 在SECP256R1()曲线上生成椭圆曲线密钥对:
P-256。{ name: 'ECDSA', namedCurve: 'P-256' } - 关键安全防护: 私钥必须配置为不可提取()。这保证私钥永远无法离开硬件加密边界(安全飞地、Android KeyStore或JS沙箱内存),阻止XSS和依赖包令牌窃取攻击。
extractable: false - 公钥必须保持可导出(),以便生成JSON Web Keys(JWK)。
extractable: true
C. Public JWK Formatting Standards
C. 公钥JWK格式化标准
- When exporting public keys to attach to DPoP Proof JWT headers, construct a
clean JWK dictionary containing strictly:
"kty": "EC""crv": "P-256"- : Base64URL-encoded x-coordinate without trailing equal sign padding (
"x").= - : Base64URL-encoded y-coordinate without trailing equal sign padding (
"y").=
- NEVER expose private key parameters () or superfluous metadata.
"d"
- 导出公钥并附加到DPoP Proof JWT头部时,需构建仅包含以下字段的标准JWK字典:
"kty": "EC""crv": "P-256"- : 无尾部等号填充(
"x")的Base64URL编码x坐标。= - : 无尾部等号填充(
"y")的Base64URL编码y坐标。=
- 切勿暴露私钥参数()或多余元数据。
"d"
D. IEEE P1363 vs. ASN.1 DER Signature Disambiguation
D. IEEE P1363与ASN.1 DER签名区分
- DPoP Proof JWTs require raw concatenated coordinate signatures ($R \parallel S$, exactly 64 bytes for P-256) per IEEE P1363 and RFC 7518.
- WebCrypto Native Rule: In standard WebCrypto (), ECDSA signatures are ALREADY emitted natively in raw IEEE P1363 format (concatenated 32-byte
crypto.subtle.signandrbuffers, 64 bytes total). DO NOT attempt DER-to-Raw conversion onsoutputs, as parsing a 64-byte raw buffer as ASN.1 DER causes an immediate runtime exception (crypto.subtle.sign). Directly base64url-encode the raw ArrayBuffer.Invalid DER sequence - Legacy API Fallback: If and only if implementing in legacy Java/Android
() or Node CommonJS (
java.security.Signature), convert ASN.1 DER output to raw 64-byte IEEE P1363 format before base64url encoding.crypto.createSign
- DPoP Proof JWT要求遵循IEEE P1363和RFC 7518的原始拼接坐标签名($R \parallel S$,P-256算法下恰好64字节)。
- WebCrypto原生规则: 在标准WebCrypto()中,ECDSA签名原生以原始IEEE P1363格式输出(拼接的32字节
crypto.subtle.sign和r缓冲区,共64字节)。不要对s的输出进行DER转原始格式转换,将64字节原始缓冲区解析为ASN.1 DER会立即引发运行时异常(crypto.subtle.sign)。直接对原始ArrayBuffer进行base64url编码即可。Invalid DER sequence - 传统API兼容方案: 仅当在传统Java/Android()或Node CommonJS(
java.security.Signature)环境中实现时,才需将ASN.1 DER输出转换为64字节原始IEEE P1363格式,再进行base64url编码。crypto.createSign
E. SPA & Backend-for-Frontend (BFF) Architecture
E. SPA与前端后端(BFF)架构
- Secretless SPAs Limitation: Pure client-side single-page applications
(SPAs) without a backend cannot use DPoP directly with Google APIs due to
requirements on server endpoints and browser CORS limitations on the
client_secretresponse header.DPoP-Nonce - BFF Pattern: To secure SPAs with DPoP, route authorization and token
refresh requests through a Backend-for-Frontend (BFF) server-side client.
The BFF sets , binds refresh tokens server-side using DPoP, and maintains secure session cookies with the frontend.
access_type=offline
- 无密钥SPA限制: 纯客户端单页应用(SPA)无后端支持时,无法直接结合Google API使用DPoP,原因是服务器端点要求,且浏览器CORS限制
client_secret响应头的获取。DPoP-Nonce - BFF模式: 要通过DPoP保护SPA,需将授权和令牌刷新请求路由到前端后端(BFF)服务器端客户端。BFF设置,通过DPoP在服务器端绑定刷新令牌,并与前端维护安全会话Cookie。
access_type=offline
2. Implementation Rules & Mandatory Public API
2. 实现规则与强制公开API
When creating new modules, your module MUST explicitly export all functions
below to integrate cleanly with CI/CD verification harnesses and automated
probers. When inspecting or refactoring existing codebases, ensure equivalent
cryptographic and RFC 9449 logic is present. Obey strict claim derivation logic
in all cases:
创建新模块时,必须显式导出以下所有函数,以与CI/CD验证工具和自动化探针无缝集成。检查或重构现有代码库时,需确保存在等效的加密和RFC 9449逻辑。在所有场景下严格遵循声明推导逻辑:
A. DPoP Proof JWT Claim Derivation Rules (createDPoPProof
)
createDPoPProofA. DPoP Proof JWT声明推导规则(createDPoPProof
)
createDPoPProofWhen generating the DPoP Proof JWT in :
createDPoPProof1. JOSE Header (, , ):
typalgjwkjavascript
// Header
{
"typ": "dpop+jwt",
"alg": "ES256",
"jwk": await exportPublicJWK(publicKey)
}2. Payload Claims:
- : Uppercase HTTP Method (
"htm"for token requests)."POST" - : Target URI stripped of query parameters and hash fragments using
"htu". For token requests, this issanitizeHTU(htu).https://oauth2.googleapis.com/token - : Current integer epoch timestamp in seconds (
"iat").Math.floor(Date.now() / 1000) - (Critical Invariant):
"jti"- If an explicit argument is provided to
jti, use that exact string over all others.createDPoPProof - Otherwise, if an argument is provided (during initial code exchange), set
authCodewherejti = await calculateAuthCodeJti(authCode)computescalculateAuthCodeJtito ensure the DPoP proof is cryptographically bound to the authorization code.base64url(sha256(authCode)) - Only if neither nor
jtiis provided, generate a fresh cryptographic random string viaauthCode(such asgenerateRandomString()base64url encoded).crypto.getRandomValues(new Uint8Array(24))
- If an explicit
- (Optional): If an
"ath"argument is provided for RFC 9449 resource requests, computeaccessTokenviabase64url(sha256(accessToken))and inject it (RFC 9449 Section 6.1).calculateATH(accessToken) - (Optional): If a
"nonce"argument is provided, inject it directly into the payload.nonce
在中生成DPoP Proof JWT时:
createDPoPProof1. JOSE头部(, , ):
typalgjwkjavascript
// Header
{
"typ": "dpop+jwt",
"alg": "ES256",
"jwk": await exportPublicJWK(publicKey)
}2. 载荷声明:
- : 大写HTTP方法(令牌请求使用
"htm")。"POST" - : 使用
"htu"去除查询参数和哈希片段后的目标URI。令牌请求的目标URI为sanitizeHTU(htu)。https://oauth2.googleapis.com/token - : 当前整数时间戳(秒级,
"iat")。Math.floor(Date.now() / 1000) - (关键不变量):
"jti"- 如果传入了明确的
createDPoPProof参数,则直接使用该字符串。jti - 否则,如果传入了参数(初始授权码交换阶段),设置
authCode,其中jti = await calculateAuthCodeJti(authCode)计算calculateAuthCodeJti,确保DPoP Proof与授权码加密绑定。base64url(sha256(authCode)) - 仅当既无也无
jti参数时,通过authCode生成全新的加密随机字符串(例如对generateRandomString()进行base64url编码)。crypto.getRandomValues(new Uint8Array(24))
- 如果
- (可选):如果为RFC 9449资源请求传入了
"ath"参数,通过accessToken计算calculateATH(accessToken)并注入该声明(RFC 9449第6.1节)。base64url(sha256(accessToken)) - (可选):如果传入了
"nonce"参数,直接注入到载荷中。nonce
B. Explicit Export Signatures
B. 显式导出签名
javascript
// 1. Key generation & JWK export
export async function generateDPoPKeyPair() // -> { publicKey, privateKey } (private key extractable=false)
export async function exportPublicJWK(publicKey) // -> { kty: 'EC', crv: 'P-256', x, y }
// 2. Proof generation & validation
export async function createDPoPProof({ privateKey, publicKey, htm, htu, nonce, accessToken, authCode, jti }) // -> signed JWT string
export async function verifyDPoPProof(dpopProofJwt) // -> { isValid: boolean, header, payload, error }
export function sanitizeHTU(htu) // -> URL stripped of query and hash: const u = new URL(htu); return `${u.origin}${u.pathname}`;
// 3. Cryptographic & encoding utilities
export function base64UrlEncode(buffer) // -> Uint8Array/ArrayBuffer to base64url string without '=' padding
export function base64UrlDecode(str) // -> base64url string to Uint8Array/Buffer
export function stringToBase64Url(str) // -> UTF-8 string to base64url
export function base64UrlToString(str) // -> base64url to UTF-8 string
export function generateRandomString(byteLength = 32) // -> cryptographic random base64url string
export async function calculateATH(accessToken) // -> base64url(sha256(accessToken)) per RFC 9449 Sec 6.1
export async function calculateAuthCodeJti(code) // -> base64url(sha256(code))
export async function generatePKCE() // -> { codeVerifier (>=43 chars), codeChallenge, codeChallengeMethod: 'S256' }javascript
// 1. 密钥生成与JWK导出
export async function generateDPoPKeyPair() // -> { publicKey, privateKey } (私钥不可提取)
export async function exportPublicJWK(publicKey) // -> { kty: 'EC', crv: 'P-256', x, y }
// 2. Proof生成与验证
export async function createDPoPProof({ privateKey, publicKey, htm, htu, nonce, accessToken, authCode, jti }) // -> 已签名JWT字符串
export async function verifyDPoPProof(dpopProofJwt) // -> { isValid: boolean, header, payload, error }
export function sanitizeHTU(htu) // -> 去除查询参数和哈希的URL: const u = new URL(htu); return `${u.origin}${u.pathname}`;
// 3. 加密与编码工具
export function base64UrlEncode(buffer) // -> Uint8Array/ArrayBuffer转无'='填充的base64url字符串
export function base64UrlDecode(str) // -> base64url字符串转Uint8Array/Buffer
export function stringToBase64Url(str) // -> UTF-8字符串转base64url
export function base64UrlToString(str) // -> base64url转UTF-8字符串
export function generateRandomString(byteLength = 32) // -> 加密随机base64url字符串
export async function calculateATH(accessToken) // -> 按RFC 9449第6.1节计算base64url(sha256(accessToken))
export async function calculateAuthCodeJti(code) // -> base64url(sha256(code))
export async function generatePKCE() // -> { codeVerifier (>=43字符), codeChallenge, codeChallengeMethod: 'S256' }3. Token Endpoint & Resource Request Workflow
3. 令牌端点与资源请求流程
When integrating with Google's OAuth 2.0 platform:
- Token Endpoint Requests ():
oauth2.googleapis.com/token- Attach the DPoP Proof JWT in the HTTP header:
DPoPwhen making`DPoP: ${proofJwt}`requests for code exchange (POST) and token refresh (grant_type=authorization_code).grant_type=refresh_token
- Attach the DPoP Proof JWT in the
- Resource API Requests:
- Google's token endpoint returns . Downstream requests to Google APIs (e.g. Calendar, Drive, Gmail) use standard
"token_type": "Bearer"headers without DPoP headers.`Authorization: Bearer ${accessToken}`
- Google's token endpoint returns
- Single-Retry Nonce Challenge Loop & Workflow Isolation:
- If Google's token endpoint returns HTTP with
400 Bad Requestand aerror: "use_dpop_nonce"response header:"DPoP-Nonce"- Workflow Isolation: Google's authorization server enforces
workflow isolation between authorization code exchange and token
refresh, returning an HTTP challenge to establish a fresh nonce namespace. This is standard RFC-compliant protocol behavior, not a server failure.
400 use_dpop_nonce - Cache the fresh nonce in client state ().
this.dpopNonce - Immediately synthesize a new DPoP Proof JWT incorporating the
updated claim and a fresh
nonce.jti - Replay the failed token request exactly once. If the retried request fails, terminate immediately with an error to prevent infinite recursion.
- Workflow Isolation: Google's authorization server enforces
workflow isolation between authorization code exchange and token
refresh, returning an HTTP
- If Google's token endpoint returns HTTP
集成Google OAuth 2.0平台时:
- 令牌端点请求():
oauth2.googleapis.com/token- 在请求(授权码交换
POST和令牌刷新grant_type=authorization_code)中,将DPoP Proof JWT附加到grant_type=refresh_tokenHTTP头部:DPoP。`DPoP: ${proofJwt}`
- 在
- 资源API请求:
- Google令牌端点返回。后续向Google API(如日历、云端硬盘、Gmail)的请求使用标准
"token_type": "Bearer"头部,无需DPoP头部。`Authorization: Bearer ${accessToken}`
- Google令牌端点返回
- 单次重试Nonce挑战循环与流程隔离:
- 如果Google令牌端点返回HTTP 400错误,且错误码为并附带
"use_dpop_nonce"响应头:"DPoP-Nonce"- 流程隔离: Google授权服务器强制授权码交换与令牌刷新流程隔离,返回HTTP 400 use_dpop_nonce挑战以建立新的nonce命名空间。这是符合RFC标准的协议行为,并非服务器故障。
- 将新nonce缓存到客户端状态()。
this.dpopNonce - 立即生成包含更新后的声明和全新
nonce的DPoP Proof JWT。jti - 仅重试一次失败的令牌请求。如果重试仍失败,立即终止并抛出错误,防止无限递归。
- 如果Google令牌端点返回HTTP 400错误,且错误码为
4. Concise Agent Egress Protocol
4. 简洁代理输出协议
When prompted to synthesize or output code deliverables under this skill,
prioritize returning clean, directly importable code blocks without redundant
conversational preambles or repetitive filler. For conceptual or architectural
inquiries, provide standard direct answers.
当需要基于此技能生成或输出代码交付物时,优先返回干净、可直接导入的代码块,避免冗余的对话前缀或重复内容。对于概念或架构问题,提供直接明确的回答。
5. References & Supporting Documentation
5. 参考资料与支持文档
Developer Documentation (Google for Developers)
开发者文档(Google for Developers)
- DPoP Adoption Guide — Official Google Identity guide for implementing DPoP across authorization code exchange and token refresh.
- Using OAuth 2.0 for Web Server Applications — Offline access, refresh tokens, and server-side authorization flows.
- OAuth 2.0 Best Practices: Sender-Constrain Tokens — Recommendations for token storage, rotation, and sender-constraining.
- DPoP 落地指南 — Google Identity官方指南,介绍如何在授权码交换和令牌刷新流程中实现DPoP。
- 为Web服务器应用使用OAuth 2.0 — 离线访问、刷新令牌和服务器端授权流程。
- OAuth 2.0最佳实践:令牌发送方约束 — 令牌存储、轮换和发送方约束的建议。
Developer Knowledge MCP Server
开发者知识MCP服务器
- Agents equipped with Model Context Protocol () can query real-time Google Developer documentation using the Google Developer Knowledge MCP Server (
MCP) vianpx -y @google/mcp-developer-knowledge-serveranddeveloper_knowledge:search_documents.developer_knowledge:get_documents
- 配备Model Context Protocol()的代理可通过 Google Developer Knowledge MCP Server (
MCP),调用npx -y @google/mcp-developer-knowledge-server和developer_knowledge:search_documents查询实时Google开发者文档。developer_knowledge:get_documents