Skip to content

Auth Middleware ​

Auth Middleware provides flexible route protection patterns for your Flowfull backend. Protect your API routes with minimal code.

Overview ​

Flowfull provides middleware patterns for different authentication scenarios:

  • requireAuth() - Require authenticated user
  • optionalAuth() - Optional authentication
  • requireUserType() - Require specific user type
  • requirePermission() - Require specific permission

Basic Usage ​

Require Authentication ​

typescript
import { Hono } from 'hono';
import { requireAuth } from './lib/auth/middleware';

const app = new Hono();

// Protected route - requires authentication
app.get('/api/profile', requireAuth(), async (c) => {
  const userId = c.get('user_id');
  const email = c.get('email');
  
  return c.json({ userId, email });
});

Optional Authentication ​

typescript
// Public route with optional auth
app.get('/api/posts', optionalAuth(), async (c) => {
  const userId = c.get('user_id');  // May be undefined
  
  if (userId) {
    // Show personalized posts
    return c.json({ posts: await getPersonalizedPosts(userId) });
  } else {
    // Show public posts
    return c.json({ posts: await getPublicPosts() });
  }
});

Require User Type ​

typescript
// Single user type - Admin only
app.get('/api/admin/users', requireUserType('admin'), async (c) => {
  const users = await db.selectFrom('users').selectAll().execute();
  return c.json({ users });
});

// Single user type - Teacher only
app.get('/api/teacher/classes', requireUserType('teacher'), async (c) => {
  const classes = await getTeacherClasses(c.get('user_id'));
  return c.json({ classes });
});

// Multiple user types - Admin OR Manager
app.get('/api/manager/dashboard', requireUserType(['admin', 'manager']), async (c) => {
  const userId = c.get('user_id');
  const userType = c.get('session').user_type;

  return c.json({
    dashboard: await getDashboard(userId, userType)
  });
});

// Custom user types - Your own business logic
app.get('/api/premium/content', requireUserType(['premium', 'vip', 'admin']), async (c) => {
  return c.json({ content: await getPremiumContent() });
});

Require Permission ​

typescript
// Require specific permission
app.delete('/api/posts/:id', requirePermission('posts.delete'), async (c) => {
  const postId = c.req.param('id');
  await db.deleteFrom('posts').where('id', '=', postId).execute();
  return c.json({ success: true });
});

Middleware Patterns ​

Pattern 1: Public Routes ​

typescript
// No authentication required
app.get('/api/public/stats', async (c) => {
  return c.json({ stats: await getPublicStats() });
});

Pattern 2: Protected Routes ​

typescript
// Authentication required
app.get('/api/profile', requireAuth(), async (c) => {
  const userId = c.get('user_id');
  return c.json({ profile: await getProfile(userId) });
});

Pattern 3: Conditional Routes ​

typescript
// Different behavior based on auth
app.get('/api/content', optionalAuth(), async (c) => {
  const userId = c.get('user_id');

  if (userId) {
    return c.json({ content: await getPremiumContent(userId) });
  } else {
    return c.json({ content: await getFreeContent() });
  }
});

Pattern 4: Role-Based Routes (Simple) ​

typescript
// Use requireUserType for simple role checks
app.get('/api/admin/dashboard', requireUserType('admin'), async (c) => {
  return c.json({ dashboard: await getAdminDashboard() });
});

app.get('/api/teacher/dashboard', requireUserType('teacher'), async (c) => {
  return c.json({ dashboard: await getTeacherDashboard() });
});

app.get('/api/student/dashboard', requireUserType('student'), async (c) => {
  return c.json({ dashboard: await getStudentDashboard() });
});

Pattern 5: Role-Based Routes (Dynamic) ​

typescript
// Different behavior for different roles in same route
app.get('/api/dashboard', requireAuth(), async (c) => {
  const session = c.get('session');
  const userType = session.user_type;

  switch (userType) {
    case 'admin':
      return c.json({ dashboard: await getAdminDashboard() });
    case 'teacher':
      return c.json({ dashboard: await getTeacherDashboard() });
    case 'student':
      return c.json({ dashboard: await getStudentDashboard() });
    default:
      return c.json({ error: 'Invalid user type' }, 403);
  }
});

Pattern 6: Multiple User Types ​

typescript
// Allow multiple user types to access the same route
app.get('/api/management/reports', requireUserType(['admin', 'manager', 'supervisor']), async (c) => {
  const session = c.get('session');
  const userId = c.get('user_id');

  // All three user types can access, but may see different data
  const reports = await getReports(userId, session.user_type);
  return c.json({ reports });
});

Flexible User Type Management ​

Why requireUserType is Better Than requireAuth ​

The requireUserType middleware is more flexible than just using requireAuth because:

  1. Single or Multiple Types: Accept one user type or an array of types
  2. Custom User Types: Works with ANY user type you define (not just admin/user)
  3. Clear Error Messages: Shows exactly what types are allowed
  4. Type Safety: Validates user types at the middleware level

Common Use Cases ​

typescript
// 1. Admin-only routes
app.delete('/api/users/:id', requireUserType('admin'), async (c) => {
  // Only admins can delete users
});

// 2. Manager routes (admin OR manager)
app.get('/api/reports', requireUserType(['admin', 'manager']), async (c) => {
  // Both admins and managers can view reports
});

// 3. Custom business roles
app.post('/api/inventory', requireUserType(['warehouse_manager', 'admin']), async (c) => {
  // Custom user types for your business logic
});

// 4. Premium features
app.get('/api/premium/analytics', requireUserType(['premium', 'enterprise', 'admin']), async (c) => {
  // Multiple subscription tiers
});

// 5. Educational platform
app.get('/api/grades', requireUserType(['teacher', 'admin', 'principal']), async (c) => {
  // Multiple educational roles
});

Building Flexible APIs ​

This is a configurable starter kit for developers. You can define ANY user types:

typescript
// Your custom user types
const USER_TYPES = {
  // Basic roles
  ADMIN: 'admin',
  USER: 'user',
  GUEST: 'guest',

  // Business roles
  MANAGER: 'manager',
  EMPLOYEE: 'employee',
  CONTRACTOR: 'contractor',

  // Educational roles
  TEACHER: 'teacher',
  STUDENT: 'student',
  PARENT: 'parent',

  // E-commerce roles
  SELLER: 'seller',
  BUYER: 'buyer',
  AFFILIATE: 'affiliate',

  // Subscription tiers
  FREE: 'free',
  PREMIUM: 'premium',
  ENTERPRISE: 'enterprise'
};

// Use them in your routes
app.get('/api/seller/products', requireUserType(USER_TYPES.SELLER), handler);
app.get('/api/teacher/classes', requireUserType(USER_TYPES.TEACHER), handler);
app.get('/api/premium/features', requireUserType([USER_TYPES.PREMIUM, USER_TYPES.ENTERPRISE]), handler);

Context Variables ​

After authentication, these variables are available in the context:

typescript
app.get('/api/me', requireAuth(), async (c) => {
  const userId = c.get('user_id');           // User ID
  const session = c.get('session');          // Full session object
  const userType = session.user_type;        // User type
  const email = session.email;               // Email
  const permissions = session.permissions;   // Permissions array (if any)

  return c.json({ userId, email, userType });
});

Validation Modes ​

You can specify validation mode per route:

typescript
// Standard validation
app.get('/api/profile', requireAuth('STANDARD'), handler);

// Advanced validation for sensitive routes
app.get('/api/billing', requireAuth('ADVANCED'), handler);

// Strict validation for critical operations
app.post('/api/transfer', requireAuth('STRICT'), handler);

Learn more about Validation Modes →

Error Handling ​

typescript
app.get('/api/protected', requireAuth(), async (c) => {
  try {
    const data = await getData(c.get('user_id'));
    return c.json({ data });
  } catch (error) {
    return c.json({ error: 'Internal server error' }, 500);
  }
});

Custom Middleware ​

Create custom middleware for specific needs:

typescript
// Require email verification
export function requireVerifiedEmail() {
  return async (c: Context, next: Next) => {
    const userId = c.get('user_id');
    
    const user = await db.selectFrom('users')
      .where('id', '=', userId)
      .select('email_verified')
      .executeTakeFirst();
    
    if (!user?.email_verified) {
      return c.json({ error: 'Email not verified' }, 403);
    }
    
    await next();
  };
}

// Usage
app.get('/api/premium', requireAuth(), requireVerifiedEmail(), handler);

Chaining Middleware ​

typescript
// Multiple middleware
app.post(
  '/api/admin/users',
  requireAuth('STRICT'),
  requireUserType('admin'),
  requirePermission('users.create'),
  async (c) => {
    // Create user
  }
);

Best Practices ​

✅ Do ​

  • Use requireAuth() for basic protected routes (any authenticated user)
  • Use requireUserType() when you need specific user types
  • Use optionalAuth() for public routes with personalization
  • Use arrays for multiple user types: requireUserType(['admin', 'manager'])
  • Define custom user types that match your business logic
  • Chain middleware for complex requirements
  • Handle errors gracefully with try-catch blocks
  • Use validation modes appropriately for security

❌ Don't ​

  • Don't use requireAuth() when you need specific user types - use requireUserType() instead
  • Don't hardcode user types in route handlers - validate at middleware level
  • Don't skip authentication on sensitive routes
  • Don't use DISABLED mode in production
  • Don't ignore authentication errors
  • Don't forget to validate permissions for sensitive operations

When to Use Each Middleware ​

MiddlewareUse CaseExample
requireAuth()Any authenticated user can accessUser profile, settings
requireUserType('admin')Only specific user typeAdmin dashboard
requireUserType(['admin', 'manager'])Multiple user typesManagement reports
optionalAuth()Public with personalizationProduct listings, blog posts
requirePermission('posts.delete')Specific permission neededDelete operations

Real-World Examples ​

E-commerce API ​

typescript
// Public product listing
app.get('/api/products', optionalAuth(), async (c) => {
  const userId = c.get('user_id');
  const products = await getProducts(userId);
  return c.json({ products });
});

// Protected cart
app.get('/api/cart', requireAuth(), async (c) => {
  const cart = await getCart(c.get('user_id'));
  return c.json({ cart });
});

// Admin product management
app.post('/api/admin/products', requireUserType('admin'), async (c) => {
  const product = await createProduct(await c.req.json());
  return c.json({ product });
});

SaaS Application ​

typescript
// Public landing page data
app.get('/api/features', async (c) => {
  return c.json({ features: await getFeatures() });
});

// User dashboard
app.get('/api/dashboard', requireAuth(), async (c) => {
  const dashboard = await getDashboard(c.get('user_id'));
  return c.json({ dashboard });
});

// Organization admin
app.get('/api/org/settings', requirePermission('org.manage'), async (c) => {
  const settings = await getOrgSettings(c.get('organization_id'));
  return c.json({ settings });
});

Quick Reference ​

Import Middleware ​

typescript
import {
  requireAuth,
  optionalAuth,
  requireUserType,
  requirePermission
} from './lib/auth/middleware';

Basic Examples ​

typescript
// 1. Protected route (any authenticated user)
app.get('/api/profile', requireAuth(), handler);

// 2. Public route with optional auth
app.get('/api/posts', optionalAuth(), handler);

// 3. Admin only
app.get('/api/admin/users', requireUserType('admin'), handler);

// 4. Multiple user types
app.get('/api/manager/dashboard', requireUserType(['admin', 'manager']), handler);

// 5. Permission-based
app.delete('/api/posts/:id', requirePermission('posts.delete'), handler);

// 6. Chain multiple middleware
app.post(
  '/api/admin/users',
  requireAuth(),
  requireUserType('admin'),
  requirePermission('users.create'),
  handler
);

Common Patterns ​

typescript
// Pattern 1: Public API
app.get('/api/public/stats', async (c) => {
  return c.json({ stats: await getStats() });
});

// Pattern 2: User-specific data
app.get('/api/my/orders', requireAuth(), async (c) => {
  const userId = c.get('user_id');
  return c.json({ orders: await getOrders(userId) });
});

// Pattern 3: Role-based access
app.get('/api/reports', requireUserType(['admin', 'manager']), async (c) => {
  return c.json({ reports: await getReports() });
});

// Pattern 4: Conditional content
app.get('/api/content', optionalAuth(), async (c) => {
  const userId = c.get('user_id');
  const content = userId
    ? await getPremiumContent(userId)
    : await getFreeContent();
  return c.json({ content });
});

Next Steps ​

Need Help? ​