
GNAP Explained — Why It Replaces OAuth 2.0 and JWT
The simple way to production auth in Rust
I work on Kavach, a Rust harness that enforces checks at every lifecycle event for AI coding agents. In one run, a bearer token was replayed and a gate was bypassed.
That incident is the hero of this story, not me. If you tend to debug auth at night, you might recognize the pattern. This post shares what helped me move forward, what remains limited, and where to try it yourself.
The problem that kept appearing
In many OAuth 2.0 setups, bearer tokens can be replayed if exposed. Front-channel redirects can add complexity for CLIs and agents, and teams often juggle several grant flows for one job. In our case, that showed up as a pin in an incident timeline, not as a theory.
- Front-channel complexity where redirects add friction
- Replay risk when a token is exposed and no proof of possession is required
- Several grant variants to maintain for similar cases
What tended to help: a key-bound grant
Instead of a single long-lived bearer, the client negotiates once and receives a token bound to its Ed25519 public key. On each request, the client signs the HTTP message (RFC 9421). The resource side can compare the signature locally.
In our 10K RPS local test, this check often fell in the tens of microseconds per request on our hardware, which can mean fewer DB hops on the verify path compared with an introspection call that in our setup often landed near 45ms at p99. Results vary with hardware and load, so treat this as a measured example, not a promise.
- One negotiation endpoint instead of a grant zoo
- Token is generally much less useful without the private key in our test setup
- Back-channel flow that can suit CLIs and agents
In our run, shifting the check to local signature comparison made it easier to keep the gate check fast enough to run on every event.
A Rust detail that mattered in our measurement
A fixed [u8; 32] stack array, SIMD-accelerated Ed25519, and compile-time state machines helped keep the verify path modest in our benchmark. The same zero-copy Serde pattern appears in my FinSolve RAG-RBAC project, which you can inspect for yourself.
A simple plan to try
If you want to explore the same shape:
- Negotiate once — client offers its public key
- Bind — server returns a token tied to that key
- Verify locally — check the HTTP message signature before touching a DB
See it running
The video walks through the same harness at a measured pace. Watching there tends to help more than following a link, so it's placed inline.
What success can look like — and what failure can still look like
If the plan fits your stack, you may see fewer replay-related incidents and a calmer on-call path for that class of auth check. If you don't adopt it, the existing trade-offs — token exposure, redirect friction, grant maintenance — tend to remain.
I share this as a guide who hit the same issue, not as a hero with a universal fix. Kavach and FinSolve are where I tried the pattern first — you can read the code and judge the limits yourself.
Try a small slice
// direct D1 read path with idempotent view handling
// invite check: compare measured 50µs-range in our run vs your hardware
if !auth::verify_admin(&req, &ctx.env).await? {
return Response::error("Forbidden", 403);
}
Explore: Kavach on GitHub · Discuss the limits: what workload does your verify path face today?