OAuth for AI Agents — Auth & Permissions Platform
AgentGate is the missing auth layer for AI agents. It gives developers scoped permissions, users consent controls, and everyone an audit trail.
This is an open-source project — contributions welcome! See CONTRIBUTING.md to get started.
AI agents are increasingly acting on behalf of users — sending emails, creating PRs, managing CRMs, making payments. But there is no standard way to control what agents can do.
-
No scoped permissions. When you give an AI agent your API key, it gets full access. There's no way to say "you can read my emails but not send them."
-
No user consent. Users have no visibility into what an agent is requesting access to, and no way to approve or deny specific capabilities.
-
No audit trail. When an agent acts on your behalf, there's no centralized log of what it did, when, or whether it was authorized.
-
No revocation. If you want to stop an agent from accessing your accounts, there's no standard "off switch" — you have to rotate API keys, breaking everything else.
-
No policy enforcement. There's no way to say "this agent can only send 10 emails per hour" or "this agent can only operate during business hours."
This is the same problem OAuth solved for web apps in 2010 — but for AI agents. Every company deploying agents will need this.
AgentGate provides a complete authorization layer between AI agents and the services they access:
┌──────────┐ ┌──────────────┐ ┌─────────────┐
│ AI Agent │────▶│ AgentGate │────▶│ Your APIs │
│ │◀────│ (Auth Layer) │◀────│ & Services │
└──────────┘ └──────────────┘ └─────────────┘
│
┌────┴────┐
│ User │
│ Consent │
└─────────┘
| Feature | Description |
|---|---|
| Scoped Permissions | 28 predefined scopes across 10 categories (email, GitHub, CRM, payments, etc.). Agents only get what users approve. |
| Consent Flow | OAuth-style consent screen where users see exactly what an agent wants to do, with risk-level indicators. |
| Token Management | Short-lived JWTs (15 min) with refresh token rotation. Replay detection automatically revokes compromised token families. |
| Policy Engine | Configurable rate limits, time windows, action allowlists/blocklists. Control agent behavior at scale. |
| Audit Logging | Every agent action is logged with outcome (allowed/denied), the policy that blocked it, and full request metadata. |
| Instant Revocation | Users revoke any agent's access with one click. All tokens invalidated immediately. |
agentgate/
├── packages/
│ ├── shared/ # Zod schemas, TypeScript types, scope catalog
│ ├── api/ # Hono REST API (core auth engine)
│ ├── web/ # Next.js dashboard + consent screen
│ └── sdk/ # TypeScript SDK for agent developers
├── .github/
│ └── workflows/ # CI/CD pipeline
└── docker-compose.yml
- API: Hono (TypeScript) on Node.js
- Database: SQLite (dev) / PostgreSQL (production) via Drizzle ORM
- Cache: In-memory (dev) / Redis (production)
- Frontend: Next.js 15 + Tailwind CSS
- Auth: JWT (HS256) via jose, Argon2 password hashing
- Validation: Zod schemas shared between API and frontend
developers ──┐
├── agents ──┐
users ───────┤ ├── consents ── tokens ── refresh_tokens
│ │
└── policies ┘
└── audit_logs
- Node.js >= 20
git clone https://github.com/your-username/agentgate.git
cd agentgate
npm install# Push the database schema (creates SQLite DB)
npm run db:push
# Start the API (port 3001)
npm run dev -w @agentgate/api
# Start the frontend (port 3000) — in another terminal
npm run dev -w @agentgate/web- Landing page: http://localhost:3000
- Developer portal: http://localhost:3000/developer
- User dashboard: http://localhost:3000/dashboard
- API: http://localhost:3001
1. DEVELOPER registers → gets API key
2. DEVELOPER registers an AGENT → gets clientId + clientSecret
3. AGENT requests consent from USER → user sees consent screen
4. USER approves scopes → auth code issued
5. AGENT exchanges code → gets accessToken + refreshToken
6. AGENT calls your API with the token
7. YOUR API calls AgentGate /verify → allowed or denied
8. Everything is logged in the audit trail
1. Register as a developer:
curl -X POST http://localhost:3001/api/v1/developers/register \
-H "Content-Type: application/json" \
-d '{"name":"Acme AI","email":"dev@acme.com","password":"securepass123"}'2. Register an agent:
curl -X POST http://localhost:3001/api/v1/agents \
-H "Content-Type: application/json" \
-H "X-Api-Key: ag_YOUR_API_KEY" \
-d '{
"name": "SmartMail",
"description": "AI email assistant",
"redirectUris": ["https://yourapp.com/callback"],
"allowedScopes": ["email:read", "email:send"]
}'3. Request user consent:
curl -X POST http://localhost:3001/api/v1/consent/initiate \
-H "Content-Type: application/json" \
-d '{
"clientId": "agc_YOUR_CLIENT_ID",
"requestedScopes": ["email:read", "email:send"],
"redirectUri": "https://yourapp.com/callback",
"userId": "usr_USER_ID"
}'4. User approves → you get an auth code. Exchange it:
curl -X POST http://localhost:3001/api/v1/tokens/exchange \
-H "Content-Type: application/json" \
-d '{
"code": "AUTH_CODE",
"clientId": "agc_YOUR_CLIENT_ID",
"clientSecret": "ags_YOUR_CLIENT_SECRET"
}'5. Verify the token before acting:
curl -X POST http://localhost:3001/api/v1/tokens/verify \
-H "Content-Type: application/json" \
-d '{
"token": "eyJhbG...",
"requiredScopes": ["email:send"]
}'
# → { "allowed": true, "userId": "usr_...", "grantedScopes": [...] }Install the SDK in your agent:
npm install @agentgate/sdkimport { AgentGateClient } from "@agentgate/sdk";
const gate = new AgentGateClient({
baseUrl: "https://api.agentgate.dev",
clientId: "agc_...",
clientSecret: "ags_...",
});
// 1. Request consent
const consent = await gate.requestConsent({
userId: "usr_...",
scopes: ["email:read", "email:send"],
redirectUri: "https://yourapp.com/callback",
});
// → redirect user to consent.consentUrl
// 2. After user approves, exchange code
const session = await gate.createSession(authCode);
// 3. Verify before acting (auto-refreshes token)
const result = await session.verify(["email:send"]);
if (result.allowed) {
// safe to send email on behalf of user
await sendEmail(/* ... */);
}
// 4. Revoke when done
await session.revoke();import { AgentGateAdmin } from "@agentgate/sdk";
const admin = new AgentGateAdmin({
baseUrl: "https://api.agentgate.dev",
apiKey: "ag_...",
});
// Register agents
const agent = await admin.createAgent({
name: "My Agent",
redirectUris: ["https://myapp.com/callback"],
allowedScopes: ["email:read"],
});
// Set policies
await admin.createPolicy({
agentId: agent.id,
name: "Rate limit emails",
rules: [{
type: "rate_limit",
config: { maxRequests: 10, windowSeconds: 3600, scope: "per_user" },
}],
});
// View audit logs
const logs = await admin.getAuditLogs({ agentId: agent.id });| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/api/v1/developers/register |
None | Register developer account |
POST |
/api/v1/developers/login |
None | Login |
POST |
/api/v1/developers/api-keys/rotate |
API Key | Rotate API key |
GET |
/api/v1/agents |
API Key | List your agents |
POST |
/api/v1/agents |
API Key | Register new agent |
GET |
/api/v1/agents/:id |
API Key | Get agent details |
PATCH |
/api/v1/agents/:id |
API Key | Update agent |
POST |
/api/v1/agents/:id/rotate-secret |
API Key | Rotate client secret |
POST |
/api/v1/consent/initiate |
None | Start consent flow |
GET |
/api/v1/consent/:id |
None | Get consent details |
POST |
/api/v1/consent/:id/approve |
None | Approve consent |
POST |
/api/v1/consent/:id/deny |
None | Deny consent |
POST |
/api/v1/tokens/exchange |
None | Exchange code for tokens |
POST |
/api/v1/tokens/refresh |
None | Refresh access token |
POST |
/api/v1/tokens/revoke |
None | Revoke a token |
POST |
/api/v1/tokens/verify |
None | Verify token + policies |
POST |
/api/v1/users/register |
None | Register user |
POST |
/api/v1/users/login |
None | Login user |
GET |
/api/v1/users/:id/consents |
None | List user's consents |
POST |
/api/v1/users/:id/consents/:cid/revoke |
None | Revoke consent |
GET |
/api/v1/users/:id/audit-logs |
None | User's audit log |
GET |
/api/v1/policies |
API Key | List policies |
POST |
/api/v1/policies |
API Key | Create policy |
PATCH |
/api/v1/policies/:id |
API Key | Update policy |
DELETE |
/api/v1/policies/:id |
API Key | Delete policy |
GET |
/api/v1/scopes |
None | List all scopes |
GET |
/api/v1/scopes/:category |
None | Scopes by category |
GET |
/api/v1/audit |
API Key | Query audit logs |
GET |
/api/v1/health |
None | Health check |
| Scope | Category | Risk Level |
|---|---|---|
email:read |
standard | |
email:send |
high | |
email:manage |
high | |
calendar:read |
calendar | low |
calendar:write |
calendar | standard |
github:repo:read |
github | standard |
github:repo:write |
github | high |
github:pr:create |
github | standard |
github:pr:merge |
github | critical |
github:issues:write |
github | standard |
crm:contacts:read |
crm | standard |
crm:contacts:write |
crm | standard |
crm:deals:read |
crm | standard |
crm:deals:write |
crm | high |
messaging:read |
messaging | standard |
messaging:send |
messaging | high |
files:read |
files | standard |
files:write |
files | high |
files:delete |
files | critical |
db:read |
database | standard |
db:write |
database | high |
payments:read |
payments | standard |
payments:charge |
payments | critical |
profile:read |
profile | low |
profile:write |
profile | standard |
// Rate limit: max N requests per window
{ type: "rate_limit", config: { maxRequests: 100, windowSeconds: 3600, scope: "per_user" } }
// Time window: only allow during business hours
{ type: "time_window", config: { allowedDays: [1,2,3,4,5], startTimeUtc: "09:00", endTimeUtc: "17:00", timezone: "UTC" } }
// Action allowlist: only allow specific scopes
{ type: "action_allowlist", config: { actions: ["email:read"] } }
// Action blocklist: block specific scopes
{ type: "action_blocklist", config: { actions: ["payments:charge"] } }
// IP allowlist: restrict to specific IPs
{ type: "ip_allowlist", config: { cidrs: ["10.0.0.0/8"] } }- Passwords hashed with Argon2id
- API keys & client secrets stored as SHA-256 hashes; plaintext shown once on creation
- Access tokens are JWTs (HS256), 15-minute expiry
- Refresh tokens are opaque, 30-day expiry, rotated on every use
- Replay detection: if a refresh token is used twice, the entire token family is revoked
- Auth codes expire in 60 seconds, single-use
- Rate limiting on all auth endpoints
- Audit logs are append-only
# Make sure the API is running first
npm run dev -w @agentgate/api
# Run the test suite
npm test -w @agentgate/api# Build
docker build -f packages/api/Dockerfile -t agentgate-api .
docker build -f packages/web/Dockerfile -t agentgate-web .
# Run
docker run -p 3001:3001 \
-e JWT_SECRET=your-production-secret-here \
-e DATABASE_PATH=/data/agentgate.db \
-v agentgate-data:/data \
agentgate-api| Variable | Default | Description |
|---|---|---|
DATABASE_PATH |
./agentgate.db |
SQLite database file path |
JWT_SECRET |
(required) | Secret for signing JWTs (min 32 chars) |
JWT_ISSUER |
agentgate |
JWT issuer claim |
API_PORT |
3001 |
API server port |
API_URL |
http://localhost:3001 |
Public API URL (for consent URLs) |
NEXT_PUBLIC_API_URL |
http://localhost:3001 |
API URL for frontend |
Contributions are welcome! Check out CONTRIBUTING.md for guidelines.
- Session-based auth for dashboard (replace userId in URLs)
- Webhook notifications on consent/revocation events
- Multi-tenant organization support
- PostgreSQL + Redis for production deployment
- OpenAPI spec auto-generation
- Agent-to-agent delegation chains
- MCP server integration
- More scope categories (Slack, Notion, Linear, etc.)
- OAuth 2.1 full compliance
- Admin analytics dashboard
Pick any of these and open a PR!
MIT — see LICENSE