awesome-everything RU
↑ Back to the climb

Security

OAuth/OIDC: build and audit a hardened login

Crux Hands-on project — build a hardened OIDC login plus a sender-constrained API, then audit it against the unit's complete-set checklist and prove each mandatory check with an adversarial test.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at senior altitude — in orbit
◷ 240 min

Reading about OAuth CVEs is not the same as proving your own implementation has none. Build a small but complete OIDC login against a real authorization server, sender-constrain the API, then attack each mandatory check yourself — only an adversarial test that fails on the broken path proves the check is actually there.

Goal

Turn the unit’s complete-set model into a working, audited system: authorization code with PKCE, full id_token validation with JWKS rotation handling, refresh rotation with replay detection, DPoP-bound API calls, and an observability dashboard — each defended by a test that proves the check holds.

Project
0 of 9
Objective

Build a hardened OIDC login and a sender-constrained protected API against a real authorization server (Auth0, Okta, Keycloak, or Authentik), then run a security audit that proves every mandatory check with an adversarial test that the unhardened path would fail.

Requirements
Acceptance criteria
  • An adversarial test suite where each test exercises one broken path and is rejected: a replayed authorization code (PKCE), a mismatched state, a tampered or alg=none id_token, an id_token with the wrong aud, a reused refresh token (replay detection fires), and a DPoP call with a missing or wrong proof.
  • A demonstrated JWKS key-rotation scenario: rotate the IdP signing key (or simulate a new kid) and show login keeps working because on-cache-miss refresh fetches the new key instead of 401-ing.
  • A token-storage check: the access token is provably absent from localStorage and the refresh token is in an httpOnly cookie inaccessible to JS (or the platform equivalent).
  • The dashboard shows refresh_replay_detected_total incrementing exactly once when the reused-refresh-token test runs, and id_token_validation_failure_total incrementing by reason for each rejected id_token test.
  • A one-page audit write-up mapping each of the unit's mandatory checks (PKCE, exact redirect, eight id_token checks, refresh rotation, sender-constraint, audience validation) to the test that proves it.
Senior stretch
  • Add PAR (Pushed Authorization Requests) so authorize parameters never appear in the browser URL, and show they are absent from history and referrer headers.
  • Add token introspection on a high-sensitivity write endpoint with a 30s introspection cache, and demonstrate that a token revoked at the IdP is rejected within the cache window while local JWT validation still serves the read path.
  • Add RFC 8707 Resource Indicators so a token minted for API-A is rejected by API-B, and include the regression test that an aud set to other-api is refused.
  • Run the same login against a second authorization server (e.g. swap Keycloak for Auth0) and document which checks needed configuration changes and which were portable.
Recap

This is the loop you run on every real OAuth integration: build the flow with PKCE, validate the id_token completely with a pinned algorithm and on-cache-miss JWKS refresh, rotate refresh tokens with replay detection, sender-constrain the API with DPoP, store tokens by client threat model, and wire the observability that surfaces compromise. Then audit it adversarially — a check you have not attacked is a check you have not proven. Doing this once on a small system turns the complete-set discipline into muscle memory for production.

Continue the climb ↑JWT security pitfalls: alg confusion, no revocation, and where tokens leak
shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.