Falytom logoFALYTOM
Games
ToolsUI ComponentsServicesProductsAbout
menu
  1. Home
  2. /Web Development
  3. /Nextjs
  4. /Middleware Authentication
Your Progress0%
0 of 70 completed

NextJS Topics

Getting Started

  • What is Next.js?
  • Next.js vs React - Key Differences
  • Creating Your First Next.js 15 Project
  • Next.js Project Structure Explained
  • App Router vs Pages Router

Routing Fundamentals

  • File-Based Routing Basics
  • Creating Pages with page.tsx
  • Dynamic Routes and Route Parameters
  • Catch-All and Optional Catch-All Routes
  • Route Groups for Organization
  • Parallel Routes
  • Intercepting Routes

Layouts and Pages

  • Understanding Layouts
  • Root Layout and Global Configuration
  • Nested Layouts
  • Templates vs Layouts
  • Loading States with loading.tsx
  • Error Handling with error.tsx

Server and Client Components

  • Understanding Server Components
  • Client Components with 'use client'
  • When to Use Server vs Client Components
  • Component Composition Patterns
  • Passing Props Between Server and Client
  • Server Component Patterns and Best Practices

Data Fetching

  • Fetching Data in Server Components
  • Parallel and Sequential Data Fetching
  • Data Fetching Patterns and Strategies
  • Caching and Revalidation
  • Revalidate and Cache Tags
  • Handling Loading and Error States in Data Fetching

Navigation and Links

  • Link Component Basics
  • useRouter Hook for Programmatic Navigation
  • usePathname and useSearchParams Hooks
  • Active Links and Navigation States
  • Redirects and Navigation Guards

Styling in Next.js

  • CSS Modules in Next.js
  • Tailwind CSS Setup and Configuration
  • Global Styles and CSS Variables
  • Next.js Font Optimization

Images and Media

  • Next.js Image Component Basics
  • Image Optimization and Best Practices
  • Working with Static Assets

Forms and Data Mutations

  • Understanding Server Actions
  • Form Handling with Server Actions
  • Form Validation and Error Handling
  • useFormStatus and useFormState Hooks
  • Revalidating Data After Mutations
  • Optimistic Updates

Metadata and SEO

  • Static Metadata Configuration
  • Dynamic Metadata Generation
  • Open Graph and Social Media Cards
  • Sitemap and Robots.txt

API Routes and Route Handlers

  • Route Handlers Introduction
  • GET and POST Request Handlers
  • Dynamic API Routes
  • Request and Response Objects
  • API Error Handling and Status Codes

Middleware and Advanced Features

  • Introduction to Middleware
  • Authentication with Middleware
  • Environment Variables and Configuration
  • Streaming and Suspense
  • Not Found and Global Error Pages

Deployment and Optimization

  • Understanding Static and Dynamic Rendering
  • generateStaticParams for Static Generation
  • Build and Production Optimization
  • Deploying to Vercel
  • Alternative Deployment Options
  • Performance Monitoring and Analytics

Real-World Projects

  • Project 1: E-commerce Product Catalog
  • Project 2: Dashboard with Authentication

Authentication with Middleware

Protecting routes and implementing auth flows

Authentication middleware provides centralized route protection, checking user credentials before allowing access to protected pages. Implement session-based or JWT authentication, handle login redirects, manage user sessions, and enforce role-based access control. Master authentication middleware and secure your entire application with a single, powerful guard!

Session-Based Authentication

Basic Session Authentication

middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

// Protected route patterns
const protectedRoutes = ['/dashboard', '/profile', '/settings'];

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;
  
  // Check if route is protected
  const isProtectedRoute = protectedRoutes.some(route =>
    pathname.startsWith(route)
  );
  
  if (isProtectedRoute) {
    // Check for session cookie
    const session = request.cookies.get('session');
    
    if (!session) {
      // No session - redirect to login
      const loginUrl = new URL('/login', request.url);
      loginUrl.searchParams.set('returnTo', pathname);
      return NextResponse.redirect(loginUrl);
    }
  }
  
  // Redirect logged-in users away from auth pages
  const authPages = ['/login', '/register'];
  const isAuthPage = authPages.includes(pathname);
  
  if (isAuthPage) {
    const session = request.cookies.get('session');
    
    if (session) {
      // Already logged in - go to dashboard
      return NextResponse.redirect(new URL('/dashboard', request.url));
    }
  }
  
  return NextResponse.next();
}

export const config = {
  matcher: [
    '/dashboard/:path*',
    '/profile/:path*',
    '/settings/:path*',
    '/login',
    '/register',
  ],
};

// ✅ Check session cookie existence
// ✅ Redirect unauthenticated users
// ✅ Preserve intended destination
// ✅ Prevent logged-in users from seeing auth pages

Session Verification with Helper

lib/auth.ts
// Auth helper functions
export async function verifySession(sessionToken: string) {
  try {
    // Verify session with your auth system
    // This could be:
    // - Database lookup
    // - Redis cache lookup
    // - JWT verification
    
    const session = await db.sessions.findUnique({
      where: { token: sessionToken },
      include: { user: true },
    });
    
    if (!session) {
      return null;
    }
    
    // Check if session expired
    if (session.expiresAt < new Date()) {
      await db.sessions.delete({ where: { id: session.id } });
      return null;
    }
    
    return {
      userId: session.userId,
      user: session.user,
      expiresAt: session.expiresAt,
    };
  } catch (error) {
    console.error('Session verification error:', error);
    return null;
  }
}

export function isSessionValid(session: any): boolean {
  if (!session) return false;
  if (!session.expiresAt) return false;
  return new Date(session.expiresAt) > new Date();
}

// ✅ Centralized session verification
// ✅ Database validation
// ✅ Expiration check
// ✅ Error handling

Using Session Verification in Middleware

middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { verifySession } from '@/lib/auth';

export async function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;
  
  const isProtected = pathname.startsWith('/dashboard');
  
  if (isProtected) {
    const sessionToken = request.cookies.get('session')?.value;
    
    if (!sessionToken) {
      return redirectToLogin(request);
    }
    
    // Verify session is valid
    const session = await verifySession(sessionToken);
    
    if (!session) {
      // Invalid or expired session
      const response = redirectToLogin(request);
      // Clear invalid session cookie
      response.cookies.delete('session');
      return response;
    }
    
    // Add user info to request headers for pages to use
    const requestHeaders = new Headers(request.headers);
    requestHeaders.set('x-user-id', session.userId);
    
    return NextResponse.next({
      request: {
        headers: requestHeaders,
      },
    });
  }
  
  return NextResponse.next();
}

function redirectToLogin(request: NextRequest) {
  const loginUrl = new URL('/login', request.url);
  loginUrl.searchParams.set('returnTo', request.nextUrl.pathname);
  return NextResponse.redirect(loginUrl);
}

export const config = {
  matcher: '/dashboard/:path*',
};

// ✅ Verify session validity
// ✅ Clear expired sessions
// ✅ Pass user ID to pages
// ✅ Helper function for redirects

JWT-Based Authentication

JWT Verification Utility

lib/jwt.ts
import { jwtVerify, SignJWT } from 'jose';

const JWT_SECRET = new TextEncoder().encode(
  process.env.JWT_SECRET || 'your-secret-key'
);

export interface JWTPayload {
  userId: string;
  email: string;
  role: string;
  iat?: number;
  exp?: number;
}

export async function verifyJWT(token: string): Promise<JWTPayload | null> {
  try {
    const verified = await jwtVerify(token, JWT_SECRET);
    return verified.payload as JWTPayload;
  } catch (error) {
    console.error('JWT verification failed:', error);
    return null;
  }
}

export async function createJWT(payload: JWTPayload): Promise<string> {
  const token = await new SignJWT({ ...payload })
    .setProtectedHeader({ alg: 'HS256' })
    .setIssuedAt()
    .setExpirationTime('7d')
    .sign(JWT_SECRET);
  
  return token;
}

// ✅ Use jose library (Edge-compatible)
// ✅ Environment variable for secret
// ✅ Type-safe payload
// ✅ 7-day expiration

JWT Authentication Middleware

middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { verifyJWT } from '@/lib/jwt';

export async function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;
  
  const isProtected = pathname.startsWith('/dashboard') ||
                     pathname.startsWith('/api/protected');
  
  if (isProtected) {
    // Get JWT from cookie or Authorization header
    let token = request.cookies.get('token')?.value;
    
    if (!token) {
      const authHeader = request.headers.get('authorization');
      if (authHeader?.startsWith('Bearer ')) {
        token = authHeader.substring(7);
      }
    }
    
    if (!token) {
      return unauthorizedResponse(request, pathname);
    }
    
    // Verify JWT
    const payload = await verifyJWT(token);
    
    if (!payload) {
      // Invalid or expired token
      const response = unauthorizedResponse(request, pathname);
      response.cookies.delete('token');
      return response;
    }
    
    // Add user info to headers
    const requestHeaders = new Headers(request.headers);
    requestHeaders.set('x-user-id', payload.userId);
    requestHeaders.set('x-user-email', payload.email);
    requestHeaders.set('x-user-role', payload.role);
    
    return NextResponse.next({
      request: {
        headers: requestHeaders,
      },
    });
  }
  
  return NextResponse.next();
}

function unauthorizedResponse(request: NextRequest, pathname: string) {
  // For API routes, return 401
  if (pathname.startsWith('/api/')) {
    return NextResponse.json(
      { error: 'Unauthorized' },
      { status: 401 }
    );
  }
  
  // For pages, redirect to login
  const loginUrl = new URL('/login', request.url);
  loginUrl.searchParams.set('returnTo', pathname);
  return NextResponse.redirect(loginUrl);
}

export const config = {
  matcher: ['/dashboard/:path*', '/api/protected/:path*'],
};

// ✅ Support cookie and header tokens
// ✅ Verify JWT signature
// ✅ Handle API vs page routes differently
// ✅ Pass user data to handlers

Role-Based Access Control (RBAC)

Define Role Requirements

lib/auth.ts
// Role definitions
export enum UserRole {
  USER = 'user',
  ADMIN = 'admin',
  MODERATOR = 'moderator',
}

// Route permissions
export const routePermissions: Record<string, UserRole[]> = {
  '/dashboard': [UserRole.USER, UserRole.ADMIN, UserRole.MODERATOR],
  '/admin': [UserRole.ADMIN],
  '/moderator': [UserRole.ADMIN, UserRole.MODERATOR],
};

export function hasPermission(
  userRole: string,
  pathname: string
): boolean {
  // Find matching route permission
  const matchingRoute = Object.keys(routePermissions).find(route =>
    pathname.startsWith(route)
  );
  
  if (!matchingRoute) {
    // No specific permission required
    return true;
  }
  
  const allowedRoles = routePermissions[matchingRoute];
  return allowedRoles.includes(userRole as UserRole);
}

// ✅ Enum for type safety
// ✅ Route-to-role mapping
// ✅ Permission checker function

RBAC Middleware

middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { verifyJWT } from '@/lib/jwt';
import { hasPermission } from '@/lib/auth';

export async function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;
  
  // Protected routes
  const isProtected = pathname.startsWith('/dashboard') ||
                     pathname.startsWith('/admin') ||
                     pathname.startsWith('/moderator');
  
  if (isProtected) {
    const token = request.cookies.get('token')?.value;
    
    if (!token) {
      return redirectToLogin(request);
    }
    
    // Verify JWT
    const payload = await verifyJWT(token);
    
    if (!payload) {
      return redirectToLogin(request);
    }
    
    // Check role-based permissions
    const hasAccess = hasPermission(payload.role, pathname);
    
    if (!hasAccess) {
      // User authenticated but doesn't have permission
      return NextResponse.redirect(
        new URL('/unauthorized', request.url)
      );
    }
    
    // Pass user info to pages
    const requestHeaders = new Headers(request.headers);
    requestHeaders.set('x-user-id', payload.userId);
    requestHeaders.set('x-user-role', payload.role);
    
    return NextResponse.next({
      request: {
        headers: requestHeaders,
      },
    });
  }
  
  return NextResponse.next();
}

function redirectToLogin(request: NextRequest) {
  const loginUrl = new URL('/login', request.url);
  loginUrl.searchParams.set('returnTo', request.nextUrl.pathname);
  return NextResponse.redirect(loginUrl);
}

export const config = {
  matcher: ['/dashboard/:path*', '/admin/:path*', '/moderator/:path*'],
};

// ✅ Verify authentication
// ✅ Check role permissions
// ✅ Redirect to unauthorized page
// ✅ Pass role to pages

Unauthorized Page

app/unauthorized/page.tsx
export default function UnauthorizedPage() {
  return (
    <div className="flex min-h-screen items-center justify-center">
      <div className="text-center">
        <h1 className="text-4xl font-bold mb-4">403 - Unauthorized</h1>
        <p className="text-gray-600 mb-8">
          You don't have permission to access this page.
        </p>
        
          href="/dashboard"
          className="text-blue-600 hover:underline"
        >
          Return to Dashboard
        </a>
      </div>
    </div>
  );
}

// ✅ Clear unauthorized message
// ✅ Link back to allowed page
// ✅ User-friendly

Complete Authentication Flow

Login API Route

app/api/auth/login/route.ts
import { NextResponse } from 'next/server';
import { z } from 'zod';
import { createJWT } from '@/lib/jwt';
import bcrypt from 'bcryptjs';

const loginSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
});

export async function POST(request: Request) {
  try {
    const body = await request.json();
    const result = loginSchema.safeParse(body);
    
    if (!result.success) {
      return NextResponse.json(
        { error: 'Invalid credentials' },
        { status: 400 }
      );
    }
    
    const { email, password } = result.data;
    
    // Find user
    const user = await db.users.findUnique({
      where: { email },
    });
    
    if (!user) {
      return NextResponse.json(
        { error: 'Invalid credentials' },
        { status: 401 }
      );
    }
    
    // Verify password
    const validPassword = await bcrypt.compare(password, user.hashedPassword);
    
    if (!validPassword) {
      return NextResponse.json(
        { error: 'Invalid credentials' },
        { status: 401 }
      );
    }
    
    // Create JWT
    const token = await createJWT({
      userId: user.id,
      email: user.email,
      role: user.role,
    });
    
    // Set cookie
    const response = NextResponse.json({
      success: true,
      user: {
        id: user.id,
        email: user.email,
        name: user.name,
        role: user.role,
      },
    });
    
    response.cookies.set('token', token, {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'lax',
      maxAge: 60 * 60 * 24 * 7, // 7 days
      path: '/',
    });
    
    return response;
  } catch (error) {
    console.error('Login error:', error);
    return NextResponse.json(
      { error: 'Login failed' },
      { status: 500 }
    );
  }
}

// ✅ Validate credentials
// ✅ Verify password
// ✅ Create JWT
// ✅ Set secure cookie

Logout API Route

app/api/auth/logout/route.ts
import { NextResponse } from 'next/server';

export async function POST(request: Request) {
  // Optional: Invalidate session in database
  const token = request.headers.get('cookie')
    ?.split('; ')
    .find(c => c.startsWith('token='))
    ?.split('=')[1];
  
  if (token) {
    // If using database sessions, delete it
    // await db.sessions.delete({ where: { token } });
  }
  
  // Clear cookie
  const response = NextResponse.json({
    success: true,
    message: 'Logged out successfully',
  });
  
  response.cookies.delete('token');
  
  return response;
}

// ✅ Clear auth cookie
// ✅ Optional: invalidate in database
// ✅ Return success

Login Page with Form

app/login/page.tsx
'use client';

import { useState } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';

export default function LoginPage() {
  const router = useRouter();
  const searchParams = useSearchParams();
  const returnTo = searchParams.get('returnTo') || '/dashboard';
  
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState('');
  const [loading, setLoading] = useState(false);
  
  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    setError('');
    setLoading(true);
    
    try {
      const response = await fetch('/api/auth/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email, password }),
      });
      
      const data = await response.json();
      
      if (!response.ok) {
        setError(data.error || 'Login failed');
        setLoading(false);
        return;
      }
      
      // Redirect to intended page
      router.push(returnTo);
      router.refresh(); // Refresh to update auth state
    } catch (err) {
      setError('An error occurred');
      setLoading(false);
    }
  }
  
  return (
    <div className="flex min-h-screen items-center justify-center">
      <form onSubmit={handleSubmit} className="w-full max-w-md space-y-4">
        <h1 className="text-2xl font-bold">Login</h1>
        
        {error && (
          <div className="bg-red-50 text-red-600 p-3 rounded">
            {error}
          </div>
        )}
        
        <div>
          <label className="block mb-2">Email</label>
          <input
            type="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            className="w-full border p-2 rounded"
            required
          />
        </div>
        
        <div>
          <label className="block mb-2">Password</label>
          <input
            type="password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
            className="w-full border p-2 rounded"
            required
          />
        </div>
        
        <button
          type="submit"
          disabled={loading}
          className="w-full bg-blue-600 text-white p-2 rounded hover:bg-blue-700 disabled:opacity-50"
        >
          {loading ? 'Logging in...' : 'Login'}
        </button>
      </form>
    </div>
  );
}

// ✅ Login form
// ✅ Error handling
// ✅ Redirect to returnTo
// ✅ Loading state

Authentication File Structure

Complete auth setup with middleware

project-rootImportant
middleware.tsImportant
app
lib

Select a file or folder to see details

Reading User Data in Pages

Server Component Access

app/dashboard/page.tsx
import { headers } from 'next/headers';

export default async function DashboardPage() {
  // Read user info from headers set by middleware
  const headersList = headers();
  const userId = headersList.get('x-user-id');
  const userRole = headersList.get('x-user-role');
  
  if (!userId) {
    // Should not happen if middleware is working
    return <div>Unauthorized</div>;
  }
  
  // Fetch user data
  const user = await db.users.findUnique({
    where: { id: userId },
  });
  
  return (
    <div>
      <h1>Dashboard</h1>
      <p>Welcome, {user?.name}!</p>
      <p>Role: {userRole}</p>
    </div>
  );
}

// ✅ Read headers from middleware
// ✅ Fetch user data
// ✅ Type-safe

API Route Access

app/api/protected/route.ts
import { NextResponse } from 'next/server';

export async function GET(request: Request) {
  // Read user info from headers
  const userId = request.headers.get('x-user-id');
  const userRole = request.headers.get('x-user-role');
  
  if (!userId) {
    return NextResponse.json(
      { error: 'Unauthorized' },
      { status: 401 }
    );
  }
  
  // Use user info
  const data = await fetchUserData(userId);
  
  return NextResponse.json({
    data,
    user: { id: userId, role: userRole },
  });
}

// ✅ Headers from middleware
// ✅ No additional auth check needed
// ✅ User info available

Advanced Authentication Patterns

Refresh Token Pattern

middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { verifyJWT, createJWT } from '@/lib/jwt';

export async function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;
  
  if (pathname.startsWith('/dashboard')) {
    const accessToken = request.cookies.get('accessToken')?.value;
    const refreshToken = request.cookies.get('refreshToken')?.value;
    
    if (!accessToken && !refreshToken) {
      return redirectToLogin(request);
    }
    
    // Try to verify access token
    let payload = accessToken ? await verifyJWT(accessToken) : null;
    
    // Access token expired, try refresh token
    if (!payload && refreshToken) {
      const refreshPayload = await verifyJWT(refreshToken);
      
      if (refreshPayload) {
        // Create new access token
        const newAccessToken = await createJWT({
          userId: refreshPayload.userId,
          email: refreshPayload.email,
          role: refreshPayload.role,
        });
        
        const response = NextResponse.next();
        response.cookies.set('accessToken', newAccessToken, {
          httpOnly: true,
          secure: process.env.NODE_ENV === 'production',
          maxAge: 60 * 15, // 15 minutes
        });
        
        return response;
      }
    }
    
    if (!payload) {
      return redirectToLogin(request);
    }
    
    // Continue with user info
    const requestHeaders = new Headers(request.headers);
    requestHeaders.set('x-user-id', payload.userId);
    
    return NextResponse.next({
      request: { headers: requestHeaders },
    });
  }
  
  return NextResponse.next();
}

function redirectToLogin(request: NextRequest) {
  const loginUrl = new URL('/login', request.url);
  loginUrl.searchParams.set('returnTo', request.nextUrl.pathname);
  return NextResponse.redirect(loginUrl);
}

// ✅ Short-lived access token
// ✅ Long-lived refresh token
// ✅ Automatic token refresh
// ✅ Better security

Multi-Tenant Authentication

middleware.ts
export async function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;
  
  // Extract tenant from subdomain or path
  const hostname = request.headers.get('host') || '';
  const subdomain = hostname.split('.')[0];
  
  // Or from path: /tenant/acme/dashboard
  const tenantFromPath = pathname.split('/')[2];
  
  const tenantId = subdomain !== 'www' ? subdomain : tenantFromPath;
  
  if (pathname.startsWith('/dashboard')) {
    const token = request.cookies.get('token')?.value;
    
    if (!token) {
      return redirectToLogin(request);
    }
    
    const payload = await verifyJWT(token);
    
    if (!payload) {
      return redirectToLogin(request);
    }
    
    // Verify user belongs to tenant
    const hasAccess = await userHasTenantAccess(
      payload.userId,
      tenantId
    );
    
    if (!hasAccess) {
      return NextResponse.redirect(
        new URL('/unauthorized', request.url)
      );
    }
    
    // Pass tenant info
    const requestHeaders = new Headers(request.headers);
    requestHeaders.set('x-tenant-id', tenantId);
    requestHeaders.set('x-user-id', payload.userId);
    
    return NextResponse.next({
      request: { headers: requestHeaders },
    });
  }
  
  return NextResponse.next();
}

async function userHasTenantAccess(
  userId: string,
  tenantId: string
): Promise<boolean> {
  const membership = await db.tenantMemberships.findFirst({
    where: { userId, tenantId },
  });
  return !!membership;
}

// ✅ Extract tenant from subdomain/path
// ✅ Verify tenant access
// ✅ Pass tenant ID to pages
// ✅ Multi-tenant support

Authentication Best Practices

1. Keep Middleware Fast

TYPESCRIPT
// ✅ GOOD: Simple JWT verification
export async function middleware(request: NextRequest) {
  const token = request.cookies.get('token')?.value;
  if (!token) return redirectToLogin(request);
  
  const payload = await verifyJWT(token); // Fast
  if (!payload) return redirectToLogin(request);
  
  return NextResponse.next();
}

// ❌ BAD: Database queries in middleware
export async function middleware(request: NextRequest) {
  const userId = await getUserFromToken(); // Slow!
  const user = await db.users.findUnique({ where: { id: userId } }); // Slow!
  const permissions = await db.permissions.findMany({ ... }); // Slow!
  
  // Middleware runs on EVERY request - keep it fast!
}

// Do complex checks in pages/API routes, not middleware

2. Use Secure Cookie Settings

TYPESCRIPT
// ✅ GOOD: Secure cookie settings
response.cookies.set('token', token, {
  httpOnly: true,       // Prevent JavaScript access
  secure: true,         // HTTPS only
  sameSite: 'lax',      // CSRF protection
  maxAge: 60 * 60 * 24 * 7, // 7 days
  path: '/',
});

// ❌ BAD: Insecure settings
response.cookies.set('token', token, {
  httpOnly: false,  // Vulnerable to XSS!
  secure: false,    // Works on HTTP - dangerous!
  sameSite: 'none', // CSRF vulnerable!
});

// Always use secure settings in production

3. Don't Expose Sensitive Info

TYPESCRIPT
// ✅ GOOD: Generic error messages
if (!payload) {
  return NextResponse.json(
    { error: 'Unauthorized' },
    { status: 401 }
  );
}

// ❌ BAD: Specific error messages
if (!payload) {
  return NextResponse.json(
    { error: 'JWT signature verification failed' }, // Too specific!
    { status: 401 }
  );
}

// Don't reveal implementation details

4. Handle Edge Cases

TYPESCRIPT
// ✅ GOOD: Handle all cases
export async function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;
  
  // Prevent redirect loops
  if (pathname === '/login') {
    return NextResponse.next();
  }
  
  // Handle missing token
  const token = request.cookies.get('token')?.value;
  if (!token) {
    return redirectToLogin(request);
  }
  
  // Handle invalid token
  const payload = await verifyJWT(token);
  if (!payload) {
    const response = redirectToLogin(request);
    response.cookies.delete('token'); // Clear invalid token
    return response;
  }
  
  // Handle expired token
  if (payload.exp && payload.exp < Date.now() / 1000) {
    const response = redirectToLogin(request);
    response.cookies.delete('token');
    return response;
  }
  
  return NextResponse.next();
}

// Handle all edge cases properly

5. Use Environment Variables for Secrets

TYPESCRIPT
// ✅ GOOD: Environment variable
const JWT_SECRET = process.env.JWT_SECRET;

if (!JWT_SECRET) {
  throw new Error('JWT_SECRET not configured');
}

// ❌ BAD: Hard-coded secret
const JWT_SECRET = 'my-secret-key-123'; // NEVER!

// Always use environment variables for secrets

Key Takeaways

  • Middleware runs first - perfect for authentication
  • Session or JWT - both work in middleware
  • Pass user data via headers - available in pages/routes
  • Role-based access control - check permissions in middleware
  • Secure cookies - httpOnly, secure, sameSite
  • Keep it fast - no heavy operations
  • Handle edge cases - expired tokens, missing cookies
  • Generic error messages - don't expose internals

What's Next?

You've mastered authentication with middleware! Next, we'll explore Environment Variables and Configuration—managing secrets, configuring different environments, using NEXT_PUBLIC variables, and building secure, configurable applications. You'll learn proper configuration management!

We'll cover .env files, environment-specific configs, accessing variables in different contexts, and security best practices.

🔐 Security Reminder

Never store JWT secrets in code, always validate tokens before trusting them, use httpOnly cookies to prevent XSS, implement CSRF protection with sameSite, and rotate secrets regularly. Security is critical for authentication!

Test Your Understanding

Question 1 of 4

What's the main advantage of using middleware for authentication?

Master authentication with Next.js middleware! Learn session management, JWT, and role-based access control.

Previous
Introduction to Middleware
Next
Environment Variables and Configuration

Master Next.js Security

Join 2,000+ developers building secure Next.js apps. Get the next lesson on environment variables - 100% FREE.

You might also like

UI Components

Ready-made components for your projects

Developer Tools

Boost productivity with handy generators

HTML Game

Learn HTML concepts through interactive gameplay

Join Our Web Development Training Program

Master frontend & backend development with hands-on projects.

NextJS Tutorials

0 of 70 completed

Your Progress0%

Topics

Getting Started

  • What is Next.js?
  • Next.js vs React - Key Differences
  • Creating Your First Next.js 15 Project
  • Next.js Project Structure Explained
  • App Router vs Pages Router

Routing Fundamentals

  • File-Based Routing Basics
  • Creating Pages with page.tsx
  • Dynamic Routes and Route Parameters
  • Catch-All and Optional Catch-All Routes
  • Route Groups for Organization
  • Parallel Routes
  • Intercepting Routes

Layouts and Pages

  • Understanding Layouts
  • Root Layout and Global Configuration
  • Nested Layouts
  • Templates vs Layouts
  • Loading States with loading.tsx
  • Error Handling with error.tsx

Server and Client Components

  • Understanding Server Components
  • Client Components with 'use client'
  • When to Use Server vs Client Components
  • Component Composition Patterns
  • Passing Props Between Server and Client
  • Server Component Patterns and Best Practices

Data Fetching

  • Fetching Data in Server Components
  • Parallel and Sequential Data Fetching
  • Data Fetching Patterns and Strategies
  • Caching and Revalidation
  • Revalidate and Cache Tags
  • Handling Loading and Error States in Data Fetching

Navigation and Links

  • Link Component Basics
  • useRouter Hook for Programmatic Navigation
  • usePathname and useSearchParams Hooks
  • Active Links and Navigation States
  • Redirects and Navigation Guards

Styling in Next.js

  • CSS Modules in Next.js
  • Tailwind CSS Setup and Configuration
  • Global Styles and CSS Variables
  • Next.js Font Optimization

Images and Media

  • Next.js Image Component Basics
  • Image Optimization and Best Practices
  • Working with Static Assets

Forms and Data Mutations

  • Understanding Server Actions
  • Form Handling with Server Actions
  • Form Validation and Error Handling
  • useFormStatus and useFormState Hooks
  • Revalidating Data After Mutations
  • Optimistic Updates

Metadata and SEO

  • Static Metadata Configuration
  • Dynamic Metadata Generation
  • Open Graph and Social Media Cards
  • Sitemap and Robots.txt

API Routes and Route Handlers

  • Route Handlers Introduction
  • GET and POST Request Handlers
  • Dynamic API Routes
  • Request and Response Objects
  • API Error Handling and Status Codes

Middleware and Advanced Features

  • Introduction to Middleware
  • Authentication with Middleware
  • Environment Variables and Configuration
  • Streaming and Suspense
  • Not Found and Global Error Pages

Deployment and Optimization

  • Understanding Static and Dynamic Rendering
  • generateStaticParams for Static Generation
  • Build and Production Optimization
  • Deploying to Vercel
  • Alternative Deployment Options
  • Performance Monitoring and Analytics

Real-World Projects

  • Project 1: E-commerce Product Catalog
  • Project 2: Dashboard with Authentication
Falytom logoFALYTOM

Learn technologies like HTML, CSS, JavaScript, React, NodeJS and more through interactive games, tutorials, components and free tools. Transform your businesses with AI chatbots, SEO-optimized websites, and professional development services.

© 2025 Tomilola Group
  • Facebook logo
  • Twitter logo
  • Instagram logo
  • LinkedIn logo
  • GitHub logo
  • YouTube logo