middleware-protection

Middleware Route Protection

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "middleware-protection" with this command: npx skills add dadbodgeoff/drift/dadbodgeoff-drift-middleware-protection

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 redirectTo param 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

  • Supabase Auth

  • JWT Auth

  • Row Level Security

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

oauth-social-login

No summary provided by upstream source.

Repository SourceNeeds Review
General

sse-streaming

No summary provided by upstream source.

Repository SourceNeeds Review
General

deduplication

No summary provided by upstream source.

Repository SourceNeeds Review