Files
composio-skills-reference/composio-sdk/rules/tr-userid-best-practices.md
sohamganatra b8b711dff6 Add Composio SDK skill with rules and agent config
Adds composio-sdk/ with SKILL.md, AGENTS.md, and 18 rule files
covering Tool Router, direct execution, triggers, and auth patterns.

Source: composiohq/skills

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-05 22:54:21 -08:00

12 KiB

title, impact, description, tags
title impact description tags
Choose User IDs Carefully for Security and Isolation CRITICAL Use proper user IDs to ensure data isolation, security, and correct session management
tool-router
user-id
security
authentication
multi-tenant

Choose User IDs Carefully for Security and Isolation

User IDs are the foundation of Tool Router isolation. They determine which user's connections, data, and permissions are used for tool execution. Choose them carefully to ensure security and proper data isolation.

Incorrect

// DON'T: Use 'default' in production multi-user apps
async function handleUserRequest(req: Request) {
  const session = await composio.create('default', {
    toolkits: ['gmail', 'slack']
  });

  // ❌ All users share the same session
  // ❌ No data isolation
  // ❌ Security nightmare
  // ❌ User A can access User B's emails!
}
# DON'T: Use 'default' in production multi-user apps
async def handle_user_request(req):
    session = composio.tool_router.create(
        user_id="default",
        toolkits=["gmail", "slack"]
    )

    # ❌ All users share the same session
    # ❌ No data isolation
    # ❌ Security nightmare
    # ❌ User A can access User B's emails!
// DON'T: Use email addresses as user IDs
async function handleUserRequest(req: Request) {
  const session = await composio.create(req.user.email, {
    toolkits: ['github']
  });

  // ❌ Emails can change
  // ❌ Breaks session continuity
  // ❌ Historical data loss
}

Correct - Use Database User IDs

// DO: Use your database user ID (UUID, primary key, etc.)
import { Composio } from '@composio/core';
import { VercelProvider } from '@composio/vercel';

const composio = new Composio({
  provider: new VercelProvider()
});

async function handleUserRequest(req: Request) {
  // Get user ID from your auth system
  const userId = req.user.id; // e.g., "550e8400-e29b-41d4-a716-446655440000"

  // Create isolated session for this user
  const session = await composio.create(userId, {
    toolkits: ['gmail', 'slack']
  });

  const tools = await session.tools();

  // ✅ Each user gets their own session
  // ✅ Complete data isolation
  // ✅ User A cannot access User B's data
  // ✅ Connections tied to correct user
  return await agent.run(req.message, tools);
}
# DO: Use your database user ID (UUID, primary key, etc.)
from composio import Composio
from composio_openai import OpenAIProvider

composio = Composio(provider=OpenAIProvider())

async def handle_user_request(req):
    # Get user ID from your auth system
    user_id = req.user.id  # e.g., "550e8400-e29b-41d4-a716-446655440000"

    # Create isolated session for this user
    session = composio.tool_router.create(
        user_id=user_id,
        toolkits=["gmail", "slack"]
    )

    tools = session.tools()

    # ✅ Each user gets their own session
    # ✅ Complete data isolation
    # ✅ User A cannot access User B's data
    # ✅ Connections tied to correct user
    return await agent.run(req.message, tools)

Correct - Use Auth Provider IDs

// DO: Use IDs from your auth provider
import { Composio } from '@composio/core';
import { VercelProvider } from '@composio/vercel';

const composio = new Composio({
  provider: new VercelProvider()
});

async function handleClerkUser(userId: string) {
  // Using Clerk user ID
  // e.g., "user_2abc123def456"
  const session = await composio.create(userId, {
    toolkits: ['github']
  });

  return session;
}

async function handleAuth0User(userId: string) {
  // Using Auth0 user ID
  // e.g., "auth0|507f1f77bcf86cd799439011"
  const session = await composio.create(userId, {
    toolkits: ['gmail']
  });

  return session;
}

async function handleSupabaseUser(userId: string) {
  // Using Supabase user UUID
  // e.g., "d7f8b0c1-1234-5678-9abc-def012345678"
  const session = await composio.create(userId, {
    toolkits: ['slack']
  });

  return session;
}
# DO: Use IDs from your auth provider
from composio import Composio
from composio_openai import OpenAIProvider

composio = Composio(provider=OpenAIProvider())

async def handle_clerk_user(user_id: str):
    # Using Clerk user ID
    # e.g., "user_2abc123def456"
    session = composio.tool_router.create(
        user_id=user_id,
        toolkits=["github"]
    )
    return session

async def handle_auth0_user(user_id: str):
    # Using Auth0 user ID
    # e.g., "auth0|507f1f77bcf86cd799439011"
    session = composio.tool_router.create(
        user_id=user_id,
        toolkits=["gmail"]
    )
    return session

async def handle_supabase_user(user_id: str):
    # Using Supabase user UUID
    # e.g., "d7f8b0c1-1234-5678-9abc-def012345678"
    session = composio.tool_router.create(
        user_id=user_id,
        toolkits=["slack"]
    )
    return session

Correct - Organization-Level Applications

// DO: Use organization ID for org-level apps
import { Composio } from '@composio/core';
import { VercelProvider } from '@composio/vercel';

const composio = new Composio({
  provider: new VercelProvider()
});

// When apps are connected at organization level (not individual users)
async function handleOrgLevelApp(req: Request) {
  // Use organization ID, NOT individual user ID
  const organizationId = req.user.organizationId;

  const session = await composio.create(organizationId, {
    toolkits: ['slack', 'github'], // Org-wide tools
    manageConnections: true
  });

  // All users in the organization share these connections
  // Perfect for team collaboration tools
  const tools = await session.tools();
  return await agent.run(req.message, tools);
}

// Example: Slack workspace integration
async function createWorkspaceSession(workspaceId: string) {
  // Workspace ID as user ID
  const session = await composio.create(`workspace_${workspaceId}`, {
    toolkits: ['slack', 'notion', 'linear']
  });

  return session;
}
# DO: Use organization ID for org-level apps
from composio import Composio
from composio_openai import OpenAIProvider

composio = Composio(provider=OpenAIProvider())

# When apps are connected at organization level (not individual users)
async def handle_org_level_app(req):
    # Use organization ID, NOT individual user ID
    organization_id = req.user.organization_id

    session = composio.tool_router.create(
        user_id=organization_id,
        toolkits=["slack", "github"],  # Org-wide tools
        manage_connections=True
    )

    # All users in the organization share these connections
    # Perfect for team collaboration tools
    tools = session.tools()
    return await agent.run(req.message, tools)

# Example: Slack workspace integration
async def create_workspace_session(workspace_id: str):
    # Workspace ID as user ID
    session = composio.tool_router.create(
        user_id=f"workspace_{workspace_id}",
        toolkits=["slack", "notion", "linear"]
    )
    return session

When to Use 'default'

The 'default' user ID should ONLY be used in these scenarios:

Development and Testing

// Testing locally
const session = await composio.create('default', {
  toolkits: ['gmail']
});

Single-User Applications

// Personal automation script
// Only YOU use this app
const session = await composio.create('default', {
  toolkits: ['github', 'notion']
});

Demos and Prototypes

// Quick demo for investors
const session = await composio.create('default', {
  toolkits: ['hackernews']
});

NEVER in Production Multi-User Apps

// Production API serving multiple users
// ❌ DON'T DO THIS
const session = await composio.create('default', {
  toolkits: ['gmail']
});

User ID Best Practices

1. Use Stable, Immutable Identifiers

Good:

  • Database primary keys (UUIDs)
  • Auth provider user IDs
  • Immutable user identifiers

Bad:

  • Email addresses (can change)
  • Usernames (can be modified)
  • Phone numbers (can change)
// ✅ Good: Stable UUID
const userId = user.id; // "550e8400-e29b-41d4-a716-446655440000"

// ❌ Bad: Email (mutable)
const userId = user.email; // "john@example.com" -> changes to "john@newdomain.com"

// ❌ Bad: Username (mutable)
const userId = user.username; // "john_doe" -> changes to "john_smith"

2. Ensure Uniqueness

// ✅ Good: Guaranteed unique
const userId = database.users.findById(id).id;

// ✅ Good: Auth provider guarantees uniqueness
const userId = auth0.user.sub; // "auth0|507f1f77bcf86cd799439011"

// ❌ Bad: Not guaranteed unique
const userId = user.firstName; // Multiple "John"s exist

3. Match Your Authentication System

// Express.js with Passport
app.post('/api/agent', authenticateUser, async (req, res) => {
  const userId = req.user.id; // From Passport
  const session = await composio.create(userId, config);
});

// Next.js with Clerk
export async function POST(req: NextRequest) {
  const { userId } = auth(); // From Clerk
  const session = await composio.create(userId!, config);
}

// FastAPI with Auth0
@app.post("/api/agent")
async def agent_endpoint(user: User = Depends(get_current_user)):
    user_id = user.id  # From Auth0
    session = composio.tool_router.create(user_id=user_id, **config)

4. Namespace for Multi-Tenancy

// When you have multiple applications/workspaces per user
const userId = `app_${appId}_user_${user.id}`;
// e.g., "app_saas123_user_550e8400"

const session = await composio.create(userId, {
  toolkits: ['gmail']
});

// Each app instance gets isolated connections

5. Be Consistent Across Your Application

// ✅ Good: Same user ID everywhere
async function handleRequest(req: Request) {
  const userId = req.user.id;

  // Use same ID for Tool Router
  const session = await composio.create(userId, config);

  // Use same ID for direct tool execution
  await composio.tools.execute('GMAIL_SEND_EMAIL', {
    userId: userId,
    arguments: { to: 'user@example.com', subject: 'Test' }
  });

  // Use same ID for connected accounts
  await composio.connectedAccounts.get(userId, 'gmail');
}

Security Implications

⚠️ User ID Leakage

// ❌ DON'T: Expose user IDs to client
app.get('/api/session', (req, res) => {
  res.json({
    sessionId: session.sessionId,
    userId: req.user.id // ❌ Sensitive information
  });
});

// ✅ DO: Keep user IDs server-side only
app.get('/api/session', (req, res) => {
  res.json({
    sessionId: session.sessionId
    // Don't send userId to client
  });
});

⚠️ User ID Validation

// ✅ Always validate user IDs match authenticated user
app.post('/api/agent/:userId', authenticateUser, async (req, res) => {
  const requestedUserId = req.params.userId;
  const authenticatedUserId = req.user.id;

  // Validate user can only access their own data
  if (requestedUserId !== authenticatedUserId) {
    return res.status(403).json({ error: 'Forbidden' });
  }

  const session = await composio.create(authenticatedUserId, config);
});

Common Patterns

Pattern 1: User-Level Isolation (Most Common)

// Each user has their own connections
// Use user ID from your database/auth system
const session = await composio.create(req.user.id, {
  toolkits: ['gmail', 'github']
});

Pattern 2: Organization-Level Sharing

// All org members share connections
// Use organization ID
const session = await composio.create(req.user.organizationId, {
  toolkits: ['slack', 'notion']
});

Pattern 3: Hybrid (User + Org)

// Personal tools use user ID
const personalSession = await composio.create(req.user.id, {
  toolkits: ['gmail'] // Personal Gmail
});

// Team tools use org ID
const teamSession = await composio.create(req.user.organizationId, {
  toolkits: ['slack', 'jira'] // Team Slack/Jira
});

Key Principles

  1. Never use 'default' in production multi-user apps
  2. Use stable, immutable identifiers (UUIDs, not emails)
  3. Match your authentication system's user IDs
  4. Validate user IDs server-side for security
  5. Be consistent across Tool Router and direct tool usage
  6. Use org IDs for organization-level applications
  7. Namespace when needed for multi-tenancy

Reference