TL;DR
Auth middleware lets you protect routes consistently without repeating code. Create a reusable function that validates sessions/tokens and attach user info to the request. These prompts help you build middleware for different frameworks.
Next.js Middleware
Create authentication middleware for Next.js App Router.
Requirements:
- Check for valid session cookie
- Validate session on server
- Redirect unauthenticated users to login
- Protect specific routes by pattern
- Allow public routes through
middleware.ts should:
- Run on matched routes
- Check session cookie exists
- Verify session is valid (not expired)
- Redirect to /login if invalid
- Pass through if valid
Config matcher:
- Protect: /dashboard/, /api/protected/
- Allow: /login, /register, /api/public/*
Also create:
- getSession() helper for server components
- requireAuth() wrapper for API routes
- useAuth() hook for client components
Express Middleware
Create reusable auth middleware for Express.
Middleware function should:
- Extract token from Authorization header or cookie
- Validate token (JWT or session lookup)
- Attach user to req.user
- Call next() if valid
- Return 401 if invalid
Create variations:
- requireAuth: must be authenticated
- optionalAuth: attach user if present, continue either way
- requireRole(role): must have specific role
Usage: app.get('/protected', requireAuth, handler) app.get('/admin', requireAuth, requireRole('admin'), handler) app.get('/public', optionalAuth, handler)
Handle errors:
- Missing token: 401 with { error: 'Authentication required' }
- Invalid token: 401 with { error: 'Invalid token' }
- Expired token: 401 with { error: 'Token expired' }
- Insufficient role: 403 with { error: 'Forbidden' }
Default deny: Your middleware should require authentication by default. Explicitly allow public routes rather than explicitly protecting private ones. Missing a protection is worse than over-protecting.
API Route Protection
Create a wrapper to protect API routes with auth.
For Next.js API routes / Route Handlers:
Create withAuth higher-order function:
- Validates session before running handler
- Passes user to handler
- Returns 401 if not authenticated
- Handles errors consistently
Usage: export const GET = withAuth(async (req, { user }) => { // user is guaranteed to exist here return Response.json({ user }) })
For role-based: export const DELETE = withAuth( async (req, { user }) => { ... }, { requiredRole: 'admin' } )
The wrapper should:
- Parse and validate session/token
- Fetch user from database
- Check role if specified
- Log unauthorized attempts
- Provide typed user object
Server Action Protection
Add auth checks to Next.js Server Actions.
Server Actions can be called from client - always verify auth!
Create pattern: async function protectedAction(formData: FormData) { 'use server'
const session = await getSession() if (!session) { throw new Error('Unauthorized') }
// Now safe to proceed with session.user }
Create reusable wrapper: const authAction = ( action: (user: User, ...args: any) => Promise ) => { return async (...args: any): Promise => { const session = await getSession() if (!session) throw new Error('Unauthorized') return action(session.user, ...args) } }
Usage: export const updateProfile = authAction(async (user, data) => { // user is verified })
Pro tip: Add request logging to your auth middleware. Log successful auths (user, route, time) and failed attempts (IP, route, reason) for security monitoring.
Should middleware validate the full session or just check existence?
At minimum, verify the session exists and hasn't expired. For high-security apps, also verify the user still exists and isn't disabled. Balance security with performance.
Where should I check permissions - middleware or handler?
Basic auth in middleware, specific permissions in handler. Middleware handles "is user logged in?" Handler handles "can this user access this specific resource?"