.do

user - User Context and Authentication

Complete guide to accessing user information, session management, and permission checks in MCP.do

user - User Context and Authentication

The user primitive provides access to the current user's authentication state, session information, and permissions. It's essential for building secure, multi-tenant applications with role-based access control.

Overview

The user context primitive enables applications to adapt behavior based on who is using the system. It provides three core capabilities: retrieving the current user's profile, accessing session metadata, and checking permissions for specific actions.

Unlike traditional authentication systems that require manual token validation and database lookups, the user primitive provides a unified interface that abstracts away the complexity of authentication and authorization.

Key Features

  • Current User Access: Get authenticated user profile and metadata
  • Session Management: Access session information and expiration
  • Permission Checks: Verify user permissions before operations
  • Graceful Degradation: Safely returns null for anonymous users
  • Type Safety: Full TypeScript support for user and session types
  • Zero Configuration: Works automatically with MCP.do authentication
  • Performance: Cached for fast access during request lifecycle

Type Signatures

interface UserOps {
  // Get current authenticated user
  current(): Promise<User | null>

  // Get current session
  session(): Promise<Session | null>

  // Get user permissions
  permissions(): Promise<string[]>
}

interface User {
  // Unique user identifier
  id: string

  // User email address
  email: string

  // Optional display name
  name?: string

  // User role (e.g., 'admin', 'user', 'guest')
  role: string

  // User permissions array
  permissions: string[]

  // Additional user metadata
  metadata?: Record<string, unknown>
}

interface Session {
  // Unique session identifier
  id: string

  // Associated user ID
  userId: string

  // Session expiration timestamp
  expiresAt: Date

  // Session creation timestamp
  createdAt: Date

  // Additional session metadata
  metadata?: Record<string, unknown>
}

Basic Usage

Get Current User

// Check if user is authenticated
const currentUser = await user.current()

if (currentUser) {
  console.log('User ID:', currentUser.id)
  console.log('Email:', currentUser.email)
  console.log('Name:', currentUser.name)
  console.log('Role:', currentUser.role)
} else {
  console.log('User is not authenticated')
}

Get Session Information

// Access session metadata
const session = await user.session()

if (session) {
  console.log('Session ID:', session.id)
  console.log('Expires:', session.expiresAt)
  console.log('Created:', session.createdAt)

  // Check if session is expiring soon
  const now = new Date()
  const expiresIn = session.expiresAt.getTime() - now.getTime()
  const hoursUntilExpiry = expiresIn / (1000 * 60 * 60)

  if (hoursUntilExpiry < 24) {
    console.log(`Session expires in ${hoursUntilExpiry.toFixed(1)} hours`)
  }
}

Check Permissions

// Get all user permissions
const permissions = await user.permissions()
console.log('User permissions:', permissions)

// Check specific permissions
const hasPermissions = permissions.includes('admin:write')

if (hasPermissions) {
  console.log('User has admin write access')
}

Advanced Examples

1. Role-Based Access Control

Implement role-based access patterns:

// Check user role before allowing access
const currentUser = await user.current()

if (!currentUser) {
  throw new Error('Authentication required')
}

// Admin-only operation
if (currentUser.role === 'admin') {
  db.Systems.update('config', {
    maintenance: true,
  })
  console.log('System maintenance enabled')
} else {
  throw new Error('Admin access required')
}

// Role hierarchy
const roleHierarchy = {
  guest: 0,
  user: 1,
  moderator: 2,
  admin: 3,
  superadmin: 4,
}

function hasMinimumRole(requiredRole: string): boolean {
  const userLevel = roleHierarchy[currentUser.role] || 0
  const requiredLevel = roleHierarchy[requiredRole] || 0
  return userLevel >= requiredLevel
}

if (hasMinimumRole('moderator')) {
  // Moderator-level operations
  db.Comments.delete(commentId)
}

2. Permission-Based Data Access

Filter data based on user permissions:

const currentUser = await user.current()

if (!currentUser) {
  // Anonymous user - show public data only
  return db.Orders.list({
    where: { visibility: 'public' },
  })
}

// Authenticated user - show their orders
const permissions = await user.permissions()

if (permissions.includes('admin:read')) {
  // Admin can see all orders
  return db.Orders.list()
} else {
  // Regular user sees only their orders
  return db.Orders.list({
    where: { customerId: currentUser.id },
  })
}

3. User-Specific Event Handlers

Customize event handling per user:

on.Order.created(async (order) => {
  const currentUser = await user.current()

  if (!currentUser) {
    console.log('Anonymous order created')
    return
  }

  // Send personalized notification
  send.Email({
    to: currentUser.email,
    subject: `Order Confirmation #${order.id}`,
    template: 'order-confirmation',
    data: {
      userName: currentUser.name || currentUser.email,
      order,
      loyaltyPoints: currentUser.metadata?.loyaltyPoints || 0,
    },
  })

  // Update user metadata
  db.Users.update(currentUser.id, {
    metadata: {
      ...currentUser.metadata,
      lastOrderAt: new Date(),
      totalOrders: (currentUser.metadata?.totalOrders || 0) + 1,
    },
  })
})

4. Session Expiry Warnings

Notify users of approaching session expiration:

// Check session status
const session = await user.session()
const currentUser = await user.current()

if (!session || !currentUser) {
  console.log('No active session')
  return
}

const now = new Date()
const expiresIn = session.expiresAt.getTime() - now.getTime()
const minutesUntilExpiry = expiresIn / (1000 * 60)

// Warn if session expires in less than 15 minutes
if (minutesUntilExpiry < 15 && minutesUntilExpiry > 0) {
  send.Notification({
    userId: currentUser.id,
    type: 'session_expiring',
    message: `Your session expires in ${Math.floor(minutesUntilExpiry)} minutes`,
    action: {
      label: 'Extend Session',
      url: '/auth/refresh',
    },
  })
}

// Session has expired
if (expiresIn < 0) {
  console.log('Session has expired')
  // Redirect to login or refresh token
}

5. Audit Logging

Track user actions for compliance:

// Log user action
async function logUserAction(action: string, resource: string, details?: any) {
  const currentUser = await user.current()
  const session = await user.session()

  db.AuditLogs.create({
    userId: currentUser?.id || 'anonymous',
    userEmail: currentUser?.email,
    sessionId: session?.id,
    action,
    resource,
    details,
    timestamp: new Date(),
    ipAddress: session?.metadata?.ipAddress,
    userAgent: session?.metadata?.userAgent,
  })
}

// Usage in application code
await logUserAction('delete', 'Order', { orderId: 'ord_123' })
db.Orders.delete('ord_123')

// Query audit logs
const currentUser = await user.current()
const permissions = await user.permissions()

if (permissions.includes('audit:read')) {
  db.AuditLogs.list({
    where: {
      action: 'delete',
      timestamp: { gte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) },
    },
    sort: { timestamp: 'desc' },
    limit: 100,
  }).then((logs) => {
    console.log(`Found ${logs.length} delete actions in past 7 days`)
  })
}

6. Multi-Tenant Data Isolation

Ensure data isolation between tenants:

// Get user's organization
const currentUser = await user.current()

if (!currentUser) {
  throw new Error('Authentication required')
}

const organizationId = currentUser.metadata?.organizationId

if (!organizationId) {
  throw new Error('User not associated with organization')
}

// All database queries scoped to organization
const customers = db.Customers.list({
  where: { organizationId },
})

const orders = db.Orders.list({
  where: { organizationId },
})

// Prevent cross-tenant access
async function validateTenantAccess(resourceId: string, resourceType: string) {
  const resource = await db.get(resourceType, resourceId)

  if (!resource) {
    throw new Error('Resource not found')
  }

  if (resource.organizationId !== organizationId) {
    throw new Error('Access denied: Resource belongs to different organization')
  }

  return resource
}

// Usage
await validateTenantAccess('ord_123', 'Order')
db.Orders.update('ord_123', { status: 'shipped' })

7. User Preferences and Settings

Store and retrieve user preferences:

// Get user preferences
const currentUser = await user.current()

if (!currentUser) {
  // Use default preferences for anonymous users
  return {
    theme: 'light',
    language: 'en',
    notifications: false,
  }
}

// Get preferences from user metadata
const preferences = currentUser.metadata?.preferences || {
  theme: 'light',
  language: 'en',
  notifications: true,
  emailDigest: 'daily',
}

// Update user preferences
async function updatePreferences(updates: any) {
  const currentUser = await user.current()

  if (!currentUser) {
    throw new Error('Authentication required')
  }

  const currentPreferences = currentUser.metadata?.preferences || {}

  db.Users.update(currentUser.id, {
    metadata: {
      ...currentUser.metadata,
      preferences: {
        ...currentPreferences,
        ...updates,
      },
    },
  })
}

// Usage
await updatePreferences({
  theme: 'dark',
  language: 'es',
})

8. Conditional AI Generation

Customize AI responses based on user context:

const currentUser = await user.current()

// Generate content appropriate for user
const content = await ai.generate({
  prompt: `Write a product description for a smart coffee maker.
${currentUser ? `User context: ${currentUser.role}, ${currentUser.metadata?.experienceLevel}` : 'Anonymous user'}`,
  model: 'gpt-5',
})

// Personalized recommendations
if (currentUser) {
  db.Orders.list({
    where: { customerId: currentUser.id },
  }).then((orderHistory) => {
    ai.generate({
      prompt: `Based on this purchase history: ${JSON.stringify(orderHistory)}
Recommend 5 products for ${currentUser.name}.`,
      schema: $.ProductRecommendation,
      model: 'claude-sonnet-4.5',
    }).then((recommendations) => {
      console.log('Personalized recommendations:', recommendations.data)
    })
  })
}

9. Rate Limiting Per User

Implement user-based rate limiting:

const currentUser = await user.current()

if (!currentUser) {
  throw new Error('Authentication required for this operation')
}

// Check rate limit
const userId = currentUser.id
const rateLimitKey = `ratelimit:${userId}:api_calls`

const rateLimitData = await db.get('RateLimit', rateLimitKey)

const now = Date.now()
const windowMs = 60 * 60 * 1000 // 1 hour
const maxRequests = currentUser.role === 'premium' ? 1000 : 100

if (rateLimitData) {
  // Check if within window
  if (now - rateLimitData.windowStart < windowMs) {
    if (rateLimitData.count >= maxRequests) {
      throw new Error(`Rate limit exceeded. Max ${maxRequests} requests per hour.`)
    }

    // Increment count
    await db.update('RateLimit', rateLimitKey, {
      count: rateLimitData.count + 1,
    })
  } else {
    // New window
    await db.update('RateLimit', rateLimitKey, {
      windowStart: now,
      count: 1,
    })
  }
} else {
  // Create rate limit entry
  await db.create('RateLimit', {
    id: rateLimitKey,
    userId,
    windowStart: now,
    count: 1,
  })
}

// Proceed with operation
console.log('Rate limit check passed')

10. User Activity Tracking

Track and analyze user activity:

// Track page view
const currentUser = await user.current()
const session = await user.session()

await db.create('PageView', {
  userId: currentUser?.id,
  sessionId: session?.id,
  path: '/products',
  timestamp: new Date(),
  referrer: session?.metadata?.referrer,
  userAgent: session?.metadata?.userAgent,
})

// Analyze user engagement
if (currentUser) {
  const recentActivity = await db.list('PageView', {
    where: {
      userId: currentUser.id,
      timestamp: { gte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) },
    },
  })

  const engagement = {
    pageViews: recentActivity.length,
    uniquePages: new Set(recentActivity.map((pv) => pv.path)).size,
    avgSessionDuration: calculateAvgSessionDuration(recentActivity),
    lastActive: recentActivity[0]?.timestamp,
  }

  // Update user metadata with engagement metrics
  await db.update('User', currentUser.id, {
    metadata: {
      ...currentUser.metadata,
      engagement,
      lastActivityAt: new Date(),
    },
  })
}

11. Permission-Based API Access

Control API access with granular permissions:

// Check permission before API call
const permissions = await user.permissions()

if (!permissions.includes('api:external:read')) {
  throw new Error('Permission denied: external API access requires api:external:read permission')
}

// Make external API call
const response = await api.fetch('https://external-api.com/data', {
  method: 'GET',
  headers: {
    Authorization: 'Bearer ...',
  },
})

// Write operations require additional permission
if (permissions.includes('api:external:write')) {
  await api.fetch('https://external-api.com/data', {
    method: 'POST',
    body: JSON.stringify({ data: 'value' }),
  })
} else {
  console.log('Write access denied')
}

12. User Impersonation (Admin)

Allow admins to impersonate users for support:

// Admin impersonation
const currentUser = await user.current()
const permissions = await user.permissions()

if (!permissions.includes('admin:impersonate')) {
  throw new Error('Permission denied: impersonation requires admin:impersonate permission')
}

async function impersonateUser(targetUserId: string) {
  // Verify admin permission
  if (!permissions.includes('admin:impersonate')) {
    throw new Error('Permission denied')
  }

  // Log impersonation for audit
  await db.create('ImpersonationLog', {
    adminId: currentUser.id,
    targetUserId,
    startedAt: new Date(),
  })

  // Create impersonation context
  // (This would typically set a temporary session token)
  console.log(`Admin ${currentUser.id} impersonating user ${targetUserId}`)

  // Perform operations as target user
  const targetUser = await db.get('User', targetUserId)
  return targetUser
}

// Usage
const targetUser = await impersonateUser('user_123')

Conditional Logic Patterns

Anonymous vs Authenticated

const currentUser = await user.current()

if (currentUser) {
  // Authenticated user path
  console.log('Welcome back,', currentUser.name)

  db.Orders.list({
    where: { customerId: currentUser.id },
  }).then((orders) => {
    console.log(`You have ${orders.length} orders`)
  })
} else {
  // Anonymous user path
  console.log('Welcome, guest')
  console.log('Sign in to view your orders')
}

Role-Based Routing

const currentUser = await user.current()

if (!currentUser) {
  // Redirect to login
  throw new Error('Authentication required')
}

switch (currentUser.role) {
  case 'admin':
    // Admin dashboard
    return await loadAdminDashboard()

  case 'moderator':
    // Moderator tools
    return await loadModeratorTools()

  case 'user':
    // User profile
    return await loadUserProfile(currentUser.id)

  default:
    throw new Error('Unknown role')
}

Permission-Based Features

const permissions = await user.permissions()

const features = {
  canExportData: permissions.includes('export:data'),
  canManageUsers: permissions.includes('admin:users'),
  canAccessAnalytics: permissions.includes('analytics:read'),
  canEditContent: permissions.includes('content:write'),
  canDeleteComments: permissions.includes('moderation:delete'),
}

// Enable features conditionally
if (features.canExportData) {
  console.log('Export feature enabled')
}

if (features.canManageUsers) {
  console.log('User management enabled')
}

Integration Patterns

With Database Operations

const currentUser = await user.current()

if (!currentUser) {
  throw new Error('Authentication required')
}

// Create with user context
db.Posts.create({
  title: 'My First Post',
  content: 'Hello World',
  authorId: currentUser.id,
  authorEmail: currentUser.email,
})

// Update with ownership check
db.Posts.get(postId).then((post) => {
  if (post.authorId !== currentUser.id) {
    user.permissions().then((permissions) => {
      if (!permissions.includes('admin:write')) {
        throw new Error('Permission denied: You can only edit your own posts')
      }
    })
  }

  db.Posts.update(postId, {
    title: 'Updated Title',
  })
})

With Event System

on.Comment.created(async (comment) => {
  const currentUser = await user.current()

  // Author gets notification
  if (currentUser && comment.authorId === currentUser.id) {
    send.Notification({
      userId: currentUser.id,
      type: 'comment_posted',
      message: 'Your comment has been posted',
    })
  }

  // Post author gets notification
  db.Posts.get(comment.postId).then((post) => {
    if (post.authorId !== comment.authorId) {
      send.Notification({
        userId: post.authorId,
        type: 'new_comment',
        message: `${currentUser?.name || 'Someone'} commented on your post`,
      })
    }
  })
})

With AI Operations

const currentUser = await user.current()
const permissions = await user.permissions()

// Only premium users or admins can use advanced models
let model = 'gpt-5'

if (currentUser?.role === 'premium' || permissions.includes('ai:advanced')) {
  model = 'claude-sonnet-4.5'
}

const result = await ai.generate({
  prompt: 'Analyze this business plan',
  model,
  schema: $.BusinessAnalysis,
})

// Track AI usage per user
if (currentUser) {
  db.AIUsages.create({
    userId: currentUser.id,
    model,
    tokens: result.usage?.totalTokens || 0,
    timestamp: new Date(),
  })
}

With Scheduled Tasks

every('0 9 * * *', async () => {
  // Send personalized emails to all users
  db.Users.list({
    where: { emailNotifications: true },
  }).then((users) => {
    for (const user of users) {
      // Get user-specific data
      db.Orders.list({
        where: {
          customerId: user.id,
          createdAt: { gte: new Date(Date.now() - 24 * 60 * 60 * 1000) },
        },
      }).then((orders) => {
        if (orders.length > 0) {
          send.Email({
            to: user.email,
            subject: 'Your Daily Order Summary',
            template: 'daily-summary',
            data: {
              userName: user.name,
              orders,
              preferences: user.metadata?.preferences,
            },
          })
        }
      })
    }
  })
})

Security Considerations

Always Validate User Context

// Bad: Assuming user is authenticated
const currentUser = await user.current()
db.Users.update(currentUser.id, { ... }) // Error if null!

// Good: Check before using
const currentUser = await user.current()

if (!currentUser) {
  throw new Error('Authentication required')
}

db.Users.update(currentUser.id, { ... })

Check Ownership

// Verify resource ownership before modification
const currentUser = await user.current()

if (!currentUser) {
  throw new Error('Authentication required')
}

const resource = await db.get('Resource', resourceId)

if (!resource) {
  throw new Error('Resource not found')
}

if (resource.ownerId !== currentUser.id) {
  const permissions = await user.permissions()

  if (!permissions.includes('admin:write')) {
    throw new Error('Permission denied')
  }
}

// Safe to proceed
db.Resources.update(resourceId, { ... })

Validate Permissions

// Check permissions before sensitive operations
const permissions = await user.permissions()

const requiredPermission = 'billing:refund'

if (!permissions.includes(requiredPermission)) {
  throw new Error(`Permission denied: ${requiredPermission} required`)
}

// Safe to process refund
await processRefund(orderId)

Readonly vs Authenticated Mode

The user primitive works in both modes but returns different data:

Readonly Mode (Anonymous)

// Without authentication
const currentUser = await user.current() // null
const session = await user.session() // null
const permissions = await user.permissions() // []

// All operations return safe defaults
if (currentUser) {
  console.log('Authenticated')
} else {
  console.log('Anonymous') // This path executes
}

Authenticated Mode

// With API key
configure({
  apiUrl: 'https://api.do',
  apiKey: 'sk_...',
})

const currentUser = await user.current() // User object
const session = await user.session() // Session object
const permissions = await user.permissions() // ['read', 'write', ...]

console.log('User:', currentUser.email)
console.log('Permissions:', permissions)

Error Handling

Handle Missing User

try {
  const currentUser = await user.current()

  if (!currentUser) {
    return { error: 'Authentication required', statusCode: 401 }
  }

  // Proceed with authenticated operations
} catch (error) {
  console.error('Failed to get current user:', error)
  return { error: 'Internal error', statusCode: 500 }
}

Handle Expired Sessions

const session = await user.session()

if (session) {
  const now = new Date()

  if (session.expiresAt < now) {
    return {
      error: 'Session expired',
      statusCode: 401,
      action: 'refresh_token',
    }
  }
}

Handle Permission Errors

const permissions = await user.permissions()

function requirePermission(permission: string) {
  if (!permissions.includes(permission)) {
    throw new Error(`Permission denied: ${permission} required`)
  }
}

try {
  requirePermission('admin:delete')
  await db.delete('User', userId)
} catch (error) {
  console.error('Permission check failed:', error)
  return { error: error.message, statusCode: 403 }
}

Performance Considerations

Caching User Context

// Cache user context during request lifecycle
let cachedUser: User | null = null

async function getCurrentUser(): Promise<User | null> {
  if (cachedUser !== null) {
    return cachedUser
  }

  cachedUser = await user.current()
  return cachedUser
}

// Use cached version
const user1 = await getCurrentUser() // Fetches from API
const user2 = await getCurrentUser() // Returns cached value

Batch Permission Checks

// Check multiple permissions at once
const permissions = await user.permissions()

const permissionMap = {
  canRead: permissions.includes('read'),
  canWrite: permissions.includes('write'),
  canDelete: permissions.includes('delete'),
  canAdmin: permissions.includes('admin')
}

// Use permission map for multiple checks
if (permissionMap.canWrite) {
  await db.create('Post', { ... })
}

if (permissionMap.canDelete) {
  await db.delete('Post', postId)
}

Common Pitfalls

1. Not Checking for Null

// Wrong: Assuming user exists
const currentUser = await user.current()
console.log(currentUser.email) // TypeError if null!

// Correct: Check first
const currentUser = await user.current()
if (currentUser) {
  console.log(currentUser.email)
}

2. Using String Role Checks

// Wrong: Hard-coded role strings
if (currentUser.role === 'admin') { ... }

// Better: Use permission system
const permissions = await user.permissions()
if (permissions.includes('admin:write')) { ... }

3. Forgetting Async/Await

// Wrong: Not awaiting promise
const currentUser = user.current() // Promise<User | null>
console.log(currentUser.email) // TypeError!

// Correct: Await the promise
const currentUser = await user.current()
if (currentUser) {
  console.log(currentUser.email)
}

4. Checking Role Instead of Permissions

// Wrong: Role-based logic is brittle
if (currentUser.role === 'moderator' || currentUser.role === 'admin') {
  await deleteComment()
}

// Correct: Permission-based logic is flexible
const permissions = await user.permissions()
if (permissions.includes('moderation:delete')) {
  await deleteComment()
}

5. Not Logging User Actions

// Wrong: No audit trail
db.Orders.delete(orderId)

// Correct: Log user actions
const currentUser = await user.current()
db.AuditLogs.create({
  userId: currentUser.id,
  action: 'delete',
  resource: 'Order',
  resourceId: orderId,
  timestamp: new Date(),
})
db.Orders.delete(orderId)

Best Practices

  1. Always Check Null: User context may be null for anonymous users
  2. Use Permissions Over Roles: Permissions are more flexible than role checks
  3. Log User Actions: Maintain audit trail for compliance
  4. Validate Ownership: Check resource ownership before modifications
  5. Cache Wisely: Cache user context within request scope, not globally
  6. Handle Expired Sessions: Check session expiry and handle gracefully
  7. Use Type Guards: TypeScript guards prevent null reference errors
  8. Implement RBAC: Use role-based access control consistently
  9. Document Permissions: Maintain clear documentation of permission model
  10. Test Both Modes: Test with authenticated and anonymous users
  • db - Database operations with user context
  • send - Send user-specific events
  • ai - Personalize AI responses
  • on - Handle user-triggered events
  • every - Schedule user-specific tasks

Next Steps