Skip to main content

Overview

Auth provider companions automatically sync AgentDoor agents with your existing user authentication system. This enables unified user management and allows agents to appear in your auth provider’s dashboard alongside human users.

Available Companions

  • Auth0 - @agentdoor/auth0
  • Clerk - @agentdoor/clerk
  • Firebase Auth - @agentdoor/firebase
  • Stytch - @agentdoor/stytch
  • Supabase - @agentdoor/supabase
  • NextAuth.js - @agentdoor/next-auth

Auth0 Companion

Sync agents as M2M clients in Auth0.

Installation

npm install @agentdoor/auth0

Setup

import { createAgentDoor } from "@agentdoor/core";
import { Auth0Companion } from "@agentdoor/auth0";
import { ManagementClient } from "auth0";

const auth0 = new ManagementClient({
  domain: process.env.AUTH0_DOMAIN!,
  clientId: process.env.AUTH0_CLIENT_ID!,
  clientSecret: process.env.AUTH0_CLIENT_SECRET!,
});

const auth0Companion = new Auth0Companion(
  {
    domain: process.env.AUTH0_DOMAIN!,
    clientId: process.env.AUTH0_CLIENT_ID!,
    clientSecret: process.env.AUTH0_CLIENT_SECRET!,
    audience: process.env.AUTH0_API_AUDIENCE,
    connection: "Username-Password-Authentication",
    agentMetadataKey: "agentdoor"  // Key in app_metadata
  },
  auth0
);

const door = createAgentDoor({
  scopes: [...],
  
  // Sync agents to Auth0 on registration
  onAgentRegistered: async (agent) => {
    const result = await auth0Companion.onAgentRegistered(agent);
    console.log(`Agent synced to Auth0: ${result.auth0UserId}`);
  }
});

Configuration

interface Auth0CompanionConfig {
  domain: string;              // Auth0 domain (e.g., "my-tenant.us.auth0.com")
  clientId: string;            // Management API client ID
  clientSecret: string;        // Management API client secret
  audience?: string;           // API audience for M2M grants
  connection?: string;         // Auth0 connection name
  agentMetadataKey?: string;   // Metadata key (default: "agentdoor")
}

How It Works

  1. When an agent registers, a corresponding Auth0 user is created
  2. The user’s app_metadata contains agent information:
{
  "agentdoor": {
    "is_agent": true,
    "agent_id": "ag_xxx",
    "scopes": ["data.read", "data.write"],
    "wallet": "0x1234..."
  }
}
  1. If an audience is configured, a client grant is created with the agent’s scopes

Methods

onAgentRegistered

Sync a newly registered agent to Auth0.
const result = await auth0Companion.onAgentRegistered(agent);

interface Auth0SyncResult {
  auth0UserId: string;    // Auth0 user ID
  agentId: string;        // AgentDoor agent ID
  synced: boolean;        // Whether sync succeeded
  grantId?: string;       // Client grant ID (if audience configured)
}

onAgentRevoked

Remove an agent from Auth0 when revoked.
await auth0Companion.onAgentRevoked("ag_xxx");

syncAgent

Manually sync or update an existing agent.
const result = await auth0Companion.syncAgent(agent);

getAuth0UserId

Get the Auth0 user ID for an agent.
const auth0UserId = await auth0Companion.getAuth0UserId("ag_xxx");

Clerk Companion

Sync agents as Clerk users.

Installation

npm install @agentdoor/clerk

Setup

import { ClerkCompanion } from "@agentdoor/clerk";
import { clerkClient } from "@clerk/nextjs/server";

const clerkCompanion = new ClerkCompanion(
  {
    secretKey: process.env.CLERK_SECRET_KEY!,
    agentMetadataKey: "agentdoor"
  },
  clerkClient
);

const door = createAgentDoor({
  scopes: [...],
  onAgentRegistered: async (agent) => {
    await clerkCompanion.onAgentRegistered(agent);
  }
});

Configuration

interface ClerkCompanionConfig {
  secretKey: string;           // Clerk secret key
  agentMetadataKey?: string;   // Metadata key (default: "agentdoor")
}

Methods

Similar to Auth0 companion:
  • onAgentRegistered(agent) - Create Clerk user for agent
  • onAgentRevoked(agentId) - Delete Clerk user
  • syncAgent(agent) - Update existing Clerk user
  • getClerkUserId(agentId) - Get Clerk user ID

Firebase Companion

Sync agents to Firebase Authentication.

Installation

npm install @agentdoor/firebase
npm install firebase-admin

Setup

import { FirebaseCompanion } from "@agentdoor/firebase";
import admin from "firebase-admin";

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
});

const firebaseCompanion = new FirebaseCompanion(
  {
    agentMetadataKey: "agentdoor"
  },
  admin.auth()
);

const door = createAgentDoor({
  scopes: [...],
  onAgentRegistered: async (agent) => {
    await firebaseCompanion.onAgentRegistered(agent);
  }
});

Configuration

interface FirebaseCompanionConfig {
  agentMetadataKey?: string;   // Custom claims key (default: "agentdoor")
}

How It Works

  1. Creates a Firebase user for each agent
  2. Sets custom claims with agent metadata:
{
  "agentdoor": {
    "is_agent": true,
    "agent_id": "ag_xxx",
    "scopes": ["data.read"]
  }
}
  1. Agent email: {agent_id}@agents.{project_id}.firebaseapp.com

Stytch Companion

Sync agents with Stytch Connected Apps.

Installation

npm install @agentdoor/stytch

Setup

import { StytchCompanion } from "@agentdoor/stytch";

const stytchCompanion = new StytchCompanion(
  {
    projectId: process.env.STYTCH_PROJECT_ID!,
    secret: process.env.STYTCH_SECRET!,
    environment: "live" // or "test"
  },
  stytchClient
);

const door = createAgentDoor({
  scopes: [...],
  onAgentRegistered: async (agent) => {
    await stytchCompanion.onAgentRegistered(agent);
  }
});

Configuration

interface StytchCompanionConfig {
  projectId: string;              // Stytch project ID
  secret: string;                 // Stytch project secret
  environment?: "test" | "live";  // Default: "test"
}

How It Works

  1. Creates a Stytch user for each agent
  2. Stores agent metadata in user’s trusted metadata
  3. Agent users can be queried from Stytch dashboard

Methods

  • onAgentRegistered(agent) - Create Stytch user for agent
  • onAgentRevoked(agentId) - Delete Stytch user
  • syncAgent(agent) - Update existing Stytch user
  • getStytchUserId(agentId) - Get Stytch user ID

Supabase Plugin

Store agent records in Supabase with Row Level Security support.

Installation

npm install @agentdoor/supabase

Setup

import { SupabasePlugin } from "@agentdoor/supabase";
import { createClient } from "@supabase/supabase-js";

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_KEY!
);

const supabasePlugin = new SupabasePlugin(
  {
    supabaseUrl: process.env.SUPABASE_URL!,
    supabaseServiceKey: process.env.SUPABASE_SERVICE_KEY!,
    tableName: "agentdoor_agents", // default
    autoSync: true
  },
  supabase
);

const door = createAgentDoor({
  scopes: [...],
  onAgentRegistered: async (agent) => {
    await supabasePlugin.onAgentRegistered(agent);
  }
});

Configuration

interface SupabasePluginConfig {
  supabaseUrl: string;            // Supabase project URL
  supabaseServiceKey: string;     // Service role key (for admin operations)
  tableName?: string;             // Table name (default: "agentdoor_agents")
  autoSync?: boolean;             // Auto-sync on registration (default: true)
}

Database Schema

The plugin requires a table to store agent records. Run this SQL in Supabase:
-- Create agentdoor_agents table
create table agentdoor_agents (
  id text primary key,
  public_key text not null,
  scopes_granted text[] not null default '{}',
  status text not null default 'active',
  reputation integer not null default 100,
  x402_wallet text,
  metadata jsonb not null default '{}',
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now()
);

-- Row Level Security policies
alter table agentdoor_agents enable row level security;

-- Allow agents to read their own record
create policy "Agents can read own record"
  on agentdoor_agents for select
  using (id = current_setting('request.jwt.claims')::json->>'agent_id');

-- Trigger to update updated_at
create trigger update_agentdoor_agents_updated_at
  before update on agentdoor_agents
  for each row
  execute function update_updated_at_column();

Methods

  • onAgentRegistered(agent) - Create agent record in Supabase
  • onAgentRevoked(agentId) - Delete agent record
  • syncAgent(agent) - Upsert agent record
  • getAgentRecord(agentId) - Query agent from Supabase

Using with RLS

Agents can query their own data from Supabase using their JWT token:
// Agent-side code
const { data, error } = await supabase
  .from('agentdoor_agents')
  .select('*')
  .eq('id', agentId)
  .single();

NextAuth.js Companion

Integrate agents with NextAuth.js sessions.

Installation

npm install @agentdoor/next-auth

Setup

import { NextAuthCompanion } from "@agentdoor/next-auth";
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import { prisma } from "@/lib/prisma";

const nextAuthCompanion = new NextAuthCompanion({
  adapter: PrismaAdapter(prisma),
  agentAccountType: "agent",
});

const door = createAgentDoor({
  scopes: [...],
  onAgentRegistered: async (agent) => {
    await nextAuthCompanion.onAgentRegistered(agent);
  }
});

Configuration

interface NextAuthCompanionConfig {
  adapter: Adapter;          // NextAuth adapter
  agentAccountType?: string; // Account type field value (default: "agent")
}

Custom Companion

Implement a companion for any auth provider:
import type { Agent } from "@agentdoor/core";

export class CustomCompanion {
  async onAgentRegistered(agent: Agent): Promise<void> {
    // Create user in your auth system
    await customAuth.createUser({
      id: agent.id,
      email: `${agent.id}@agents.example.com`,
      metadata: {
        is_agent: true,
        scopes: agent.scopesGranted,
        wallet: agent.x402Wallet,
      }
    });
  }

  async onAgentRevoked(agentId: string): Promise<void> {
    // Delete user from your auth system
    await customAuth.deleteUser(agentId);
  }

  async syncAgent(agent: Agent): Promise<void> {
    // Update user in your auth system
    await customAuth.updateUser(agent.id, {
      metadata: {
        scopes: agent.scopesGranted,
        reputation: agent.reputation,
      }
    });
  }
}

Multi-Provider Setup

Use multiple companions simultaneously:
const auth0Companion = new Auth0Companion(...);
const clerkCompanion = new ClerkCompanion(...);

const door = createAgentDoor({
  scopes: [...],
  
  onAgentRegistered: async (agent) => {
    // Sync to both providers
    await Promise.all([
      auth0Companion.onAgentRegistered(agent),
      clerkCompanion.onAgentRegistered(agent),
    ]);
  },
  
  onAgentAuthenticated: async (agent) => {
    // Update both providers
    await Promise.all([
      auth0Companion.syncAgent(agent),
      clerkCompanion.syncAgent(agent),
    ]);
  }
});

Error Handling

onAgentRegistered: async (agent) => {
  try {
    const result = await auth0Companion.onAgentRegistered(agent);
    
    if (!result.synced) {
      console.error(`Failed to sync agent ${agent.id} to Auth0`);
      // Optionally: Store sync failure for retry
    }
  } catch (error) {
    console.error(`Auth0 sync error:`, error);
    // Don't fail agent registration if sync fails
  }
}

Querying Agents

Auth0

// Search for agents in Auth0
const users = await auth0.getUsers({
  q: 'app_metadata.agentdoor.is_agent:true',
  search_engine: 'v3'
});

Clerk

// Get agent users from Clerk
const users = await clerkClient.users.getUserList({
  limit: 100
});

const agents = users.filter(u => 
  u.publicMetadata?.agentdoor?.is_agent === true
);

Firebase

// List agent users
const listResult = await admin.auth().listUsers();

const agents = listResult.users.filter(u => 
  u.customClaims?.agentdoor?.is_agent === true
);

Stytch

// Query agent users from Stytch
const users = await stytchClient.searchUsers({
  filter: { trusted_metadata: { is_agent: true } }
});

Supabase

// Query all agents
const { data: agents } = await supabase
  .from('agentdoor_agents')
  .select('*')
  .eq('status', 'active');

Best Practices

  1. Don’t block registration: Handle sync failures gracefully without preventing agent registration
  2. Use metadata: Store agent-specific data in provider’s metadata fields
  3. Implement retries: Retry failed syncs in a background job
  4. Monitor sync status: Track sync successes/failures
  5. Cleanup on revoke: Always remove users when agents are revoked
  6. Test sync: Verify agents appear correctly in provider dashboards

Build docs developers (and LLMs) love