Inspiration
Token Vault stops at the boundary of a single agent. The moment an orchestrator spawns a sub-agent, the clean model breaks. I found that in every multi-agent system in production today, teams fall into one of three patterns:
- The raw provider token: full access, no expiry control, blast radius = everything the user ever consented to
- A hardcoded service account: not user-scoped at all, defeats the purpose of Token Vault
- Nothing: the sub-agent re-authenticates independently, breaking the user context chain entirely
This is the most commonly overlooked security gap in multi-agent systems. Not a dramatic exploit, just credential propagation by convenience. One prompt injection or one compromised dependency and the full provider token is in play.
RFC 8693 already defines the primitives for solving this, and Token Vault is built on it. But the delegation surface, issuing narrowed and time-boxed credentials to sub-agents, hasn't been exposed in the product yet. GrantChain is the bridge.
What it does
GrantChain adds a delegation layer on top of Auth0 Token Vault. Users create scoped, time-boxed grants for their AI agents from a Permission Dashboard. Each grant encodes which operations the agent is allowed to perform on a connected service, and for how long. Agents connect via a lightweight MCP server (npx @jordanchoi/grantchain-mcp) with no database access and no encryption keys on the user's machine. The demo uses GitHub, but the delegation model applies to any OAuth-connected service Token Vault supports.
Two core security primitives are enforced:
- Delegation chains: each level gets less, never more.
A child grant's scopes must be a strict subset of the parent's. Its TTL cannot exceed the parent's remaining time. Revoking a parent immediately invalidates all descendants.
Token Vault (Auth0) └─ Claude Code (parent grant) scopes: repo:read, issues:read TTL: 8 hours │ └─ Codex (child grant) scopes: repo:read only TTL: 1 hour - Expiring grants: access disappears automatically. Every delegation is time-boxed. When the clock runs out, the Delegation Service rejects further token requests. No manual cleanup, no revocation call required.
The architecture, with an honest split between what Auth0 provides and what GrantChain adds:
| Component | Responsibility | Built by |
|---|---|---|
| Token Vault | Stores user's OAuth tokens for connected services. Issues scoped access tokens on demand. | Auth0 |
| Connected Accounts | User connects external services (e.g. GitHub) via Auth0. OAuth consent and token storage fully managed. | Auth0 |
| Refresh Token Exchange | Exchanges Auth0 refresh token for a scoped provider access token. | Auth0 |
| Delegation Service | Issues signed JWTs. Enforces scope subset and TTL cascade. Proxies Token Vault for agents. | GrantChain |
| Grant Store | SQLite tracking active grants, JTI replay prevention, and encrypted refresh tokens. | GrantChain |
| Permission Dashboard | Create and revoke grants. Config snippet for Claude Code and Codex. | GrantChain |
| MCP Server | npm package connecting Claude Code and Codex to GrantChain via MCP. | GrantChain |
How I built it
- Next.js 16 App Router: API routes for grant management, delegation, and the token endpoint; server components for the dashboard
- Auth0 (
@auth0/nextjs-auth0v4): session management, Connected Accounts OAuth flow, Refresh Token Exchange to Token Vault - Delegation Service: HS256 JWTs signed with
jose, single-use JTI enforcement via SQLite atomicINSERT, AES-256-GCM encryption for refresh tokens at rest - Grant Store:
better-sqlite3with WAL mode; three tables:grants,consumed_jtis,refresh_tokens - MCP Server:
@modelcontextprotocol/sdkstdio transport, bundled withesbuildinto a single-file npm package (@jordanchoi/grantchain-mcp) - Deployed on Railway with a persistent SQLite volume
Challenges I ran into
Workaround or contribution?
The most important challenge wasn't technical, it was conceptual. RFC 8693 already specifies the actor delegation primitives that Auth0 hasn't yet exposed. I had to be honest with myself about whether GrantChain was filling a genuine gap or papering over a missing feature.
I concluded it's a genuine contribution. Even if Auth0 ships native actor delegation tomorrow, someone still needs to design how an orchestrator uses it: how sub-agents are identified, how TTLs cascade, how scope narrowing is enforced, and how any of this is surfaced to a user. GrantChain is that design. The pattern is the contribution, not the plumbing.
Single-use JWTs with MCP
The Delegation Service enforces single-use JTI consumption: each delegation JWT can only be presented once. MCP tool calls are stateless and per-request, so each call needs a fresh JWT. This required rethinking the token endpoint. Instead of issuing a JWT upfront, POST /api/token validates the grant and calls Token Vault directly on each request, bypassing the JWT issuance step for the local MCP flow.
Making security legible
The permission dashboard had to make an abstract security property tangible to a non-security user. "You have repo:read scope" means nothing. "This agent has read access to your repos, expires in 1 hour" means something. Every UI decision is in service of that translation.
Accomplishments that I'm proud of
- A working end-to-end delegation chain: user grants access, agent calls an external API, Token Vault exchange happens server-side, and the AI model never sees the OAuth token
- Grant chaining with enforced scope narrowing and TTL cascade, demonstrable live with Claude Code and Codex holding different grants against the same connected service
- A published npm package (
@jordanchoi/grantchain-mcp) with three env vars and zero secrets on the user's machine
What I learned
The multi-agent credential problem is already here. It's not a future risk. It's a convenience decision that every team building with agents makes right now, usually without thinking about it. Token Vault gives the community the secure credential root. GrantChain shows what the delegation architecture above it looks like.
RFC 8693 exists and is the right answer. Auth0 Token Vault is built on it. The delegation surface is a product gap, not a protocol gap. Building GrantChain made that gap concrete.
What's next for GrantChain
- Privileged Worker Exchange: once Auth0's Beta access is available, migrate away from storing refresh tokens so the backend only needs
user_idto fetch tokens - DPoP-style key binding: bind delegation JWTs to a keypair at spawn time so theft of the JWT doesn't grant access without the private key
- HTTP MCP endpoint: expose
POST /api/mcpas a remote MCP server so users can connect any Streamable HTTP client directly to the deployed URL, no local process needed - Additional providers: Google Drive, Slack, Linear — the delegation model is provider-agnostic, adding a new connection is a matter of implementing the tool handlers
- Audit logging: structured log of every Token Vault call and delegation issuance for compliance use cases
Built With
- next.js
- node.js
- sqlite
- typescript
Log in or sign up for Devpost to join the conversation.