Skip to content

Instantly share code, notes, and snippets.

@yeasin2002
Created June 29, 2025 18:48
Show Gist options
  • Select an option

  • Save yeasin2002/d40d86b12ba782c000d01d965834fa63 to your computer and use it in GitHub Desktop.

Select an option

Save yeasin2002/d40d86b12ba782c000d01d965834fa63 to your computer and use it in GitHub Desktop.
minimal-auth
/*
File: contexts/AuthContext.tsx
Description: React context for authentication using JWT stored in HTTP-only cookies.
*/
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';

// --- Types ---
export interface User {
  id: string;
  email: string;
  name?: string;
}

export interface AuthContextType {
  user: User | null;
  loading: boolean;
  signIn: (email: string, password: string) => Promise<void>;
  signOut: () => Promise<void>;
}

// --- Context ---
const AuthContext = createContext<AuthContextType | undefined>(undefined);

// --- Provider ---
export const AuthProvider = ({ children }: { children: ReactNode }) => {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);

  // fetch current user on mount
  useEffect(() => {
    fetch('/api/auth/session')
      .then(res => res.ok ? res.json() : Promise.reject())
      .then((data: { user: User }) => setUser(data.user))
      .catch(() => setUser(null))
      .finally(() => setLoading(false));
  }, []);

  const signIn = async (email: string, password: string) => {
    setLoading(true);
    const res = await fetch('/api/auth/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, password }),
    });
    if (!res.ok) {
      setLoading(false);
      throw new Error('Invalid credentials');
    }
    const { user: logged } = await res.json();
    setUser(logged);
    setLoading(false);
  };

  const signOut = async () => {
    setLoading(true);
    await fetch('/api/auth/logout', { method: 'POST' });
    setUser(null);
    setLoading(false);
  };

  return (
    <AuthContext.Provider value={{ user, loading, signIn, signOut }}>
      {children}
    </AuthContext.Provider>
  );
};

// --- Hook ---
export const useAuth = (): AuthContextType => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within AuthProvider');
  }
  return context;
};


/*
File: middleware.ts
Description: Protect routes based on presence of JWT in cookies.
*/
import { NextResponse, NextRequest } from 'next/server';
import { jwtVerify } from 'jose';

const PUBLIC_FILE = /\.(.*)$/;
const AUTH_TOKEN_NAME = 'token';
const LOGIN_PATH = '/login';

export async function middleware(req: NextRequest) {
  const { pathname } = req.nextUrl;

  // skip public files and API routes and login page
  if (
    pathname.startsWith('/api') ||
    pathname === LOGIN_PATH ||
    PUBLIC_FILE.test(pathname)
  ) {
    return NextResponse.next();
  }

  const token = req.cookies.get(AUTH_TOKEN_NAME)?.value;
  if (!token) {
    const url = req.nextUrl.clone();
    url.pathname = LOGIN_PATH;
    return NextResponse.redirect(url);
  }

  try {
    // verify JWT (HS256 secret from env)
    await jwtVerify(token, new TextEncoder().encode(process.env.JWT_SECRET));
    return NextResponse.next();
  } catch {
    const url = req.nextUrl.clone();
    url.pathname = LOGIN_PATH;
    return NextResponse.redirect(url);
  }
}

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

/*
File: next-env.d.ts (for jose types)
*/
/// <reference types="next" />
/// <reference types="next/types/global" />

Next.js Minimal Auth Setup

A lightweight, fully type-safe JWT authentication setup for Next.js (13+), using HTTP-only cookies and the jose library. This boilerplate provides:

  • React Context (AuthContext) for client-side auth state management
  • API routes for login, logout, and session retrieval
  • Next.js Middleware to protect routes and redirect unauthenticated users
  • TypeScript interfaces for strong typing

Features

  • Minimal & Opinionated: Zero bloat, focused solely on JWT-based auth.
  • Secure: Stores JWT in HTTP-only cookies; verifies tokens in middleware.
  • Type-Safe: All interfaces and hooks written in TypeScript.
  • Easy to Extend: Plug in your own login logic, add refresh tokens, or integrate with a database.

Prerequisites

  • Node.js ≥ 18
  • Next.js ≥ 13
  • TypeScript

Installation

  1. Clone the repo

    git clone https://github.com/your-org/nextjs-auth-minimal.git
    cd nextjs-auth-minimal
  2. Install dependencies

    npm install
    # or
    yarn install
  3. Set up environment variables

    Create a .env.local file at the project root:

    # JWT secret for signing/verifying tokens
    JWT_SECRET=your_super_secret_key
    
    # Cookie settings
    NEXTAUTH_URL=http://localhost:3000

Project Structure

nextjs-auth-minimal/
├── contexts/
│   └── AuthContext.tsx      # React AuthProvider & useAuth hook
├── pages/
│   ├── api/
│   │   ├── auth/
│   │   │   ├── login.ts     # POST: { email, password } → sets HTTP-only JWT cookie
│   │   │   ├── logout.ts    # POST: clears cookie
│   │   │   └── session.ts   # GET: returns { user } if JWT valid
│   └── protected-page.tsx   # Example protected page
├── middleware.ts            # Protects routes via jwtVerify
├── next-env.d.ts            # Type references for Next.js + jose
└── README.md                # This file

Usage

1. Wrap your app with AuthProvider

In app/layout.tsx or pages/_app.tsx:

import { AuthProvider } from '../contexts/AuthContext';

export default function App({ Component, pageProps }) {
  return (
    <AuthProvider>
      <Component {...pageProps} />
    </AuthProvider>
  );
}

2. Protect UI with useAuth

import { useAuth } from '../contexts/AuthContext';

export default function ProfilePage() {
  const { user, loading, signOut } = useAuth();

  if (loading) return <p>Loading...</p>;
  if (!user) return <p>Not signed in</p>;

  return (
    <div>
      <h1>Welcome, {user.name ?? user.email}!</h1>
      <button onClick={signOut}>Sign Out</button>
    </div>
  );
}

3. API Routes

  • POST /api/auth/login

    • Expects { email, password }
    • Validates credentials, returns { user }, sets JWT cookie
  • POST /api/auth/logout

    • Clears cookie
  • GET /api/auth/session

    • Returns { user } if JWT valid, otherwise 401

Customize these routes to connect to your database or external auth service.


Middleware Configuration

The middleware.ts file uses Next.js Middleware to protect routes:

  • Skips /api, /login, and static files
  • Verifies token cookie via jose / HS256
  • Redirects unauthenticated requests to /login
  • Apply to paths in config.matcher (default: / and /protected/:path*)

Modify matcher as needed for your app’s routing.


Extending the Boilerplate

  • Refresh Tokens: Add a second cookie and rotate tokens in middleware.
  • RBAC / Permissions: Enhance JWT payload with roles, and check in middleware.
  • OAuth: Swap login route logic to integrate with OAuth providers.

License

MIT © Your Name

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment