middleware-protection
Implement declarative security and route protection in Next.js applications.
Install
mkdir -p .claude/skills/middleware-protection && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/4274" && unzip -o skill.zip -d .claude/skills/middleware-protection && rm skill.zipInstalls to .claude/skills/middleware-protection
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Protect routes with Next.js middleware. Check authentication once, protect routes declaratively. Supports public routes, protected routes, and role-based access.Key capabilities
- →Centralize authentication checks for multiple routes
- →Implement role-based access control
- →Refresh user sessions during SSR
- →Inject user ID into request headers
- →Handle API-specific error responses
How it works
The middleware intercepts all requests to verify session validity via Supabase, then either redirects unauthenticated users or injects the user ID into headers for downstream routes.
Inputs & outputs
When to use middleware-protection
- →Protect private routes
- →Implement role-based access
- →Verify auth tokens on requests
About this skill
Middleware Route Protection
Check auth once, protect routes declaratively.
When to Use This Skill
- Need to protect multiple routes
- Want centralized auth checking
- Tired of repeating auth logic in every route
- Need role-based access control
Core Concepts
- Middleware intercepts - All requests pass through middleware
- Declarative routes - Define protected/public routes in config
- Session refresh - Keep sessions alive automatically
- Consistent errors - API routes get JSON, pages get redirects
TypeScript Implementation
middleware.ts
// middleware.ts
import { createServerClient } from '@supabase/ssr';
import { NextResponse, type NextRequest } from 'next/server';
// Routes that require authentication
const PROTECTED_ROUTES = [
'/dashboard',
'/settings',
'/api/user',
'/api/predictions',
];
// Routes that are always public
const PUBLIC_ROUTES = [
'/',
'/login',
'/signup',
'/api/health',
'/api/public',
];
export async function middleware(request: NextRequest) {
const pathname = request.nextUrl.pathname;
// Skip static files and Next.js internals
if (
pathname.startsWith('/_next') ||
pathname.startsWith('/favicon') ||
pathname.match(/\.(svg|png|jpg|jpeg|gif|webp|ico)$/)
) {
return NextResponse.next();
}
// Skip explicitly public routes
if (PUBLIC_ROUTES.some(route => pathname === route || pathname.startsWith(route + '/'))) {
return NextResponse.next();
}
// Create response that we'll modify
let response = NextResponse.next({ request });
// Create Supabase client with cookie handling
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll();
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value }) =>
request.cookies.set(name, value)
);
response = NextResponse.next({ request });
cookiesToSet.forEach(({ name, value, options }) =>
response.cookies.set(name, value, options)
);
},
},
}
);
// Refresh session (important for SSR)
const { data: { user } } = await supabase.auth.getUser();
// Check if route requires auth
const requiresAuth = PROTECTED_ROUTES.some(route =>
pathname === route || pathname.startsWith(route + '/')
);
if (requiresAuth && !user) {
// API routes: return 401 JSON
if (pathname.startsWith('/api/')) {
return NextResponse.json(
{
error: 'Authentication required',
code: 'AUTH_REQUIRED',
loginUrl: '/login',
},
{ status: 401 }
);
}
// Pages: redirect to login with return URL
const url = request.nextUrl.clone();
url.pathname = '/login';
url.searchParams.set('redirectTo', pathname);
return NextResponse.redirect(url);
}
// Add user ID to headers for downstream use
if (user) {
response.headers.set('x-user-id', user.id);
}
return response;
}
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
],
};
Using User ID in API Routes
// app/api/user/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { createServerSupabaseClient } from '@/lib/supabase-server';
export async function GET(request: NextRequest) {
// User ID was added by middleware
const userId = request.headers.get('x-user-id');
if (!userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const supabase = await createServerSupabaseClient();
const { data: profile } = await supabase
.from('user_profiles')
.select('*')
.eq('id', userId)
.single();
return NextResponse.json({ profile });
}
Role-Based Protection
// middleware.ts (extended)
const ROUTE_ROLES: Record<string, string[]> = {
'/admin': ['admin'],
'/dashboard': ['user', 'admin'],
'/api/admin': ['admin'],
};
// After getting user, check role
if (user) {
const userRole = user.user_metadata?.role || 'user';
const requiredRoles = Object.entries(ROUTE_ROLES)
.find(([route]) => pathname.startsWith(route))?.[1];
if (requiredRoles && !requiredRoles.includes(userRole)) {
if (pathname.startsWith('/api/')) {
return NextResponse.json(
{ error: 'Forbidden', code: 'FORBIDDEN' },
{ status: 403 }
);
}
return NextResponse.redirect(new URL('/unauthorized', request.url));
}
}
Pattern-Based Routes
// For more complex route matching
const PROTECTED_PATTERNS = [
/^\/dashboard(\/.*)?$/,
/^\/api\/user\/.*$/,
/^\/settings$/,
/^\/api\/v\d+\/private\/.*/, // /api/v1/private/*, /api/v2/private/*
];
const requiresAuth = PROTECTED_PATTERNS.some(pattern =>
pattern.test(pathname)
);
Python Implementation (FastAPI)
# middleware/auth.py
from fastapi import Request, HTTPException
from fastapi.responses import RedirectResponse
from starlette.middleware.base import BaseHTTPMiddleware
from typing import Set
PROTECTED_ROUTES: Set[str] = {
"/dashboard",
"/settings",
"/api/user",
}
PUBLIC_ROUTES: Set[str] = {
"/",
"/login",
"/signup",
"/api/health",
}
class AuthMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
path = request.url.path
# Skip public routes
if path in PUBLIC_ROUTES or any(path.startswith(r + "/") for r in PUBLIC_ROUTES):
return await call_next(request)
# Check if route requires auth
requires_auth = path in PROTECTED_ROUTES or any(
path.startswith(r + "/") for r in PROTECTED_ROUTES
)
if not requires_auth:
return await call_next(request)
# Get user from session/token
user = await get_user_from_request(request)
if not user:
if path.startswith("/api/"):
raise HTTPException(
status_code=401,
detail={
"error": "Authentication required",
"code": "AUTH_REQUIRED",
"login_url": "/login",
}
)
return RedirectResponse(f"/login?redirectTo={path}")
# Add user to request state
request.state.user = user
request.state.user_id = user.id
return await call_next(request)
async def get_user_from_request(request: Request):
"""Extract and validate user from request."""
token = request.cookies.get("session") or request.headers.get("Authorization")
if not token:
return None
# Validate token and return user
return await validate_session(token)
# Using in routes
from fastapi import Request, Depends
@app.get("/api/user/profile")
async def get_profile(request: Request):
user_id = request.state.user_id
profile = await db.get_profile(user_id)
return {"profile": profile}
Error Response Format
// Consistent error format for API routes
interface AuthError {
error: string;
code: 'AUTH_REQUIRED' | 'SESSION_EXPIRED' | 'FORBIDDEN';
message?: string;
loginUrl: string;
}
// 401 - Not authenticated
{
"error": "Authentication required",
"code": "AUTH_REQUIRED",
"loginUrl": "/login"
}
// 403 - Authenticated but not authorized
{
"error": "Forbidden",
"code": "FORBIDDEN",
"message": "Admin access required"
}
Best Practices
- Refresh sessions - Call
getUser()in middleware to refresh - Pass user ID - Add to headers for downstream routes
- JSON for APIs - Never redirect API routes
- Return URL - Include
redirectToparam for login redirects - Skip static - Don't process static files
Common Mistakes
- Redirecting API routes (should return 401 JSON)
- Not refreshing session in middleware
- Processing static files through auth
- Missing return URL on login redirect
- Not handling role-based access
Related Skills
When not to use it
- →When using client-side only routing without server-side middleware
- →When authentication is handled entirely by a third-party service provider without custom route logic
Prerequisites
Limitations
- →Must explicitly skip static files and internal Next.js paths
- →Requires careful configuration to avoid redirect loops
How it compares
This approach centralizes security logic in one file rather than repeating authentication checks across every individual route handler.
Compared to similar skills
middleware-protection side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| middleware-protection (this skill) | 1 | 5mo | Review | Intermediate |
| auth-patterns | 7 | 7mo | Review | Intermediate |
| reviewing-nextjs-16-patterns | 11 | 8mo | Review | Intermediate |
| better-auth | 5 | 9mo | Review | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by dadbodgeoff
View all by dadbodgeoff →You might also like
auth-patterns
davepoon
This skill should be used when the user asks about "authentication in Next.js", "NextAuth", "Auth.js", "middleware auth", "protected routes", "session management", "JWT", "login flow", or needs guidance on implementing authentication and authorization in Next.js applications.
reviewing-nextjs-16-patterns
djankies
Review code for Next.js 16 compliance - security patterns, caching, breaking changes. Use when reviewing Next.js code, preparing for migration, or auditing for violations.
better-auth
mrgoonie
Implement authentication and authorization with Better Auth - a framework-agnostic TypeScript authentication framework. Features include email/password authentication with verification, OAuth providers (Google, GitHub, Discord, etc.), two-factor authentication (TOTP, SMS), passkeys/WebAuthn support, session management, role-based access control (RBAC), rate limiting, and database adapters. Use when adding authentication to applications, implementing OAuth flows, setting up 2FA/MFA, managing user sessions, configuring authorization rules, or building secure authentication systems for web applications.
route-handlers
davepoon
This skill should be used when the user asks to "create an API route", "add an endpoint", "build a REST API", "handle POST requests", "create route handlers", "stream responses", or needs guidance on Next.js API development in the App Router.
nextjs-server-side-error-debugging
blader
Debug getServerSideProps and getStaticProps errors in Next.js. Use when: (1) Page shows generic error but browser console is empty, (2) API routes return 500 with no details, (3) Server-side code fails silently, (4) Error only occurs on refresh not client navigation. Check terminal/server logs instead of browser for actual error messages.
azure-keyvault-keys-ts
microsoft
Manage cryptographic keys using Azure Key Vault Keys SDK for JavaScript (@azure/keyvault-keys). Use when creating, encrypting/decrypting, signing, or rotating keys.