JSPM

@waelhabbaldev/next-jwt-auth

0.5.0-beta
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 1
  • Score
    100M100P100Q59022F
  • License MIT

A secure, lightweight JWT authentication solution for Next.js, providing access and refresh token handling, middleware support, and easy React integration.

Package Exports

  • @waelhabbaldev/next-jwt-auth
  • @waelhabbaldev/next-jwt-auth/client

Readme

@waelhabbaldev/next-jwt-auth

NPM Version License: MIT

Next.js JWT Auth

Declarative, Secure, Enterprise-Grade Authentication for Next.js

A lightweight, secure, and performance-optimized authentication library for the Next.js App Router. It implements a robust, multi-layered security model using JWTs and provides a simple, declarative API to protect your pages, server actions, and API routes.


Features

  • State-of-the-Art Security: Automatic Refresh Token Rotation and JTI-based Reuse Detection to protect against token theft and session hijacking.
  • Flexible JWT Algorithms: Supports both symmetric (HS256) and asymmetric (RS256) algorithms out of the box.
  • Zero-Downtime Key Rotation: Built-in support for JWT Key Rotation (kid) to allow for seamless, zero-downtime secret updates.
  • Multi-Factor Authentication (MFA): Easily add a second factor of authentication (e.g., TOTP) to your sign-in flow.
  • Built-in CSRF Protection: Double Submit Cookie pattern automatically protects all Server Actions from Cross-Site Request Forgery.
  • Rate Limiting: Pluggable rate-limiting support to protect your signIn endpoint against brute-force attacks.
  • Declarative Protection Guards: Secure your application with a single line of code using protectPage(), protectAction(), and protectApi().
  • Optimized for Performance: Middleware-based session validation and request-level caching (React.cache) automatically prevent redundant database calls.
  • Flexible Authorization: Implement Role-Based (RBAC) or Attribute-Based (ABAC) access control with a simple authorize callback in any protection guard.
  • Extensible: Supports social/OAuth providers, custom logging, and customizable error messages.
  • Session Versioning: Instantly invalidate all of a user's sessions from the server-side by incrementing a version number in the database.
  • Next.js Ready: Built for the App Router with first-class support for Server Components, Server Actions, API Routes, and Middleware.

Installation

bun add @waelhabbaldev/next-jwt-auth jose
# or
npm install @waelhabbaldev/next-jwt-auth jose
# or
yarn add @waelhabbaldev/next-jwt-auth jose

Note: jose is a peer dependency and must be installed alongside the package.


Database Schema Requirements

Your database needs two tables to support all security features.

1. users Table

Must include version and isForbidden columns. For MFA, add hasMFA.

CREATE TABLE `users` (
  -- ... other user columns (id, email, passwordHash, etc.)
  `version` INT UNSIGNED NOT NULL DEFAULT 1,
  `isForbidden` BOOLEAN NOT NULL DEFAULT FALSE,
  `hasMFA` BOOLEAN NOT NULL DEFAULT FALSE
);

2. revokedTokens Table

Required for Refresh Token Reuse Detection, the core defense against token theft.

CREATE TABLE `revokedTokens` (
    `jti` VARCHAR(36) NOT NULL COMMENT 'The JWT ID, typically a UUID.',
    `expiresAt` TIMESTAMP NOT NULL COMMENT 'When the token can be safely deleted.',
    PRIMARY KEY (`jti`),
    INDEX `IX_revokedTokens_expiresAt` (`expiresAt`)
);

Note: You should run a scheduled job to periodically delete expired JTIs: DELETE FROM revokedTokens WHERE expiresAt < NOW();.


Quick Start

1. Define your User Identity and DAL

Implement the UserIdentityDAL interface to connect the library to your database. This is the bridge between the auth logic and your data.

// src/lib/auth-dal.ts
import type { UserIdentity, UserIdentityDAL } from "@waelhabbaldev/next-jwt-auth";
import db from "./db"; // Your database client
import { compare } from "bcrypt"; // Example password hashing library

// Define a type for your user identity that extends the base UserIdentity
export interface AppUserIdentity extends UserIdentity {
  id: number;
  email: string;
}

export const authDal: UserIdentityDAL<AppUserIdentity> = {
  // Find a user by their credentials (e.g., email and password)
  async fetchIdentityByCredentials(email, password) {
    const user = await db.user.findUnique({ where: { email } });
    if (!user) return null;

    const isPasswordValid = await compare(password, user.passwordHash);
    if (!isPasswordValid) return null;

    // Return the full user identity object on success
    return { ...user, roles: user.roles.split(',') };
  },

  // Find a user for an existing session
  async fetchIdentityForSession(identifier) {
    const user = await db.user.findUnique({ where: { id: Number(identifier) } });
    if (!user) return null;
    return { ...user, roles: user.roles.split(',') };
  },

  // Invalidate all sessions by incrementing the user's version
  async invalidateAllSessionsForIdentity(identifier) {
    await db.user.update({
      where: { id: Number(identifier) },
      data: { version: { increment: 1 } },
    });
  },

  // Check if a JTI has been used (replay attack defense)
  async isTokenJtiUsed(jti) {
    const token = await db.revokedToken.findUnique({ where: { jti } });
    return !!token;
  },

  // Mark a JTI as used
  async markTokenJtiAsUsed(jti, expirationInSeconds) {
    const expiresAt = new Date(Date.now() + expirationInSeconds * 1000);
    await db.revokedToken.create({ data: { jti, expiresAt } });
  },

  // Optional: Implement your TOTP or other MFA verification logic
  async verifyMFA(identifier, code) {
    // Example: const isValid = totp.verify({ token: code, ... });
    return true;
  },
};

2. Configure and Export the Auth Instance

Create a central file (src/lib/auth.ts) to configure your auth object. Store secrets in environment variables.

// src/lib/auth.ts
import { createAuth } from "@waelhabbaldev/next-jwt-auth";
import { authDal, AppUserIdentity } from "./auth-dal";
import { checkRateLimit } from "./rate-limiter"; // Your rate limit logic

export const {
  createMiddleware,
  getSession,
  getCsrfToken,
  signIn,
  signOut,
  protectPage,
  protectAction,
  protectApi,
} = createAuth<AppUserIdentity>({
  // Required
  dal: authDal,
  baseUrl: process.env.BASE_URL!, // e.g., "http://localhost:3000"
  secrets: {
    accessTokenSecret: process.env.ACCESS_TOKEN_SECRET!,
    refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET!,
  },
  
  cookies: {
    access: { name: "__at", maxAge: 15 * 60 }, // 15 minutes
    refresh: { name: "__rt", maxAge: 30 * 24 * 60 * 60 }, // 30 days
  },

  redirects: {
    unauthenticated: "/signin",
    unauthorized: "/dashboard?error=unauthorized",
    forbidden: "/signin?error=forbidden",
  },
  
  // Recommended Security Features
  refreshTokenRotationIntervalSeconds: 7 * 24 * 60 * 60, // 7 days
  csrfEnabled: true,
  
  // Optional Features
  rateLimit: checkRateLimit,
  debug: process.env.NODE_ENV === "development",
});

3. Set Up Middleware for Session Management

The middleware is essential for handling automatic session refreshing and caching identity for performance.

// middleware.ts
import { createMiddleware } from "./lib/auth";

export default createMiddleware();

export const config = {
  /**
   * Forces 'nodejs' runtime. Required for full Node.js APIs (e.g., database clients) 
   * as the default 'edge' runtime restricts I/O and networking features.
 */
  runtime:'nodejs',
  // Match all paths except for static assets and API routes.
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};

Protecting Your Application

Protecting Pages and Layouts

Use protectPage() to secure any Server Component. It guarantees a valid session or redirects.

// app/dashboard/layout.tsx
import { protectPage } from "@/lib/auth";

export default async function DashboardLayout({ children }) {
  const session = await protectPage();
  // `session.identity` is guaranteed to be valid and available here.
  return <main>{children}</main>;
}

Protecting Server Actions

Use protectAction() to secure Server Actions. This automatically validates the CSRF token.

// app/actions.ts
"use server";
import { protectAction } from "@/lib/auth";
import { CsrfError } from "@waelhabbaldev/next-jwt-auth";

export async function sensitiveAction(formData: FormData) {
  try {
    // Pass the formData to enable the automatic CSRF check.
    const session = await protectAction(undefined, formData);
    // User is authenticated and authorized.
    // ... logic for the action ...
  } catch (error) {
    if (error instanceof CsrfError) {
      return { error: "Invalid CSRF token." };
    }
    // Handle other auth errors (NotAuthenticatedError, ForbiddenError)
  }
}

CSRF Protection with Forms

For forms using Server Actions, you must provide the CSRF token. This is done by generating the token on the server page and passing it to a Client Component.

1. Generate the token in your Server Page/Component:

// app/settings/page.tsx (Server Component)
import { getCsrfToken } from '@/lib/auth';
import { CsrfProvider } from '@waelhabbaldev/next-jwt-auth/client';
import { SettingsForm } from './settings-form'; // Your form is a Client Component

export default async function SettingsPage() {
  const csrfToken = await getCsrfToken();

  return (
    <CsrfProvider token={csrfToken}>
      <SettingsForm />
    </CsrfProvider>
  );
}

2. Use the CsrfInput in your Client Component form:

Place the <CsrfInput /> component inside your form. It automatically reads the token from the provider.

// components/settings-form.tsx (Client Component)
"use client";
import { CsrfInput } from "@waelhabbaldev/next-jwt-auth/client";
import { sensitiveAction } from "@/app/actions";

export function SettingsForm() {
  return (
    <form action={sensitiveAction}>
      <CsrfInput />
      {/* ... rest of your form inputs */}
      <button type="submit">Save</button>
    </form>
  );
}

Protecting API Routes

Use protectApi(). On failure, it returns a response object that you must immediately return.

// app/api/projects/[id]/route.ts
import { protectApi } from "@/lib/auth";
import { NextResponse } from "next/server";

export async function GET(req, { params }) {
  const result = await protectApi();
  
  // If `response` is present, auth failed. Return it.
  if (result.response) return result.response;
  
  // `result.session` is guaranteed to be valid here.
  const { session } = result;
  
  return NextResponse.json({ data: `secret data for ${session.identity.email}` });
}

Client-Side Usage (React Hooks & Components)

This library provides React components and hooks for managing authentication state in Client Components. These must be imported from the /client entry point.

import { AuthProvider, useAuth } from '@waelhabbaldev/next-jwt-auth/client';

Setting up AuthProvider

Wrap your application layout with AuthProvider to make the session available globally.

1. Create Server Actions for the client to use:

// app/auth/actions.ts
'use server';
import { getSession, signIn, signOut } from '@/lib/auth';
import { AuthError } from '@waelhabbaldev/next-jwt-auth';

// Action to get the current session
export async function getSessionAction() {
  return getSession();
}

// Action to sign in
export async function signInAction(identifier: string, secret: string) {
  try {
    return await signIn(identifier, secret);
  } catch (error) {
    if (error instanceof AuthError) {
      // Re-throw specific auth errors to be handled by the client
      throw error;
    }
    throw new Error('An unknown error occurred.');
  }
}

// Action to sign out
export async function signOutAction() {
  return signOut();
}

2. Create a client-side provider wrapper:

// app/providers.tsx
'use client';
import { AuthProvider } from '@waelhabbaldev/next-jwt-auth/client';
import { getSessionAction, signInAction, signOutAction } from '@/app/auth/actions';

export function Providers({ children }) {
  return (
    <AuthProvider
      sessionFetcher={getSessionAction}
      signInAction={signInAction}
      signOutAction={signOutAction}
    >
      {children}
    </AuthProvider>
  );
}

3. Use the provider in your root layout:

// app/layout.tsx
import { Providers } from './providers';

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

Using the useAuth Hook

Now, any Client Component can access the authentication state and actions.

// components/user-button.tsx
'use client';
import { useAuth } from '@waelhabbaldev/next-jwt-auth/client';

export function UserButton() {
  const { identity, isAuthenticated, isLoading, signOut } = useAuth<AppUserIdentity>();

  if (isLoading) return <div>Loading...</div>;

  if (!isAuthenticated) {
    return <a href="/signin">Sign In</a>;
  }

  return (
    <div>
      <span>Welcome, {identity.email}!</span>
      <button onClick={() => signOut()}>Sign Out</button>
    </div>
  );
}```

---

## API Reference

### Server-Side API (`@waelhabbaldev/next-jwt-auth`)

| Method                | Description                                                                 |
| --------------------- | --------------------------------------------------------------------------- |
| `createAuth()`        | Creates and configures the main authentication instance.                    |
| `protectPage()`       | Secures Pages/Layouts. Guarantees a session or redirects on failure.        |
| `protectAction()`     | Secures Server Actions. Throws `AuthError` on failure. Auto-validates CSRF. |
| `protectApi()`        | Secures API Routes. Returns a `{ session }` or `{ response }` object.       |
| `getSession()`        | **(Fast)** Gets the session without protection. Reads from cache. Can be `null`. |
| `getCsrfToken()`      | Generates a CSRF token for use with `CsrfProvider`.                         |
| `createMiddleware()`  | **(Essential)** Creates the middleware for session management and rotation. |
| `signIn()`            | Authenticates a user and sets session cookies.                              |
| `signOut()`           | Deletes session cookies and invalidates the user's sessions.                |
| `verifyAccessToken()` | A utility to verify an access token's signature without hitting the DB.     |

### Client-Side API (`@waelhabbaldev/next-jwt-auth/client`)

| Component/Hook | Description                                                                     |
| -------------- | ------------------------------------------------------------------------------- |
| `AuthProvider` | React Context provider for making session state available to Client Components. |
| `useAuth()`    | React hook to access session state (`identity`, `isAuthenticated`, `signIn`, `signOut`, etc.). |
| `CsrfProvider` | Provides a CSRF token (generated on the server) to descendant components.     |
| `CsrfInput`    | A hidden input that automatically includes the CSRF token in a form.            |

---

## Keywords

`nextjs`, `authentication`, `authorization`, `jwt`, `auth`, `security`, `rbac`, `abac`, `session-management`, `access-token`, `refresh-token`, `middleware`, `mfa`, `oauth`, `rate-limiting`, `csrf`, `key-rotation`, `nextjs-auth`, `app-router`, `jose`

---

## License

MIT License