Beta

Create a tenant

A new tenant starts with its own users, OAuth settings, audit history and logs. You are its first Tenant Admin.

BTL Admin

OAUTH 2.0 · LAB

Build DPoP proof contents and hashes by hand

Reproduce the lesson's thumbprint and ath values exactly, assemble the token endpoint and UserInfo proof headers and claims for lab-printer-app, and catch the small mistakes that never match.

PlannedUses your lab tenant

The lesson

Builds on: Binding a token to a key.

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.

Setup

  1. Keep app-key.pem, app-key.jwk, the b64url helper and APP_ID from Bind the phone app's tokens to a key it generated.

  2. Have a current access token for lab-printer-app in TOKEN. Run that lab's Do today steps 2 and 3 if you need a new one.

Note: the lab toolkit has no command yet that signs a DPoP proof with your own key. Everything up to the signature runs today; sending signed proofs needs that command and G14.

Planned walkthrough

  1. Build and sign the token endpoint proof described in Do today step 3, then redeem a code for lab-printer-app with it in the DPoP header.

curl -s "$ISSUER/oauth/token" -H "DPoP: $PROOF" -d grant_type=authorization_code -d code=<code> \
  --data-urlencode redirect_uri=$REDIRECT -d code_verifier=$VERIFIER -d client_id=$APP_ID | jq '{token_type}'

Expect "token_type": "DPoP".

Why it matters: "The proof at the token endpoint". The proof carries no secret, code or body content; it names only the method, the address, the moment and a unique identifier.

  1. Build and sign the UserInfo proof from Do today step 4 for the new token, then call UserInfo with Authorization: DPoP $TOKEN and the proof in the DPoP header. Ava's claims come back.

Why it matters: "The proof at the photo API". The proof names this token through ath, so it is useless with any other token.

  1. Send the same proof again with a newer token from the same app. Expect 401 with WWW-Authenticate: DPoP error="invalid_token", because ath names the old token.

Why it matters: the token and the proof are each useless without the other.

Do today

  1. Reproduce the lesson's thumbprint from its public key, by hand and with the toolkit.

printf %s '{"crv":"P-256","kty":"EC","x":"DboN9G_lcWgZ3rxckKu35JwH2OYBLWJgxcT_-KEXK8Y","y":"rj69LkdPmdnAbPoBq6mpcpbJPWLEQ6Qs2SaMMyKWBSk"}' \
  | openssl dgst -sha256 -binary | b64url; echo
printf %s '{"kty":"EC","crv":"P-256","x":"DboN9G_lcWgZ3rxckKu35JwH2OYBLWJgxcT_-KEXK8Y","y":"rj69LkdPmdnAbPoBq6mpcpbJPWLEQ6Qs2SaMMyKWBSk"}' > lesson-key.jwk
btl-lab thumbprint lesson-key.jwk

Both print BVohHgHi0D9O5iBFRpVSFAukBjzgVbIRUjYuTsfJER8. The hand-made input is the canonical form: required members only, in alphabetical order, no whitespace.

  1. Reproduce the lesson's ath.

printf %s demo-app-access-token-1 | openssl dgst -sha256 -binary | b64url; echo

The result is xcbjgyKf_F3-foHYAOd_VdSpNEB0ThBmPo1xXGoYihA. Hash the exact token string, nothing around it.

  1. Assemble the token endpoint proof's header and claims for your own key.

HEADER=$(jq -nc --slurpfile k app-key.jwk '{typ:"dpop+jwt", alg:"ES256", jwk:$k[0]}')
CLAIMS=$(jq -nc --arg u "$ISSUER/oauth/token" --arg j "$(openssl rand 16 | b64url)" --argjson t $(date +%s) '{jti:$j, htm:"POST", htu:$u, iat:$t}')
echo "$HEADER" | jq .; echo "$CLAIMS" | jq .
echo "$(printf %s "$HEADER" | b64url).$(printf %s "$CLAIMS" | b64url)"     # the signing input

Check the header against the lesson: typ is dpop+jwt, alg is asymmetric, and jwk holds only kty, crv, x and y. The last line is what the private key signs; the signature is then appended after another dot.

  1. Assemble the UserInfo proof for your real token, and compute its ath.

ATH=$(printf %s "$TOKEN" | openssl dgst -sha256 -binary | b64url)
jq -nc --arg u "$ISSUER/oidc/userinfo" --arg j "$(openssl rand 16 | b64url)" --argjson t $(date +%s) --arg a "$ATH" \
  '{jti:$j, htm:"GET", htu:$u, iat:$t, ath:$a}' | jq .

Every claim differs from step 3, and ath is new. If the request were for $ISSUER/oidc/userinfo?x=1, htu would still be $ISSUER/oidc/userinfo: the query string and fragment are left out.

  1. Build two token endpoint claim sets a second apart and compare them. jti and iat differ: each request a correct client sends gets its own proof.

Break it

These are the lesson's common mistakes, checked offline today. Once G14 exists, the tenant rejects each one.

  1. Add kid to the thumbprint input and hash it by hand.

printf %s '{"crv":"P-256","kid":"app-1","kty":"EC","x":"DboN9G_lcWgZ3rxckKu35JwH2OYBLWJgxcT_-KEXK8Y","y":"rj69LkdPmdnAbPoBq6mpcpbJPWLEQ6Qs2SaMMyKWBSk"}' \
  | openssl dgst -sha256 -binary | b64url; echo

The value changes and would never match cnf.jkt. Reordering the members or adding spaces does the same.

  1. Hash the whole header value instead of the token: printf %s "DPoP $TOKEN" | openssl dgst -sha256 -binary | b64url. The result differs from ATH, so a resource would answer invalid_token.

  2. Encode with ordinary Base64 (openssl dgst -sha256 -binary | base64). The +, / and = characters give a value that looks plausible and never matches.

Check your work

Your step 1 and step 2 outputs equal the lesson's values exactly, and the jq output in steps 3 and 4 shows exactly the claims the lesson lists. Once G14 exists, Audit shows oauth.token succeeded with token type DPoP and oidc.userinfo userinfo_served for lab-printer-app.

Cleanup

Delete lesson-key.jwk. Keep app-key.pem and app-key.jwk for the next DPoP labs.

Missing infrastructure

  • G14: the tenant must accept and check proofs before sending them has any effect. Today the token endpoint ignores a DPoP header and UserInfo refuses the DPoP scheme. Once G14 exists and the toolkit can sign a proof with your own key, the Planned walkthrough runs as written.

Back to all labs

We value your privacy

We use cookies and similar technologies to enhance your browsing experience, and analytics to understand our traffic. By clicking "Allow All", you consent to optional analytics. Cookie Policy

The Lab