OAuth solved authorization for a federated web. It does not solve identity for systems where every device, every session, and every request needs to be verifiable. Here's what we built instead.
OAuth is the most successful security protocol of the last two decades. It works because it solves a narrow, real problem — letting one service grant another limited access to a user's data without sharing credentials. Everywhere a Sign in with Google button exists, OAuth is doing its job.
But OAuth doesn't solve identity. It solves delegation. The token says one thing: "Google believes this user gave you permission." Once that token leaves Google's domain, no one — not the issuing server, not the resource server, not the client — can verify anything beyond the token's surface validity. The token is the identity. Whoever has it, is the user.
In a federated web circa 2010, this was fine. Browsers were the only client. Tokens lived briefly, traveled over HTTPS, and the attack surface was narrow. In 2026, the picture is different:
Every additional layer is an opportunity for the token to escape. And every escape is total — the holder of the token becomes indistinguishable from the user. This is the design constraint OAuth never tried to solve.
To verify identity continuously — not just at the moment of login — a system needs three things OAuth doesn't provide:
Enravo Core layers all three on top of OAuth-style authorization. The OAuth flow still happens — that's how we exchange credentials for tokens. What changes is what happens to the token afterward.
When a device first authenticates, it generates a P-256 key pair using the platform's secure enclave (Android Keystore, iOS Secure Enclave, Windows TPM). The public key is registered with our identity service. The token issued in response includes a binding claim — a fingerprint of that public key. From that moment, the token is meaningless without the corresponding private key.
{
"sub": "user-9f3a",
"iss": "https://identity.enravo.com",
"aud": "rakton-pos",
"exp": 1735689600,
"cnf": {
"jkt": "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs"
}
}That cnf.jkt claim is the JWK thumbprint of the device's public key. RFC 7800 calls this a confirmation method — it says "this token only proves the bearer's right to act if they can also prove possession of the key whose thumbprint is X."
Sending the token is no longer enough. Every mutating request must include a signature, computed over the request method, path, body hash, timestamp, and a nonce, signed with the device's private key. The server verifies this signature against the public key on file. No valid signature, no request.
This means a token leaked into a log file is useless to an attacker. They have the token but not the key. They can't sign requests. They can read the token, copy it, broadcast it — and still be unable to do anything with it. This is the difference between proof of possession and bearer semantics.
A device proving possession of a key only proves that the key holder is consistent. It doesn't prove the key is being used by the legitimate app. An attacker who roots a phone, extracts the private key, and runs their own client can still produce valid signatures. So we add a third layer: app attestation.
Google Play Integrity (Android) and App Attest (iOS) both let the operating system vouch that requests are coming from a genuine, unmodified instance of the app. Our identity service requires an attestation token alongside the PoP signature on sensitive operations. The combination makes the trust chain runs end to end: device → key → app → request.
Concretely: leaked tokens stop being incidents. Lost devices stop being breaches. Compromised endpoints (a browser extension that exfiltrates auth headers, an APM tool that captures request bodies) lose their leverage because the token alone isn't enough to act.
All of this layers on top of OAuth. We didn't replace it; we extended it. OAuth still handles the initial credential exchange, the authorization grant, the refresh flow. Federated identity providers (Google, Microsoft, Apple) plug into our identity service through standard OIDC. The difference is in the lifetime of the token, the constraints on its use, and what it actually proves at every request.
OAuth, by itself, says "this user agreed to delegate access." That's a contract, not an identity. Identity is the continuous proof that the same entity is still making the requests. OAuth was never built for that — and that's why every serious authentication system in 2026 is built on top of it, not from it.
Zero trust isn't a product. It's an admission that no single signal — no token, no certificate, no IP address — is enough to verify intent. Every request has to carry its own proof. OAuth gave us the standard for delegating consent. PoP, device binding, and attestation give us the standard for trusting the result. The combination is what identity should have been all along.
Recent articles on platform, security, and engineering.
Component libraries scale poorly when every product has different domain models. We rebuilt Enravo's UI layer around schema definitions — and shipped admin panels in days instead of weeks.
Read moreWe turned on auto-ban in production six months ago. Here's what the data shows about real-world attacks, false positives, and the thresholds that actually work.
Read moreBearer tokens are a liability. How ECDSA-based proof of possession makes stolen tokens worthless and why every API should require it.
Read more