I Built an AI Agent That Can't Steal Your Passwords — Here's How
Posted for the Auth0 "Authorized to Act" Hackathon
There's this moment every developer has. You're building something cool, something that feels genuinely useful, and then you stop and go — wait, where do the tokens actually live?
That moment hit me hard while thinking about AI agents. Not the polished demo kind. The real kind. The kind that wakes up in the morning, checks your Gmail, files a Jira ticket, posts a Slack update, and you don't even have to think about it.
Those agents need OAuth tokens. Lots of them. And traditionally, that means someone has to hold onto them — and that someone is usually the agent itself.
That's a problem I couldn't stop thinking about.
So I Built Bridgekeeper
The name felt right. A gatekeeper that stands between your AI and your actual data. Something that says "you can ask, but I decide what you get."
The idea is pretty simple: split the AI agent into two distinct layers. One layer does the thinking. The other does the doing. And critically — only the second layer ever sees a real token.
The local AI agent — powered by Claude — sits in a sandbox. It reads your goal in plain English, breaks it down into structured capability requests, and hands that plan over to the bridge. That's it. That's all it gets to do. It never calls Gmail directly. It never touches the vault. It literally cannot.
The Governed Execution Bridge is where the action happens. It validates the plan, checks a risk policy, fetches tokens from Auth0 Token Vault on a per-request basis, calls the actual APIs, and writes everything to an audit log. Clean, controlled, accountable.
The Thing That Actually Surprised Me
I expected the token management to be the annoying part. Refresh logic, expiry checks, retry handling — that stuff is tedious. But Auth0 Token Vault just... handles it.
When a provider's token is close to expiring, I just re-fetch. Vault internally checks the expiry, uses the stored refresh token, and returns something fresh. There's no scheduler. No background job. No state machine for "is this token still valid?" You ask for the token, you get a valid one. Done.
The async authorization flow was the real moment of delight, though. Say a user asks the agent to post something to Notion — but they've never connected Notion before. Instead of throwing an error, Bridgekeeper suspends the job, generates a proper Auth0 authorization URL, and waits. The user gets a little banner: "Hey, looks like Notion isn't connected yet. Click here." They authorize, Vault stores the token, and the job quietly resumes like nothing happened.
That flow took me maybe two hours to implement. It felt like it should have taken two weeks.
Step-Up Auth: Because Some Actions Deserve a Pause
Here's something I feel strongly about: an AI agent should not be able to send an email on your behalf without asking you first.
I don't care how much you trust the model. Sending emails, deleting records, publishing to external systems — these are irreversible actions. The model might misread your intent. It might get confused. It might do exactly what you asked but not what you meant.
So in Bridgekeeper, any HIGH-risk action triggers a step-up authentication challenge. The job pauses. You see a consent modal. You have to physically click Approve before the bridge will proceed. The agent cannot bypass this. There is no flag to disable it. It's structural.
This is what I mean when I say the security model is baked in, not bolted on. The risk posture engine isn't a configuration file you can tweak away. It's part of the execution path.
The Audit Trail That Made Me Feel Good About Everything
Every single thing Bridgekeeper does gets written to a structured audit log. Token requested. Step-up required. Consent given. API called. Result received.
Not just "something happened." Exactly what happened, when, for whom, on which provider, with which risk level, in how many milliseconds.
I built a little ring buffer in the UI so you can watch events appear in real time while the agent works. It sounds like a small thing but it fundamentally changed how the system feels. You're not watching a spinner hoping the AI is doing the right thing. You're watching a log prove it is.
Six Providers, All in Mock Mode
Gmail, Google Calendar, GitHub, Jira, Notion, Slack.
All six are wired up and working. And because I wanted the hackathon judges (and anyone curious about the code) to be able to run the whole thing without setting up real credentials, there's a MOCK_PROVIDERS=true flag. The mock mode returns realistic-looking data and exercises every single code path — including the async auth flow, the step-up challenge, and the risk policy logic.
npm install && npm start and you're running.
What I'd Build Next
Honestly, the part I kept thinking about at the end was the risk posture engine. Right now it's configurable at a system level — LOW, MEDIUM, HIGH. But what if it were configurable per-user, or even per-workflow? What if the agent could propose a risk justification, and the bridge could evaluate it before deciding whether to proceed?
That feels like the future of AI agent authorization. Not blanket trust. Not blanket restriction. Context-aware consent, managed by a proper authorization system that the agent can't override.
That's what Auth0 Token Vault makes possible. And building Bridgekeeper made me genuinely excited about where this is heading.
Try It Yourself
Live at: bridgekeeper.vercel.app
Full source on GitHub — the vault integration is in src/vault/, four files, heavily commented. If you're thinking about token management for your own agent projects, it's worth a read.
The agent planned. Auth0 authorized. Bridgekeeper executed.
Built for the Auth0 "Authorized to Act" Hackathon.
Bonus Blog Post
The Night I Realised My AI Agent Had Too Much Power
It started with a dumb mistake.
Early in building Bridgekeeper, I had the agent holding a hardcoded access token in an environment variable — just to "get things working." Standard prototype move. And it did work. The agent could read emails, create Jira tickets, post to Notion. Everything hummed along.
Then I sat back and thought: what exactly is stopping this thing from doing whatever it wants?
Nothing. Absolutely nothing. The agent had a long-lived token and a direct line to six different APIs. One confused prompt, one edge case in the planner logic, and it could start sending emails I never approved. Silently. With my credentials.
That's when the project changed.
Integrating Auth0 Token Vault forced me to think architecturally, not just functionally. The first real technical hurdle was understanding that Token Vault isn't just a safer place to store secrets — it's a different mental model entirely. Tokens aren't held by the agent. They're fetched on demand, scoped to a single action, and never survive the request. I had to rethink how the executor passed credentials down the call stack. Every function that previously accepted a config object now calls getTokenForProvider() fresh.
The async authorization pattern broke my brain for a day. Suspending a running job mid-execution, storing a requestId, waiting for an OAuth callback, then resuming — that's not a tutorial pattern. I had to build the job state machine from scratch before it clicked.
But the moment the step-up challenge fired correctly for the first time — pausing a send_email action and waiting for my explicit approval before proceeding — I felt it. That's what it's supposed to feel like. The agent earned its access. I gave it deliberately. And the audit log proved every step.
That's the thing Token Vault gave me that no environment variable ever could: proof.
Log in or sign up for Devpost to join the conversation.