This is a submission for the Auth0 for AI Agents Challenge
What I Built
AgentFlow is a comprehensive AI agent platform that enables users to create, manage, and deploy autonomous AI agents that can interact with their connected services (Gmail, Slack, Google Calendar, etc.) on their behalf. The platform solves the critical problem of secure credential management for AI agents while providing a beautiful, intuitive interface for agent creation and monitoring.
Key Features:
π€ Multi-Template Agent Builder
- Pre-configured templates (Email Assistant, Calendar Manager, Social Media Manager)
- Custom agent creation with flexible service selection
- Visual workflow configuration
π Secure OAuth Integration
- Auth0-powered authentication for users
- Token Vault for secure credential storage
- Service-specific permission scopes
- Encrypted token storage in DynamoDB
π Real-Time Agent Dashboard
- Live activity feed showing agent actions
- Performance metrics and analytics
- Success rate tracking
- Time-saving calculations
β‘ Agent Management
- Pause/Resume agents with one click
- Delete agents with confirmation
- View detailed activity logs
- Monitor connected services
π Multi-Service Integration
- Gmail (read, send, categorize emails)
- Slack (post messages, notifications)
- Google Calendar (event management)
- Extensible architecture for more services
Demo
Live Demo: https://main.d13aenlm5qrdln.amplifyapp.com
GitHub Repository: https://github.com/Abhinandangithub01/AgentFlow
How I Used Auth0 for AI Agents
Auth0 was absolutely critical to solving the core security challenge of AgentFlow. Here's how I leveraged Auth0's capabilities:
- User Authentication (Auth0 SDK)
// Next.js App Router integration
import { handleAuth, handleLogin, handleCallback } from '@auth0/nextjs-auth0';
export const GET = handleAuth({
login: handleLogin({
returnTo: '/dashboard'
}),
callback: handleCallback({
redirectUri: process.env.AUTH0_BASE_URL + '/api/auth/callback'
})
});
Why this matters: Every user action is authenticated, ensuring only authorized users can create and manage agents.
- Token Vault for Secure Credential Storage The game-changer was implementing Auth0's Token Vault pattern for storing OAuth tokens:
// lib/token-vault.ts
export class TokenVaultService {
async storeOAuthToken(
userId: string,
service: string,
tokens: SecureToken,
agentId?: string
): Promise<TokenVaultEntry> {
const vaultKey = this.generateVaultKey(userId, service, agentId);
// Store in DynamoDB with encryption
await DynamoDBService.put(TABLES.TOKENS, {
PK: `TOKEN#${vaultKey}`,
SK: 'METADATA',
accessToken: tokens.accessToken,
refreshToken: tokens.refreshToken,
expiresIn: tokens.expiresIn,
tokenType: tokens.tokenType,
scope: tokens.scope,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
return entry;
}
async getOAuthToken(
userId: string,
service: string,
agentId?: string
): Promise<SecureToken | null> {
const vaultKey = this.generateVaultKey(userId, service, agentId);
const item = await DynamoDBService.get(TABLES.TOKENS, {
PK: `TOKEN#${vaultKey}`,
SK: 'METADATA',
});
return item ? {
accessToken: item.accessToken,
refreshToken: item.refreshToken,
expiresIn: item.expiresIn,
tokenType: item.tokenType,
scope: item.scope,
} : null;
}
}
Security Benefits:
β
Tokens never exposed to client-side code
β
Encrypted at rest in DynamoDB (AWS AES-256)
β
Scoped to specific user + service + agent combinations
β
Automatic token refresh handling
β
Secure revocation on agent deletion
- OAuth Flow for Service Connections
// app/api/auth/gmail/callback/route.ts
export async function GET(request: NextRequest) {
const session = await getSession();
if (!session?.user) {
return NextResponse.redirect(new URL('/?error=no_session', request.url));
}
const code = searchParams.get('code');
// Exchange code for tokens
const oauth2Client = new google.auth.OAuth2(
process.env.GOOGLE_CLIENT_ID,
process.env.GOOGLE_CLIENT_SECRET,
redirectUri
);
const { tokens } = await oauth2Client.getToken(code);
// Store in Token Vault (Auth0 pattern)
await tokenVault.storeOAuthToken(
session.user.sub,
'gmail',
{
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token,
expiresIn: tokens.expiry_date ?
Math.floor((tokens.expiry_date - Date.now()) / 1000) : 3600,
tokenType: 'Bearer',
scope: tokens.scope || '',
}
);
// Store connection record
await DynamoDBService.put(TABLES.CONNECTIONS, {
PK: `USER#${session.user.sub}`,
SK: 'SERVICE#gmail',
service: 'gmail',
status: 'connected',
scopes: tokens.scope?.split(' ') || [],
connectedAt: new Date().toISOString(),
});
return NextResponse.redirect(new URL('/integrations', request.url));
}
- Agent-Specific Token Access Each agent gets its own scoped access to tokens:
// When agent needs to access Gmail
const agent = await agentManager.getAgent(agentId, userId);
const gmailToken = await tokenVault.getOAuthToken(
userId,
'gmail',
agentId // Agent-specific token
);
// Use token to perform action
const gmail = google.gmail({ version: 'v1', auth: oauth2Client });
oauth2Client.setCredentials({
access_token: gmailToken.accessToken,
refresh_token: gmailToken.refreshToken
});
- Session Management
// Middleware for protected routes
export async function middleware(request: NextRequest) {
const session = await getSession();
if (!session?.user) {
return NextResponse.redirect(new URL('/', request.url));
}
return NextResponse.next();
}
- Persistent Token Storage Critical Fix: Initially used in-memory storage which lost tokens on reload. Migrated to DynamoDB for persistence:
// Before: β Lost on reload
global.tokenVault = new Map();
global.tokenVault.set(key, tokens);
// After: β
Persists forever
await DynamoDBService.put(TABLES.TOKENS, {
PK: `TOKEN#${key}`,
SK: 'METADATA',
...tokens
});
Result: Tokens now survive:
β
Page reloads
β
Server restarts
β
Deployment updates
β
Load balancing across instances
Lessons Learned and Takeaways
π― 1. Token Security is HARD (But Auth0 Makes It Easier)
Challenge: Initially, I stored OAuth tokens in memory, which seemed simple but caused tokens to disappear on every page reload!
Solution: Implemented Auth0's Token Vault pattern with DynamoDB persistence.
Lesson: Never underestimate the complexity of secure credential management. Auth0's patterns and documentation were invaluable in getting this right.
// The critical bug that took hours to debug:
// Storing with entry.id but retrieving with vaultKey! π
await this.encryptAndStore(entry.id, tokens); // β Wrong key
const token = await this.decryptAndRetrieve(vaultKey); // β Different key
// The fix:
await this.encryptAndStore(vaultKey, tokens); // β
Consistent key
const token = await this.decryptAndRetrieve(vaultKey); // β
Same key
Huge thanks to Auth0 and DEV.to for this challenge! Building AgentFlow taught me more about authentication, security, and AI agents than any tutorial could. The Auth0 SDK and Token Vault patterns were game-changers.
Special shoutout to the Auth0 documentation team - your Next.js examples saved me countless hours!
Built with β€οΈ using Auth0, Next.js 14, TypeScript, DynamoDB, and AWS Amplify
Top comments (0)