OAUTH 2.0 · LAB
Watch the tenant check DPoP proofs at the token endpoint and UserInfo
See the tenant apply the lesson's proof checklist, require DPoP for one client, bind a code with dpop_jkt, and keep a confidential client's refresh token usable across key changes.
PlannedUses your lab tenant
The lesson
Builds on: Creating a DPoP proof.
New to the labs? Start with the lab toolkit and the shared cast and names every lab uses.
Planned. The core of this lab waits on platform features that are not built yet. The planned walkthrough shows exactly how it will run; Do today is a real exercise you can do now.
- G14 DPoP
- G3 Sample protected resource API; no RFC 9728 protected resource metadata
Request console
Requests in this lab can be sent from this page to your tenant: open one and choose Send. Fill in the values below first. They stay in this page's memory and are gone when you leave; secrets are never stored or sent anywhere except the request you send.
Setup
Keep
app-key.pem,app-key.jwk, theb64urlhelper and the shell variables forlab-printerandlab-printer-appfrom the earlier labs.Planned (G14): on
lab-printer, turn on DPoP-bound access tokens required (dpop_bound_access_tokens: true).Planned (G14): open OAuth > Clients > DPoP proof test, a tenant tool that runs a pasted proof through the checklist for a chosen method and address and shows which check passed or failed, so you can see each check without sending malformed requests.
Note: the lab toolkit has no command yet that signs a DPoP proof with your own key. The planned steps say exactly which proof each request needs; they need that command before they can run.
Planned walkthrough
Paste a correct UserInfo proof into the proof test with method
GETand address$ISSUER/oidc/userinfo. Every check passes: one header, required claims,typ, an asymmetricalg, a signature by the embedded key with no private members, matchinghtmandhtu, and freshness.
Why it matters: "Checking a proof". These are the lesson's eight checks, in the tenant's own words.
In the same tool, change the method to
POST: thehtmcheck fails. Change the address to another path, or tohttp://: thehtucheck fails.
Why it matters: a proof is valid for one method and one public address only.
Request a token for
lab-printerwithout aDPoPheader. Expect400 invalid_request.
Why it matters: "At the token endpoint". dpop_bound_access_tokens closes the unbound route for that client.
Start an authorization for
lab-printer-appwith&dpop_jkt=<thumbprint of app-key.jwk>, then redeem the code with a proof fromapp-key.pem. It succeeds.
Why it matters: the authorization code itself is bound to the key, so a code stolen on its way back is useless without it.
Refresh a
lab-printertoken (with its Basic credentials) using a proof from a new key,printer-dpop-2.pem. It succeeds, and the new access token'scnf.jktis the new key's thumbprint.
Why it matters: confidential clients authenticate on every refresh, so their refresh tokens are not bound to a DPoP key and a back end can change keys without losing its grants.
Switch
lab-printer-app's access token manager toopaqueand call UserInfo with DPoP again: still served, because the tenant reads the binding from storage, as introspection does. Switch it back tojwt.
Why it matters: "At the photo API". The binding check works for both token formats; for an opaque token, the resource gets cnf.jkt from introspection and compares it itself.
Do today
Read the exact public addresses a proof's
htumust match.
GET$ISSUER/.well-known/oauth-authorization-server
Open in console
GET $ISSUER/.well-known/oauth-authorization-serverNote token_endpoint and the userinfo_endpoint from the OpenID configuration. A server behind a load balancer must compare against these public addresses, not the internal one it sees.
Introspect a current
lab-printer-apptoken withbtl-lab introspect "$TOKEN"and list the fields a DPoP-aware resource would need that are missing:cnfwithjkt, and a token type of DPoP.Open
lab-printerin OAuth > Clients. There is no DPoP setting, so no client can require binding today.Take the two claim sets you assembled in Build DPoP proof contents and hashes by hand, for
POSTat the token endpoint andGETat UserInfo, and run each through the lesson's checklist by hand. For the UserInfo one, also write the two comparisons only a resource makes:athagainst the token in theAuthorizationheader, and the key's thumbprint againstcnf.jkt.Run the bearer-only behavior the lesson warns about, using the tenant as the resource: call UserInfo with
Authorization: Bearer $TOKENand no proof. It succeeds, as an older API that ignorescnfwould. That is why every resource that receives bound tokens needs the checks before the protection is real.
Break it
These run once G14 exists. Each is a client mistake the tenant must catch.
Redeem the step 4 code with a proof from a different key of yours. Expect
400 invalid_grant.Reuse a UserInfo proof made for one access token with a newer token from the same app. Expect
401withWWW-Authenticate: DPoP error="invalid_token", becauseathnames the old token.Send a bound token as
Authorization: Bearerwith no proof. Expect a refusal: a resource that knows about DPoP never accepts a bound token as a bearer token.
Check your work
Today, Audit shows oidc.userinfo succeeded userinfo_served for the bearer call. Once G14 exists, Audit shows oauth.token refusals for the missing proof and the dpop_jkt mismatch, an oidc.userinfo refusal for the ath mismatch, and tenant.oauth.clients.update for the client setting.
Cleanup
Once G14 exists, turn DPoP-bound access tokens required off on
lab-printer, and confirmlab-printer-app's token manager format isjwt.Revoke the tokens you obtained and delete
printer-dpop-2.pemif you made it.
Missing infrastructure
G14: proof checks at the token endpoint and UserInfo,
dpop_bound_access_tokens,dpop_jkt, binding for opaque tokens through storage and introspection, and a proof test tool in the tenant portal.G3: a sample protected resource, so the lab can compare a DPoP-aware resource with a deliberately bearer-only lab endpoint, clearly labelled and limited to the lab.
Once these exist and the toolkit can sign proofs, the Planned walkthrough runs as written.