Lion Cipher Lab

Authenticate OAuth clients with private_key_jwt from scratch (RFC 7523), then close the audience and replay gaps

Why it matters

Every backend service that talks to an OAuth server has to prove who it is, and most still do it by sending the same shared secret on every request. Whoever copies that secret once becomes the service for as long as nobody rotates it. private_key_jwt replaces the secret with a short, single-use, signed statement, and the server stores only a public key. It is the client authentication that the OAuth Security Best Current Practice recommends, and it is required by high-assurance profiles like FAPI. It is also easy to get subtly wrong: in 2025 the OpenID Foundation disclosed that loose audience…

What you'll learn

Steps in this lab

  1. The problem: a client secret is a password you send every time
  2. Set up Python and one package
  3. Write the shared module: keys, the JWT, and two verifiers
  4. Authenticate once, then try the same assertion again
  5. What the hardened verifier checks, and why in that order
  6. Run the tamper matrix against both verifiers
  7. The 2025 audience disclosure: what was actually found
  8. Reproduce the audience gap on localhost, then close it from both sides
  9. Measure it against client_secret_basic
  10. When to choose private_key_jwt, mTLS, or a secret
  11. Honest negatives: rotation, caching, clock skew and key theft
  12. How a pro does it for real

Open the full interactive lab →