SSOGEN / Free tools / DPoP Proof Checker: Test a DPoP Proof in Your Browser
Free tool
DPoP Proof Checker: Test a DPoP Proof in Your Browser
A DPoP proof is a small signed JWT the client sends in the DPoP request header with every call. This free checker runs the checks a resource server runs, in your browser, with nothing uploaded.
Check a proof here
Paste the proof, add the access token, method and URL if you have them, then select Check the proof.
Private by design. The checks run in this page and nothing is sent anywhere. A DPoP proof holds only a public key, but a real access token is a credential: do not paste a live production token on a shared screen.
Free, in your browser. Decoding, hashing and signature checks use your browser’s built-in cryptography. Nothing is stored or uploaded.
If the proof’s jwk contains a private key, the checker flags it and never uses it.
The steps in order:
- Paste the proof (the value of the
DPoPheader). - To check the key binding and the
athclaim, also paste the access token it was sent with. - Enter the HTTP method and URL of the request, and the server nonce if the server issued one.
- Select Check the proof.
What a DPoP proof is
DPoP proof. A DPoP proof is a JWT the client signs with its own private key and sends in the DPoP request header on every call, alongside its access token.
What it asserts. It asserts “this exact request, to this exact URL, right now, from the holder of this key”.
The checker. The checker above decodes a proof and runs the checks a resource server runs. It is free, needs no sign-up and makes no network request.
Try it: a good proof, then a stolen token
Each sample button generates fresh keys in your browser, so every run shows new values.
Load a matching token and proof. The access token is bound to a key, and the proof is signed with that same key.
Select Check the proof and every line passes:
typisdpop+jwtand the algorithm is asymmetric.- The signature verifies with the public key in the header.
htmandhtumatch the request fields,iatis recent andjtiis present.athequals the SHA-256 hash of the token.- The token’s
cnf.jktequals the thumbprint of the proof’s key.
Load a stolen-token replay. The access token is the same, but the proof is signed by a different key.
That is the only option a thief has: they hold the token, not the owner’s private key. Note which lines fail and which pass.
The signature check still passes, because the proof is genuinely signed by the key in its own header; that proves only that the sender holds some key.
The key-binding check fails: the token is bound to one thumbprint, and the proof was signed with another.
A server that verifies the signature but skips this comparison accepts the thief. The comparison is what gives DPoP its value.
What the proof claims mean
RFC 9449, section 4.2 defines the proof’s header and claims (read 2026-10-08), and each one closes a specific gap. For the full background, read the DPoP explainer.
| Field | What it holds | Attack it stops |
|---|---|---|
typ |
dpop+jwt in the header |
An ordinary JWT, such as an ID token, being passed off as a proof |
alg |
An asymmetric algorithm, never none or a shared-secret one |
A forged proof signed with none or with a secret the attacker can guess |
jwk |
The client’s public key, with no private part | Gives the server the key to verify with, and the thumbprint to compare with the token |
htm |
The HTTP method | A proof captured on a GET being used for a DELETE |
htu |
The request URL, without query and fragment | A proof captured for one endpoint being used at another |
iat |
When the proof was made | An old captured proof being used long after |
jti |
A unique id, at least 96 bits of randomness per the RFC | The same proof being sent twice, if the server remembers recent ids |
ath |
Base64url SHA-256 of the access token | A proof made for one token being attached to a different token |
nonce |
A value the server issued | Proofs signed in advance with future iat values (section 11.2) |
The checks in the order a server runs them
Section 4.3 lists what a server must verify, and section 7 adds the access-token checks. The tool runs the same sequence:
- One
DPoPheader, and it is a well-formed JWT with the required claims present. typisdpop+jwt.algis asymmetric and one your server accepts.jwkis a public key with no private material. A private key in a proof means that key is exposed and must be retired.- The signature verifies with the key in
jwk. htmequals the request method.htuequals the request URL, ignoring query and fragment.iatis inside your acceptance window.jtiis new within that window.noncematches the value you issued, if you issue them.athequals the hash of the access token in theAuthorizationheader.- The token’s binding matches the proof’s key. In a JWT access token that is the
cnf.jktclaim; for an opaque token, the introspection response carries it.
The order keeps cost down. Cheap string comparisons can reject a request before the signature is verified, and the replay lookup goes last because it touches storage.
The RFC does not rank the checks, so this order is our recommendation.
htu and htm mistakes
Most real failures are not attacks: the client built a proof for one URL and the server sees another.
The checker parses the URL and compares scheme, host and path. It ignores host capitalization and a default port, and it flags a trailing slash.
| Symptom | Likely cause | Fix |
|---|---|---|
htu does not match, and it looks the same |
A reverse proxy or load balancer rewrote the host or scheme, so the app sees http://internal-host/... |
Validate against the public URL the client called. Build it from configuration, or from trusted forwarded headers, never from the raw connection |
| Mismatch only on some endpoints | The proxy strips or adds a path prefix | Compare against the external path, prefix included |
/invoices versus /invoices/ |
A trailing slash is a different path | Make the client sign exactly the URL it requests, and redirect or normalize on the server consistently |
Proof includes ?page=2 |
The client put the query in htu |
Sign the URL without query and fragment, as section 4.2 requires |
| Port in one place, not the other | :443 on an https URL is the default and the same address |
Normalize before comparing; RFC 3986 scheme-based normalization covers this (section 4.3 recommends it) |
htm mismatch after a redirect |
The client re-sent a POST as a GET, or the reverse |
Create a new proof for the redirected request; each request needs its own |
Clock and nonce problems
iat. RFC 9449 leaves the acceptance window to the server. Section 11.1 advises allowing a little time in the future for clock skew and keeping the total lifetime short.
This tool’s window. This tool accepts a proof within five minutes either side of your computer’s clock, a common choice rather than an RFC rule.
Clock check. If the checker reports a proof as in the future or very old, check the clock on the machine that created it first.
Nonces. A server can require a nonce so that proofs cannot be signed in advance. The two servers signal it differently:
- The authorization server responds with HTTP 400, the error
use_dpop_nonceand aDPoP-Nonceheader (section 8). - A resource server responds with a 401, a
WWW-Authenticate: DPoPchallenge and the same header (section 9).
The client repeats the request with that value in the proof’s nonce claim.
To test this, paste the issued value into the checker’s nonce box. A nonce error on the first call is expected, not a failure; retry once with the new value.
Once a server issues a nonce, it must not accept proofs without one (section 11.3).
What a checker cannot do
- It cannot remember
jtivalues. To detect a repeated proof, the server must store recent ids for the length of its window (section 11.1). The checker confirms that ajtiexists, not that it is unused. - It cannot see your server’s policy. Your algorithms, window and nonce rules are yours to set.
- It does not validate the access token’s own signature. It reads the token’s
cnf.jktto test the binding. To verify the token itself, use the JWT decoder with the issuer’s keys from the JWKS inspector. - It cannot read an opaque token. The binding of an opaque token is in your authorization server’s introspection response, so the checker reports that it could not read it.
- It does not prove a token is safe to trust. It tests a proof against a request you describe; a real server must do the same on the real request.
What SSOGEN does with DPoP-bound agent credentials
SSOGEN requires AI agents to prove possession of a key on every request:
- The agent sends
Authorization: DPoP <access token>plus a fresh proof signed with the key the token is bound to. ABearertoken is refused. - The access token lives about four minutes, never more than 270 seconds.
- An agent acting on its own receives no refresh token.
More on how SSOGEN handles it:
- Nonce. SSOGEN can answer a token request with
use_dpop_nonceand aDPoP-Nonceheader, and the agent repeats the request with the nonce in its proof. - Acting for a person. A client acting for a person also sends its DPoP key’s thumbprint as
dpop_jktin its pushed authorization request. - Discovery. The realm’s discovery document lists the accepted proof algorithms in
dpop_signing_alg_values_supported. - MCP route. On a protected MCP route, the MCP Gateway answers an unauthenticated request with
WWW-Authenticate: DPoPand resource metadata. - Every tool call. It then checks the credential and the key proof on every tool call, and it does not forward the agent’s token to your MCP server.
- Setup. The exact requests are in the setup guide for MCP authorization.
- Tool choice. To choose which tools an agent should hold, use the AI agent permission builder.
What is a DPoP proof?
What does the DPoP header look like?
DPoP, whose value is a compact JWT of three base64url parts. The access token travels separately in Authorization: DPoP <token>. A bound token uses the DPoP scheme, not Bearer.Is a DPoP token different from a bearer token?
cnf.jkt thumbprint. A copy is useless without the matching private key, because every request needs a proof signed with it.Why does my proof fail with invalid_dpop_proof?
htu that differs from the public URL, a clock off by more than your window, a missing ath, or a proof that was reused or built for a different token.Can I paste a production token?
Where to go next
- The mechanism. Read DPoP OAuth explained.
- Acting for a person. See OAuth token exchange.
- MCP requirements. See MCP authentication for what the MCP specification requires.
- More tools. More browser tools are on the free tools page.
- The standard. The authoritative text is RFC 9449.
Sources, all read 2026-10-09: RFC 9449, OAuth 2.0 Demonstrating Proof of Possession.