Protected Routes Guide
Complete guide to protecting your API routes with authentication middleware in Flowfull.
Overview
Flowfull provides flexible middleware patterns for route protection:
requireAuth()- Require authenticated useroptionalAuth()- Optional authenticationrequireUserType()- Require specific user typerequirePermission()- Require specific permission
Basic Authentication
Require Authentication
Protect routes that require a logged-in user:
typescript
import { Hono } from 'hono';
import { requireAuth } from './lib/auth/middleware';
const app = new Hono();
// Protected route
app.get('/api/profile', requireAuth(), async (c) => {
const userId = c.get('user_id');
const email = c.get('email');
const profile = await db.selectFrom('users')
.where('id', '=', userId)
.selectAll()
.executeTakeFirst();
return c.json({ profile });
});Optional Authentication
For routes that work with or without authentication:
typescript
// Public route with personalization
app.get('/api/posts', optionalAuth(), async (c) => {
const userId = c.get('user_id'); // May be undefined
if (userId) {
// Show personalized posts
const posts = await db.selectFrom('posts')
.where('user_id', '=', userId)
.orWhere('visibility', '=', 'public')
.selectAll()
.execute();
return c.json({ posts, personalized: true });
} else {
// Show public posts only
const posts = await db.selectFrom('posts')
.where('visibility', '=', 'public')
.selectAll()
.execute();
return c.json({ posts, personalized: false });
}
});Role-Based Access
Require User Type
Protect routes for specific user types:
typescript
// Admin-only route
app.get('/api/admin/users', requireUserType('admin'), async (c) => {
const users = await db.selectFrom('users').selectAll().execute();
return c.json({ users });
});
// Teacher-only route
app.get('/api/teacher/classes', requireUserType('teacher'), async (c) => {
const teacherId = c.get('user_id');
const classes = await db.selectFrom('classes')
.where('teacher_id', '=', teacherId)
.selectAll()
.execute();
return c.json({ classes });
});
// Student-only route
app.get('/api/student/assignments', requireUserType('student'), async (c) => {
const studentId = c.get('user_id');
const assignments = await db.selectFrom('assignments')
.where('student_id', '=', studentId)
.selectAll()
.execute();
return c.json({ assignments });
});Dynamic Role Routing
Route users to different dashboards based on role:
typescript
app.get('/api/dashboard', requireAuth(), async (c) => {
const userType = c.get('user_type');
switch (userType) {
case 'admin':
return c.json({
dashboard: await getAdminDashboard(),
type: 'admin'
});
case 'teacher':
return c.json({
dashboard: await getTeacherDashboard(c.get('user_id')),
type: 'teacher'
});
case 'student':
return c.json({
dashboard: await getStudentDashboard(c.get('user_id')),
type: 'student'
});
default:
return c.json({ error: 'Invalid user type' }, 403);
}
});Permission-Based Access
Require Permission
Protect routes with granular permissions:
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 });
});
// Multiple permissions
app.post('/api/admin/users',
requirePermission('users.create'),
requirePermission('admin.access'),
async (c) => {
const data = await c.req.json();
const user = await createUser(data);
return c.json({ user });
}
);Validation Modes
Use different validation modes for different security levels:
typescript
// Standard validation (IP check)
app.get('/api/profile', requireAuth('STANDARD'), async (c) => {
// ...
});
// Advanced validation (IP + User-Agent)
app.get('/api/billing', requireAuth('ADVANCED'), async (c) => {
// ...
});
// Strict validation (IP + User-Agent + Device ID)
app.post('/api/transfer', requireAuth('STRICT'), async (c) => {
// ...
});Learn more about Validation Modes →
Context Variables
After authentication, these variables are available:
typescript
app.get('/api/me', requireAuth(), async (c) => {
const userId = c.get('user_id'); // User ID
const email = c.get('email'); // Email
const userType = c.get('user_type'); // User type
const sessionId = c.get('session_id'); // Session ID
const permissions = c.get('permissions'); // Permissions array
return c.json({
userId,
email,
userType,
permissions
});
});Custom Middleware
Create custom middleware for specific needs:
Require Email Verification
typescript
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',
code: 'EMAIL_NOT_VERIFIED'
}, 403);
}
await next();
};
}
// Usage
app.get('/api/premium',
requireAuth(),
requireVerifiedEmail(),
async (c) => {
// Premium content
}
);Require Organization Membership
typescript
export function requireOrgMember(orgId?: string) {
return async (c: Context, next: Next) => {
const userId = c.get('user_id');
const targetOrgId = orgId || c.req.param('orgId');
const membership = await db.selectFrom('org_members')
.where('user_id', '=', userId)
.where('organization_id', '=', targetOrgId)
.selectAll()
.executeTakeFirst();
if (!membership) {
return c.json({
error: 'Not a member of this organization'
}, 403);
}
c.set('org_role', membership.role);
await next();
};
}
// Usage
app.get('/api/orgs/:orgId/settings',
requireAuth(),
requireOrgMember(),
async (c) => {
const orgId = c.req.param('orgId');
const role = c.get('org_role');
// ...
}
);Chaining Middleware
Combine multiple middleware for complex requirements:
typescript
app.post(
'/api/admin/users',
requireAuth('STRICT'), // Strict validation
requireUserType('admin'), // Must be admin
requirePermission('users.create'), // Must have permission
requireVerifiedEmail(), // Must have verified email
async (c) => {
// Create user
const data = await c.req.json();
const user = await createUser(data);
return c.json({ user });
}
);Error Handling
Handle authentication errors gracefully:
typescript
app.get('/api/protected', requireAuth(), async (c) => {
try {
const data = await getData(c.get('user_id'));
return c.json({ data });
} catch (error) {
console.error('Error:', error);
return c.json({
error: 'Internal server error',
code: 'INTERNAL_ERROR'
}, 500);
}
});
// Global error handler
app.onError((err, c) => {
if (err.message === 'Unauthorized') {
return c.json({
error: 'Authentication required',
code: 'UNAUTHORIZED'
}, 401);
}
return c.json({
error: 'Internal server error',
code: 'INTERNAL_ERROR'
}, 500);
});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 });
});
// Checkout (strict validation)
app.post('/api/checkout', requireAuth('STRICT'), async (c) => {
const order = await processCheckout(c.get('user_id'));
return c.json({ order });
});
// 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 features
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 settings
app.get('/api/org/:orgId/settings',
requireAuth(),
requireOrgMember(),
async (c) => {
const settings = await getOrgSettings(c.req.param('orgId'));
return c.json({ settings });
}
);
// Billing (strict validation)
app.post('/api/billing/subscribe',
requireAuth('STRICT'),
requireVerifiedEmail(),
async (c) => {
const subscription = await createSubscription(c.get('user_id'));
return c.json({ subscription });
}
);Best Practices
✅ Do
- Use
requireAuth()for protected routes - Use
optionalAuth()for public routes with personalization - Use appropriate validation modes
- Handle errors gracefully
- Chain middleware for complex requirements
- Create reusable custom middleware
❌ Don't
- Skip authentication on sensitive routes
- Use
DISABLEDmode in production - Ignore authentication errors
- Hardcode user types in routes
- Forget to validate permissions
- Mix authentication logic in route handlers
Next Steps
- Auth Middleware - Core concepts
- Validation Modes - Security levels
- Bridge Validation - How it works
Need Help?
- 🌐 Notside.com - Professional API security
- 📧 Email: contact@notside.com