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?