dpop-adoption

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

DPoP 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 (
    "type": "module"
    for Node 18+ and browsers), ALWAYS access
    globalThis.crypto
    directly after verifying the environment context.
  • NEVER import legacy CommonJS modules via
    require('node:crypto')
    or reference browser-scoped
    window.crypto
    , as these cause module initialization crashes across hybrid runtimes.
  • 在现代ES6 JavaScript环境(Node 18+及浏览器的
    "type": "module"
    )中,验证环境上下文后,始终直接访问
    globalThis.crypto
  • 切勿通过
    require('node:crypto')
    导入传统CommonJS模块,或引用浏览器作用域的
    window.crypto
    ,这会导致混合运行时环境下的模块初始化崩溃。

B. Hardware-Backed Non-Extractable Key Persistence

B. 硬件级不可提取密钥持久化

  • Generate an Elliptic Curve key pair on the SECP256R1 (
    P-256
    ) curve:
    { name: 'ECDSA', namedCurve: 'P-256' }
    .
  • CRITICAL SECURITY GUARDRAIL: The private key MUST be configured as non-extractable (
    extractable: false
    ). 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.
  • The public key MUST remain exportable (
    extractable: true
    ) to allow emitting JSON Web Keys (JWKs).
  • 在SECP256R1(
    P-256
    )曲线上生成椭圆曲线密钥对:
    { name: 'ECDSA', namedCurve: 'P-256' }
  • 关键安全防护: 私钥必须配置为不可提取
    extractable: false
    )。这保证私钥永远无法离开硬件加密边界(安全飞地、Android KeyStore或JS沙箱内存),阻止XSS和依赖包令牌窃取攻击。
  • 公钥必须保持可导出(
    extractable: true
    ),以便生成JSON Web Keys(JWK)。

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"
    • "x"
      : Base64URL-encoded x-coordinate without trailing equal sign padding (
      =
      ).
    • "y"
      : Base64URL-encoded y-coordinate without trailing equal sign padding (
      =
      ).
  • NEVER expose private key parameters (
    "d"
    ) or superfluous metadata.
  • 导出公钥并附加到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 (
    crypto.subtle.sign
    ), ECDSA signatures are ALREADY emitted natively in raw IEEE P1363 format (concatenated 32-byte
    r
    and
    s
    buffers, 64 bytes total). DO NOT attempt DER-to-Raw conversion on
    crypto.subtle.sign
    outputs, as parsing a 64-byte raw buffer as ASN.1 DER causes an immediate runtime exception (
    Invalid DER sequence
    ). Directly base64url-encode the raw ArrayBuffer.
  • Legacy API Fallback: If and only if implementing in legacy Java/Android (
    java.security.Signature
    ) or Node CommonJS (
    crypto.createSign
    ), convert ASN.1 DER output to raw 64-byte IEEE P1363 format before base64url encoding.
  • DPoP Proof JWT要求遵循IEEE P1363和RFC 7518的原始拼接坐标签名($R \parallel S$,P-256算法下恰好64字节)。
  • WebCrypto原生规则: 在标准WebCrypto(
    crypto.subtle.sign
    )中,ECDSA签名原生以原始IEEE P1363格式输出(拼接的32字节
    r
    s
    缓冲区,共64字节)。不要
    crypto.subtle.sign
    的输出进行DER转原始格式转换,将64字节原始缓冲区解析为ASN.1 DER会立即引发运行时异常(
    Invalid DER sequence
    )。直接对原始ArrayBuffer进行base64url编码即可。
  • 传统API兼容方案: 仅当在传统Java/Android(
    java.security.Signature
    )或Node CommonJS(
    crypto.createSign
    )环境中实现时,才需将ASN.1 DER输出转换为64字节原始IEEE P1363格式,再进行base64url编码。

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
    client_secret
    requirements on server endpoints and browser CORS limitations on the
    DPoP-Nonce
    response header.
  • 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
    access_type=offline
    , binds refresh tokens server-side using DPoP, and maintains secure session cookies with the frontend.
  • 无密钥SPA限制: 纯客户端单页应用(SPA)无后端支持时,无法直接结合Google API使用DPoP,原因是服务器端点要求
    client_secret
    ,且浏览器CORS限制
    DPoP-Nonce
    响应头的获取。
  • BFF模式: 要通过DPoP保护SPA,需将授权和令牌刷新请求路由到前端后端(BFF)服务器端客户端。BFF设置
    access_type=offline
    ,通过DPoP在服务器端绑定刷新令牌,并与前端维护安全会话Cookie。

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
)

A. DPoP Proof JWT声明推导规则(
createDPoPProof

When generating the DPoP Proof JWT in
createDPoPProof
:
1. JOSE Header (
typ
,
alg
,
jwk
):
javascript
// Header
{
  "typ": "dpop+jwt",
  "alg": "ES256",
  "jwk": await exportPublicJWK(publicKey)
}
2. Payload Claims:
  • "htm"
    : Uppercase HTTP Method (
    "POST"
    for token requests).
  • "htu"
    : Target URI stripped of query parameters and hash fragments using
    sanitizeHTU(htu)
    . For token requests, this is
    https://oauth2.googleapis.com/token
    .
  • "iat"
    : Current integer epoch timestamp in seconds (
    Math.floor(Date.now() / 1000)
    ).
  • "jti"
    (Critical Invariant):
    1. If an explicit
      jti
      argument is provided to
      createDPoPProof
      , use that exact string over all others.
    2. Otherwise, if an
      authCode
      argument is provided (during initial code exchange), set
      jti = await calculateAuthCodeJti(authCode)
      where
      calculateAuthCodeJti
      computes
      base64url(sha256(authCode))
      to ensure the DPoP proof is cryptographically bound to the authorization code.
    3. Only if neither
      jti
      nor
      authCode
      is provided, generate a fresh cryptographic random string via
      generateRandomString()
      (such as
      crypto.getRandomValues(new Uint8Array(24))
      base64url encoded).
  • "ath"
    (Optional): If an
    accessToken
    argument is provided for RFC 9449 resource requests, compute
    base64url(sha256(accessToken))
    via
    calculateATH(accessToken)
    and inject it (RFC 9449 Section 6.1).
  • "nonce"
    (Optional): If a
    nonce
    argument is provided, inject it directly into the payload.
createDPoPProof
中生成DPoP Proof JWT时:
1. JOSE头部(
typ
,
alg
,
jwk
):
javascript
// Header
{
  "typ": "dpop+jwt",
  "alg": "ES256",
  "jwk": await exportPublicJWK(publicKey)
}
2. 载荷声明:
  • "htm"
    : 大写HTTP方法(令牌请求使用
    "POST"
    )。
  • "htu"
    : 使用
    sanitizeHTU(htu)
    去除查询参数和哈希片段后的目标URI。令牌请求的目标URI为
    https://oauth2.googleapis.com/token
  • "iat"
    : 当前整数时间戳(秒级,
    Math.floor(Date.now() / 1000)
    )。
  • "jti"
    (关键不变量):
    1. 如果
      createDPoPProof
      传入了明确的
      jti
      参数,则直接使用该字符串。
    2. 否则,如果传入了
      authCode
      参数(初始授权码交换阶段),设置
      jti = await calculateAuthCodeJti(authCode)
      ,其中
      calculateAuthCodeJti
      计算
      base64url(sha256(authCode))
      ,确保DPoP Proof与授权码加密绑定。
    3. 仅当既无
      jti
      也无
      authCode
      参数时,通过
      generateRandomString()
      生成全新的加密随机字符串(例如对
      crypto.getRandomValues(new Uint8Array(24))
      进行base64url编码)。
  • "ath"
    (可选):如果为RFC 9449资源请求传入了
    accessToken
    参数,通过
    calculateATH(accessToken)
    计算
    base64url(sha256(accessToken))
    并注入该声明(RFC 9449第6.1节)。
  • "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:
  1. Token Endpoint Requests (
    oauth2.googleapis.com/token
    ):
    • Attach the DPoP Proof JWT in the
      DPoP
      HTTP header:
      `DPoP: ${proofJwt}`
      when making
      POST
      requests for code exchange (
      grant_type=authorization_code
      ) and token refresh (
      grant_type=refresh_token
      ).
  2. Resource API Requests:
    • Google's token endpoint returns
      "token_type": "Bearer"
      . Downstream requests to Google APIs (e.g. Calendar, Drive, Gmail) use standard
      `Authorization: Bearer ${accessToken}`
      headers without DPoP headers.
  3. Single-Retry Nonce Challenge Loop & Workflow Isolation:
    • If Google's token endpoint returns HTTP
      400 Bad Request
      with
      error: "use_dpop_nonce"
      and a
      "DPoP-Nonce"
      response header:
      • Workflow Isolation: Google's authorization server enforces workflow isolation between authorization code exchange and token refresh, returning an HTTP
        400 use_dpop_nonce
        challenge to establish a fresh nonce namespace. This is standard RFC-compliant protocol behavior, not a server failure.
      • Cache the fresh nonce in client state (
        this.dpopNonce
        ).
      • Immediately synthesize a new DPoP Proof JWT incorporating the updated
        nonce
        claim and a fresh
        jti
        .
      • Replay the failed token request exactly once. If the retried request fails, terminate immediately with an error to prevent infinite recursion.
集成Google OAuth 2.0平台时:
  1. 令牌端点请求(
    oauth2.googleapis.com/token
    ):
    • POST
      请求(授权码交换
      grant_type=authorization_code
      和令牌刷新
      grant_type=refresh_token
      )中,将DPoP Proof JWT附加到
      DPoP
      HTTP头部:
      `DPoP: ${proofJwt}`
  2. 资源API请求:
    • Google令牌端点返回
      "token_type": "Bearer"
      。后续向Google API(如日历、云端硬盘、Gmail)的请求使用标准
      `Authorization: Bearer ${accessToken}`
      头部,无需DPoP头部。
  3. 单次重试Nonce挑战循环与流程隔离:
    • 如果Google令牌端点返回HTTP 400错误,且错误码为
      "use_dpop_nonce"
      并附带
      "DPoP-Nonce"
      响应头:
      • 流程隔离: Google授权服务器强制授权码交换与令牌刷新流程隔离,返回HTTP 400 use_dpop_nonce挑战以建立新的nonce命名空间。这是符合RFC标准的协议行为,并非服务器故障。
      • 将新nonce缓存到客户端状态(
        this.dpopNonce
        )。
      • 立即生成包含更新后的
        nonce
        声明和全新
        jti
        的DPoP Proof JWT。
      • 仅重试一次失败的令牌请求。如果重试仍失败,立即终止并抛出错误,防止无限递归。

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)

Developer Knowledge MCP Server

开发者知识MCP服务器

  • Agents equipped with Model Context Protocol (
    MCP
    ) can query real-time Google Developer documentation using the Google Developer Knowledge MCP Server (
    npx -y @google/mcp-developer-knowledge-server
    ) via
    developer_knowledge:search_documents
    and
    developer_knowledge:get_documents
    .
  • 配备Model Context Protocol(
    MCP
    )的代理可通过 Google Developer Knowledge MCP Server (
    npx -y @google/mcp-developer-knowledge-server
    ),调用
    developer_knowledge:search_documents
    developer_knowledge:get_documents
    查询实时Google开发者文档。

Standards & RFC Specifications

标准与RFC规范