MI

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.zip

Installs 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.
161 charsno explicit “when” trigger
Intermediate

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

You give it
Incoming HTTP request
You get back
Redirect response or modified request with user headers

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

  1. Middleware intercepts - All requests pass through middleware
  2. Declarative routes - Define protected/public routes in config
  3. Session refresh - Keep sessions alive automatically
  4. 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

  1. Refresh sessions - Call getUser() in middleware to refresh
  2. Pass user ID - Add to headers for downstream routes
  3. JSON for APIs - Never redirect API routes
  4. Return URL - Include redirectTo param for login redirects
  5. 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

Next.jsSupabase accountEnvironment variables for Supabase URL and key

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.

SkillInstallsUpdatedSafetyDifficulty
middleware-protection (this skill)15moReviewIntermediate
auth-patterns77moReviewIntermediate
reviewing-nextjs-16-patterns118moReviewIntermediate
better-auth59moReviewIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

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.

720

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.

11106

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.

527

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.

14

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.

11

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.

10

Search skills

Search the agent skills registry