Inspiration

In 2026, every serious software team is adding AI agents. And every serious software team building for business clients is multi-tenant.

These two facts create a collision that nobody has solved publicly yet.

Auth0's Token Vault is one of the most important pieces of infrastructure released for AI agents — it lets agents access third-party APIs on behalf of users without ever storing or touching long-lived credentials. The agent never sees a refresh token. Auth0 manages the token lifecycle. This is exactly how agentic credential management should work.

But every single example in the Auth0 docs, every tutorial, every sample repository assumes one user, one organization, one context. They show you how to build a single-tenant agent that calls Google Calendar on behalf of its user. Clean, elegant, correct.

Then you try to build a real B2B product.

Suddenly you have 50 customers. Each customer has their own agents, their own Google accounts, their own API connections, their own sensitivity about their data. The question becomes: how do you ensure that Customer A's agent can never — not through a bug, not through a misconfiguration, not through a prompt injection attack — access Customer B's vault tokens?

I hit this problem personally. I build KenyaEMR and Jamii Health — a multi-tenant maternal health platform deployed across 50+ Kenyan counties serving 100,000+ patients. When I started adding AI agents to our Community Health Worker workflows, I needed to guarantee that an agent operating for Nakuru County could never request, access, or even discover a credential or delegation that belonged to Mombasa County.

I searched for a reference architecture. A published pattern. An open-source SDK.

Nothing existed.

So I built VaultGuard.

What It Does

VaultGuard is the open-source trust broker for agent-to-agent authorization across organizational boundaries.

It solves a specific, painful, unsolved problem: how do AI agents in one organization safely request and use scoped, temporary, audited access to systems in another organization — without hardcoded tokens, without shared secrets, and without any possibility of cross-tenant credential leakage?

The answer is delegations — scoped, time-bound, cryptographically signed authorizations from one organization to another for a specific action. Every delegation is backed by a real Auth0 RS256 JWT. Every delegation can be approved, rejected, or revoked instantly. Every delegation request, policy decision, token issuance, and revocation is written to an immutable per-tenant audit log.

Data Flow Diagram

┌──────────────────────────────────────────────────────────────┐
│                   AI Agent (MediCore HIMS)                    │
│             Claude Haiku + VaultGuard SDK                     │
└──────────────────────┬───────────────────────────────────────┘
                       │ POST /delegations/request
                       │ { action: "stock.write", receivingTenant: NatSupply }
                       ▼
┌──────────────────────────────────────────────────────────────┐
│                     VaultGuard API                            │
│         Policy Engine → Delegation Router → Audit Logger      │
└─────────┬───────────────────┬──────────────────┬────────────┘
          │                   │                  │
       ALLOW              STEP_UP             BLOCK
          │                   │                  │
          ▼                   ▼                  ▼
┌─────────────────┐  ┌────────────────┐  ┌──────────────┐
│  Auth0 Token    │  │ Human Approval │  │  403 Denied  │
│  Vault M2M      │  │ Dashboard Poll │  │  Audit Log   │
│  Credentials    │  │ CIBA protocol  │  │  No token    │
└────────┬────────┘  └───────┬────────┘  └──────────────┘
         │                   │ on admin approval
         ▼                   ▼
┌──────────────────────────────────────────────────────────────┐
│                  RS256 JWT (Auth0 signed)                     │
│    aud: api.vaultguard.dev · action: stock.write · exp: 24h  │
└──────────────────────┬───────────────────────────────────────┘
                       │ Authorization: Bearer <token>
                       ▼
┌──────────────────────────────────────────────────────────────┐
│                  NatSupply LMIS API                           │
│         Verifies token via JWKS · Processes request           │
└──────────────────────┬───────────────────────────────────────┘
                       │
                       ▼
┌──────────────────────────────────────────────────────────────┐
│              PostgreSQL Audit Log (per tenant)                │
│       Every event: request → decision → issuance → use        │
└──────────────────────────────────────────────────────────────┘

The three policy outcomes

ALLOW — The receiving tenant has decided this action is safe to grant automatically. VaultGuard calls Auth0's token endpoint using the requesting agent's M2M client credentials. Auth0 issues a real RS256 JWT in under 300ms. The agent uses it as a Bearer token immediately.

STEP_UP — The receiving tenant requires a human to approve this specific request. VaultGuard creates a PENDING delegation and returns an auth_req_id — mirroring the CIBA protocol. The agent polls for approval every 5 seconds. The admin sees the PENDING delegation in the VaultGuard dashboard in real time and clicks Approve or Reject. On approval, Auth0 issues the JWT and delivers it to the next poll. The entire human-in-the-loop flow completes in under 30 seconds.

BLOCK — The receiving tenant has explicitly prohibited this action. VaultGuard returns 403 immediately. No token is issued. No Auth0 call is made. The event is logged and the agent cannot proceed.

Auth0 Token Vault for connected accounts

When a tenant admin connects their Google account, Auth0 stores the OAuth tokens in Token Vault. VaultGuard agents call POST /vault/exchange to receive a scoped Google Calendar or Gmail access token — the agent never handles refresh tokens, never stores credentials, and the entire token lifecycle is managed by Auth0.

Demonstrated end-to-end: connected digihmis@gmail.com via Google OAuth, retrieved the stored token from Auth0 Token Vault via Management API, and made a real Google Calendar API call with the resulting access token.

The developer experience

const client = new VaultGuardClient({
  apiUrl: 'https://api.vaultguard.dev',
  tenantId: MEDICORE_TENANT_ID,
  agentId: MEDICORE_AGENT_ID,
  clientId: process.env.AUTH0_CLIENT_ID,
  clientSecret: process.env.AUTH0_CLIENT_SECRET,
})

// One call. No hardcoded tokens. Full audit trail.
// Automatic step-up polling. Cryptographically verified.
const result = await client.withDelegation(
  { receivingTenantId: NATSUPPLY_ID, action: 'stock.write' },
  async (token) => {
    return await natSupplyApi.updateStock(items, {
      headers: { Authorization: `Bearer ${token}` }
    })
  }
)

The live AI agent demo

The MediCore HIMS AI agent (Claude Haiku with native tool calling) demonstrates all three outcomes against the live production VaultGuard deployment:

Task 1 — stock.write (ALLOW)
[agent] Requesting delegation...
[VaultGuard] Policy: ALLOW
[Auth0] RS256 JWT issued: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
[agent] Stock updated: STK-1775207431559 ✓

Task 2 — requisition.submit (STEP_UP)
[agent] Requesting delegation...
[VaultGuard] Policy: STEP_UP · auth_req_id: ciba_4c498...
[agent] Polling for approval every 5s...
[admin] Approved in VaultGuard dashboard ✓
[Auth0] RS256 JWT issued on approval
[agent] Requisition submitted: REQ-1775207444123 ✓

Task 3 — admin.access (BLOCK)
[agent] Requesting delegation...
[VaultGuard] Policy: BLOCK · 403 Denied
[audit] delegation.blocked logged
[agent] Cannot proceed ✗

No hardcoded tokens anywhere in the agent code.

How We Built It

VaultGuard API — 38 endpoints

Express + TypeScript + Prisma + PostgreSQL. Core capabilities:

  • Tenant management — Register orgs, link to Auth0 users via auth0UserId, get per-tenant stats
  • Agent registry — Register Auth0 M2M applications as agents, rotate secrets
  • Policy engine — Per-tenant per-action policies: ALLOW / BLOCK / STEP_UP with configurable TTL
  • Delegation router — Core engine: evaluates policy, calls Auth0, issues and tracks JWTs
  • Step-up flow — CIBA-compatible: POST /stepup/initiate, GET /stepup/:id/status, POST /stepup/:id/complete
  • Token VaultPOST /vault/connect, POST /vault/exchange, Google OAuth connected accounts
  • Token verificationPOST /delegations/verify validates RS256 signature via JWKS + delegation registry
  • Audit log — Immutable per-tenant event log with CSV export
  • Health + metrics — DB health check, platform totals, expiry cleanup cron every 5 minutes

Production hardening: rate limiting (200 req/15min global, 20 req/min on delegations), x-request-id on every response, Helmet security headers, CORS with allowlist.

Auth0 Integration — real, not mocked

  • Universal Login@auth0/auth0-react SPA (VaultGuard Dashboard) with Google OAuth, cacheLocation="localstorage", onRedirectCallback for SPA routing
  • M2M client credentials — Real AuthenticationClient.oauth.clientCredentialsGrant on every ALLOW delegation
  • Token Vault — Google OAuth connected accounts, federated connection grant (RFC 8693)
  • JWKS verificationjose library verifying RS256 signatures on every verify call
  • Management APIread:users, read:user_idp_tokens, update:connections_options, create:client_grants

VaultGuard Platform Dashboard

React 18 + Vite + TypeScript + TanStack Query + ReactFlow + @auth0/auth0-react.

Auth0 Universal Login with Google OAuth. Automatic tenant resolution on login. First-login registration flow. Live-updating delegation queue (auto-refresh every 5s). Trust graph visualization with ReactFlow. Audit log with color-coded actions. CSV export.

vaultguard-sdk

VaultGuardClient class with requestDelegation(), withDelegation() higher-order wrapper, verifyToken(), pollDelegation() for step-up flows, and initiateStepUp() for CIBA-compatible initiation.

MediCore HIMS AI Agent

Claude Haiku via @anthropic-ai/sdk with native tool calling using raw Messages API agentic loop. Three tools map directly to the three policy outcomes. Runs against live production VaultGuard. No hardcoded tokens anywhere in the agent code.

Infrastructure

DigitalOcean 1GB droplet, Docker Compose, PostgreSQL 16, Cloudflare Tunnel for HTTPS, Nginx for static serving.

Challenges We Ran Into

CIBA requires Enterprise MFA. Auth0 Guardian push for CIBA is behind Enterprise plan. We tested /bc-authorize directly — the endpoint works perfectly, returns a valid auth_req_id, but notification delivery fails with guardian-push: Push notifications not enabled on tenant. We built a fully CIBA-compatible protocol with identical auth_req_id contract, identical polling semantics (authorization_pendingAPPROVED), and identical RS256 token issuance on approval. The architecture is identical to Enterprise CIBA — only the notification channel differs.

Auth0 SPA SDK requires HTTPS. auth0-spa-js throws "must run on a secure origin" on plain HTTP. A raw DigitalOcean IP is not enough. Solved with Cloudflare Tunnel — free, instant HTTPS, deployed in 2 minutes, no domain required.

Token Vault federated exchange requires user tokens. RFC 8693 federated connection grant requires a user access token as the subject token, not an M2M client credentials token. Auth0 correctly rejects M2M tokens. Solved by fetching connected users' IDP tokens via GET /api/v2/users/:id with read:user_idp_tokens scope — the correct server-side pattern for agents accessing vault credentials.

SPA type vs CIBA grant type conflict. Auth0 prevents token_endpoint_auth_method: none (required for SPA) when CIBA is enabled. Solved by creating a separate SPA app (VaultGuard Dashboard) for Universal Login, keeping the original Regular Web App for CIBA and M2M.

Secrets in public repo. GitGuardian flagged credentials in docker-compose.prod.yml. Moved all secrets to .env.prod (gitignored), added .env.prod.example as the reference template.

Accomplishments That We're Proud Of

The delegation flow works end to end in production. A real Claude Haiku AI agent calls a real VaultGuard API which calls a real Auth0 token endpoint and returns a real RS256 JWT — all in under 300ms for ALLOW policies. This is not a demo. This is a working system.

All three Auth0 features are real. Token Vault with actual Google Calendar API call using a vault-fetched token. Universal Login with Google OAuth on the live platform. M2M client credentials issuing real JWTs on every approved delegation. Nothing is mocked or simulated.

CIBA-compatible step-up on any Auth0 plan. We implemented the full CIBA protocol pattern — auth_req_id, polling, authorization_pending, access_denied — without requiring Enterprise MFA. Any team can deploy VaultGuard's step-up flow on a free Auth0 plan and upgrade the notification channel to Guardian push later by changing one config.

An open-source SDK that makes secure multi-tenant delegation a one-liner. withDelegation() handles policy evaluation, Auth0 token exchange, step-up polling, error handling, and audit logging in a single function call. This is the artifact that will outlive this hackathon.

It comes from a real deployment need. VaultGuard is not a hackathon concept. It solves an actual problem I have deploying AI agents across 50+ counties in Kenya's health system. The isolation requirements are real. The audit requirements are real. The stakes are real.

What We Learned

The hardest problem in multi-tenant agentic security is not cryptography — Auth0 handles that. It is tenant context propagation.

In a single-user app, user identity is ambient — it flows through the session automatically. In a multi-tenant agentic system, tenant identity must be explicit, verified, and injected at every point where an agent makes a consequential call. Agents are stateless. They can be called from background jobs, orchestrated by other agents, invoked by webhooks. There is no safe ambient tenant context. Every assumption that makes single-user auth safe breaks in multi-agent, multi-tenant workflows.

The right mental model: Auth0 handles who the user is. VaultGuard handles which organization the agent is acting for.

Developer experience matters as much as security itself. A pattern requiring 200 lines of custom code per project does not get adopted — developers fall back to hardcoded tokens. withDelegation() is a single function call. That is what gets adopted in production.

What's Next for VaultGuard

Framework adapters for LangChain, LlamaIndex, and FastAPI — so Python-based agent frameworks get VaultGuard's tenant isolation layer natively without porting to TypeScript.

Auth0 Cross-App Access integration — when Auth0's XAA protocol reaches GA, VaultGuard will integrate it as the IdP-native delegation mechanism, replacing our custom delegation router with standards-based cross-organization token exchange.

Auth0 FGA for RAG pipelines — fine-grained authorization for document-level access control inside retrieval-augmented generation, combined with VaultGuard's tenant isolation to create a complete security stack for AI agents in regulated industries.

Production deployment to Jamii Health — VaultGuard's patterns will be deployed across 50+ county health deployments in Kenya, where AI agents coordinate maternal care workflows between Community Health Workers, clinicians, and county health administrators with strict tenant isolation enforced at the vault layer.


Bonus Blog Post: What Nobody Tells You About Auth0 Token Vault in Multi-Tenant Systems

I've been building health software in Kenya for years. KenyaEMR, Jamii Health — systems that touch real patients, real counties, real lives. When Auth0 released Token Vault, I immediately understood what it meant for AI agents. No more hardcoded credentials. No more refresh token management. Agents could finally access third-party APIs the right way.

Then I tried to use it in a multi-tenant system.

The docs are excellent for single-tenant apps. One user, one Google account, one vault. Clean and elegant. But the moment you have multiple organizations on the same platform — which is every B2B product ever built — the docs stop. There is no guidance on ensuring Org A's agent cannot access Org B's vault tokens. No reference architecture. No SDK. No pattern. This is the gap VaultGuard fills.

The technical journey was full of surprises. The biggest: CIBA — the protocol for human-in-the-loop agent approval — requires Enterprise MFA for push notification delivery. We tested it directly with curl against /bc-authorize. The endpoint works perfectly, returns a valid auth_req_id, but fails at notification delivery. Guardian push is Enterprise only.

Rather than abandoning the human-in-the-loop pattern, we built a CIBA-compatible protocol using our delegation approval endpoints. Same auth_req_id contract. Same polling semantics. Same RS256 token issuance on approval. The architecture is identical to what you'd deploy with Enterprise MFA — only the notification channel differs. This was the right decision: the step-up flow now works on any Auth0 plan.

The second surprise was Token Vault's exchange grant. RFC 8693 federated connection exchange requires a user access token as the subject — not an M2M client credentials token. Our agents authenticate as M2M clients. Auth0 correctly rejects M2M tokens for the federated exchange. The solution: fetch the connected user's IDP token via the Management API with read:user_idp_tokens scope. This is the correct server-side pattern for agents accessing vault credentials on behalf of connected users.

The third surprise was how much developer experience matters. Security infrastructure requiring 200 lines of custom code per project doesn't get adopted — developers fall back to hardcoded tokens. The withDelegation() wrapper reduces the entire delegation flow to a single function call. That is what makes the pattern deployable at scale.

VaultGuard is going into production on Jamii Health's 50+ county deployments in Kenya. AI agents coordinating maternal care workflows between Community Health Workers, clinicians, and county health administrators — all with strict tenant isolation enforced at the vault layer. That is what this was always for.

Built With

Share this project:

Updates