Implement and debug OAuth 2.0 DPoP (RFC 9449) refresh token sender-constraining for WebCrypto, Node.js ES6, and browser runtimes integrating with Google's OAuth
复制下面这句话,粘贴给 Claude Code、Codex、Cursor 等 AI 编程工具,它会读取安装说明并在你确认后完成安装。
请阅读 https://ai.atlankj.com/install/asset/gh-dpop-adoption-e57c8df15d3f ,按照其中的说明把「dpop-adoption」安装到你(当前 AI 工具)中。执行前先告诉我将运行的命令和写入的位置,等我确认。
查看 AI 将读取的安装说明正在读取 GitHub 原文…
内容来自 GitHub 原始文件,由原作者维护。在 GitHub 查看
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").
When implementing DPoP helpers or upgrading HTTP clients, you MUST adhere to the following strict security invariants:
"type": "module" for Node 18+ and browsers),
ALWAYS access globalThis.crypto directly after verifying the environment
context.require('node:crypto') or
reference browser-scoped window.crypto, as these cause module
initialization crashes across hybrid runtimes.P-256) curve: { name: 'ECDSA', namedCurve: 'P-256' }.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.extractable: true) to allow
emitting JSON Web Keys (JWKs)."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 (=)."d") or superfluous metadata.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.java.security.Signature) or Node CommonJS (crypto.createSign), convert
ASN.1 DER output to raw 64-byte IEEE P1363 format before base64url encoding.client_secret requirements on server endpoints and browser CORS
limitations on the DPoP-Nonce response header.access_type=offline, binds refresh tokens server-side using
DPoP, and maintains secure session cookies with the frontend.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:
createDPoPProof)When generating the DPoP Proof JWT in createDPoPProof:
1. JOSE Header (typ, alg, jwk):
// 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):
jti argument is provided to createDPoPProof, use that
exact string over all others.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.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.// 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' }
When integrating with Google's OAuth 2.0 platform:
oauth2.googleapis.com/token):
DPoP HTTP header:
`DPoP: ${proofJwt}` when making POST requests for code exchange
(grant_type=authorization_code) and token refresh
(grant_type=refresh_token)."token_type": "Bearer". Downstream
requests to Google APIs (e.g. Calendar, Drive, Gmail) use standard
`Authorization: Bearer ${accessToken}` headers without DPoP
headers.400 Bad Request with
error: "use_dpop_nonce" and a "DPoP-Nonce" response header:
400 use_dpop_nonce challenge to
establish a fresh nonce namespace. This is standard RFC-compliant
protocol behavior, not a server failure.this.dpopNonce).nonce claim and a fresh jti.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.
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.