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>
223 lines
5.6 KiB
Markdown
223 lines
5.6 KiB
Markdown
---
|
|
title: User Context and ID Patterns for Applications
|
|
impact: HIGH
|
|
description: Critical patterns for user identification, multi-tenancy, and data isolation in production applications
|
|
tags: [user-context, security, multi-tenancy, isolation, production]
|
|
---
|
|
|
|
# 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:
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```typescript
|
|
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)
|
|
|
|
```typescript
|
|
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`:
|
|
|
|
```typescript
|
|
// 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:**
|
|
|
|
```typescript
|
|
// 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:
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
// ❌ 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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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
|