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>
5.6 KiB
title, impact, description, tags
| title | impact | description | tags | |||||
|---|---|---|---|---|---|---|---|---|
| User Context and ID Patterns for Applications | HIGH | Critical patterns for user identification, multi-tenancy, and data isolation in production applications |
|
User Context and ID Patterns
Every Composio operation requires a userId parameter for security and data isolation. Users can only access their own connected accounts.
The 'default' User ID
default refers to your project's default account.
Only use 'default' for:
- Testing and development
- Single-user applications
- Internal tools with no external users
Never use in production multi-user apps - it bypasses user isolation.
Production User ID Patterns
Database UUID (Recommended)
Use your database's primary key:
const userId = user.id; // "550e8400-e29b-41d4-a716-446655440000"
await composio.tools.execute('GITHUB_GET_REPO', {
userId: userId,
arguments: { owner: 'example', repo: 'repo' },
});
Pros: Stable, immutable, already exists, no mapping needed
External Auth ID (Acceptable)
Use IDs from Auth0, Firebase, etc:
const userId = user.externalId; // "auth0|507f1f77bcf86cd799439011"
// Or with prefix
const userId = `user_${user.id}`; // "user_12345"
Pros: Works with external auth, human-readable, allows namespacing Cons: May require mapping, usernames can change
Email (Not Recommended)
const userId = user.email; // "user@example.com"
Only use when:
- Email is guaranteed immutable
- No other unique identifier available
- SSO requires email-based identification
Cons: Emails can change, privacy concerns
Organization-Based Applications
For team/org-wide tool access, use organization ID as userId:
// All users in org share same connected accounts
const userId = organization.id; // "org_550e8400..."
await composio.tools.execute('SLACK_SEND_MESSAGE', {
userId: userId, // organization ID, not individual user
arguments: { channel: '#general', text: 'Team message' },
});
Use organization IDs when:
- Team/org tools (Slack, MS Teams, Jira)
- Enterprise apps with IT admin connections
- Shared resources across users
- Role-based access at org level
Example:
// Admin connects Slack for entire org
async function connectOrgToSlack(orgId: string) {
const request = await composio.connectedAccounts.link(orgId, 'slack');
return request.redirectUrl;
}
// Any user in org can use connected tools
async function sendMessage(orgId: string, message: string) {
return await composio.tools.execute('SLACK_SEND_MESSAGE', {
userId: orgId,
arguments: { channel: '#general', text: message },
});
}
// Check org connections
async function listOrgConnections(orgId: string) {
return await composio.connectedAccounts.list({
userIds: [orgId],
});
}
Shared vs. Isolated Connections
Isolated (User-Level)
Each user has their own connections:
await composio.connectedAccounts.link('user_123', 'github_config');
await composio.connectedAccounts.link('user_456', 'github_config');
// Each execution uses that user's account
await composio.tools.execute('GITHUB_GET_REPO', {
userId: 'user_123', // Uses user_123's GitHub
arguments: { ... },
});
Use for: Personal integrations, individual credentials, privacy-critical
Shared (Organization-Level)
All users share organization connections:
await composio.connectedAccounts.link('org_acme', 'github_config');
// All org users use same connection
await composio.tools.execute('GITHUB_GET_REPO', {
userId: 'org_acme', // All users share
arguments: { ... },
});
Use for: Org-wide access, centralized credentials, simplified administration
Security Best Practices
Never Expose User IDs to Frontend
// ❌ DON'T: Allow frontend to specify userId
app.post('/execute-tool', async (req, res) => {
await composio.tools.execute(req.body.tool, {
userId: req.body.userId, // SECURITY RISK
arguments: req.body.arguments,
});
});
// ✅ DO: Derive userId from authenticated session
app.post('/execute-tool', async (req, res) => {
const userId = req.user.id; // From auth session
await composio.tools.execute(req.body.tool, {
userId: userId,
arguments: req.body.arguments,
});
});
Validate User Ownership
async function executeForUser(authenticatedUserId, targetUserId, tool, args) {
if (authenticatedUserId !== targetUserId) {
throw new Error('Unauthorized');
}
return await composio.tools.execute(tool, {
userId: targetUserId,
arguments: args,
});
}
Common Patterns
Express Middleware
app.use((req, res, next) => {
req.userId = req.user.id; // From authenticated session
next();
});
app.post('/execute-tool', async (req, res) => {
const result = await composio.tools.execute(req.body.tool, {
userId: req.userId,
arguments: req.body.arguments,
});
res.json(result);
});
Debug User Context
const accounts = await composio.connectedAccounts.list({
userIds: [userId],
});
console.log(`User ${userId} has ${accounts.items.length} accounts`);
accounts.items.forEach(account => {
console.log(`- ${account.toolkit.slug}: ${account.status}`);
});
Key Points
- Use database UUIDs - Most stable and reliable
- Never expose userId - Always derive from authenticated session
- Validate ownership - Ensure users only access their data
- Use consistent format - Pick one pattern and stick to it
- Organization IDs - For team-wide tool access
- Handle changes gracefully - Maintain mapping if IDs can change