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 valueBatch 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
- Always Check Null: User context may be null for anonymous users
- Use Permissions Over Roles: Permissions are more flexible than role checks
- Log User Actions: Maintain audit trail for compliance
- Validate Ownership: Check resource ownership before modifications
- Cache Wisely: Cache user context within request scope, not globally
- Handle Expired Sessions: Check session expiry and handle gracefully
- Use Type Guards: TypeScript guards prevent null reference errors
- Implement RBAC: Use role-based access control consistently
- Document Permissions: Maintain clear documentation of permission model
- Test Both Modes: Test with authenticated and anonymous users
Related Primitives
db- Database operations with user contextsend- Send user-specific eventsai- Personalize AI responseson- Handle user-triggered eventsevery- Schedule user-specific tasks
Next Steps
- Authentication Guide - Setting up authentication
- db Primitive - User-scoped database operations
- Examples - Real-world authentication patterns
- Security Best Practices - Securing your application