A companion project for the article DPoP: What It Is, How It Works, and Why Bearer Tokens Aren't Enough. It demonstrates how to secure a Quarkus REST API with DPoP (Demonstration of Proof-of-Possession) as defined in RFC 9449.
- DPoP-bound token validation via Quarkus OIDC (
quarkus.oidc.token.authorization-scheme=dpop) jtireplay protection with a custom@ServerRequestFilter- A k6 test script that exercises happy-path, replay attack, method mismatch, and URL mismatch scenarios
- Java 21+
- Maven 3.9+
- Docker and Docker Compose
- k6 (for running tests)
The included compose.yml starts PostgreSQL and Keycloak 26.5.5 with a pre-configured realm (dpop-demo client and hakdogan test user).
docker compose up -dWait until Keycloak is healthy:
docker compose psKeycloak will be available at http://localhost:8080. Admin credentials: admin / admin.
./mvnw quarkus:devThe application starts on port 8180.
k6 run k6/dpop-test.jsThe script runs 6 scenarios against the Quarkus API and prints the status and response body for each:
| # | Scenario | Expected |
|---|---|---|
| 1 | GET /user-info (Happy Path) | 200 |
| 2 | POST /user-info | 200 |
| 3 | POST /list-users | 200 |
| 4 | Replay Attack (jti reuse) | 401 |
| 5 | Method Mismatch (htm) | 401 |
| 6 | URL Mismatch (htu) | 401 |
All configuration values (Keycloak URL, client ID, credentials) can be overridden via environment variables:
k6 run -e KEYCLOAK_URL=http://keycloak:8080 -e CLIENT_ID=my-client -e USERNAME=user1 -e PASSWORD=secret k6/dpop-test.js├── compose.yml # Keycloak + PostgreSQL
├── keycloak/
│ └── master-realm.json # Pre-configured realm export
├── k6/
│ └── dpop-test.js # k6 test script
└── src/main/java/org/jugistanbul/
├── resource/
│ └── ProtectedResource.java # REST endpoints (GET/POST /user-info, POST /list-users)
└── filter/
└── DpopJtiFilter.java # jti replay protection filter