
Security News
Axios Supply Chain Attack Reaches OpenAI macOS Signing Pipeline, Forces Certificate Rotation
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.
next-auth-session
Advanced tools
Session management package for Next.js with JWT encryption, route protection, and React hooks
A simple, flexible session management library for Next.js 14+ with App Router support.
joseuseSession, useUser, useSessionExpiry and morenpm install next-auth-session
# or
pnpm add next-auth-session
# or
yarn add next-auth-session
// Server-side: Factory and utilities
import { NextAuthSession, route, patterns } from "next-auth-session";
// Client-side: Provider and hooks
import {
SessionProvider,
useSession,
useUser,
useSessionExpiry,
useRequireAuth
} from "next-auth-session/client";
SESSION_SECRET=your-secret-key-at-least-32-characters
Generate a secret:
openssl rand -base64 32
Create lib/auth.ts:
import { NextAuthSession, route, patterns } from "next-auth-session";
export const {
// Session operations
createSession,
getSession,
updateSession,
refreshSession,
deleteSession,
isAuthenticated,
// Utilities
encryptSession,
decryptSession,
isSessionExpired,
shouldRefreshSession,
// Middleware & Handlers
middleware,
handlers,
// Server Actions - use with client components
actions,
// Config
config,
clientConfig,
} = NextAuthSession({
session: {
sessionExpiry: 60 * 60 * 24, // 1 day in seconds
cookieName: "session",
autoRefresh: true,
},
middleware: {
routes: [
route.public("/login"),
route.public("/register"),
route.auth(patterns.dashboard), // /dashboard/*
route.hybrid("/"),
],
loginPath: "/login",
homePath: "/",
},
});
Create app/api/session/[[...session]]/route.ts:
import { handlers } from "@/lib/auth";
export const { GET, POST, PATCH, DELETE } = handlers();
Create middleware.ts:
import { middleware } from "@/lib/auth";
export default middleware();
export const config = {
matcher: ["/((?!api|_next|static|.*\\..*).*)"],
};
Or with custom logic:
import { middleware } from "@/lib/auth";
import { NextRequest, NextResponse } from "next/server";
export default middleware((request, isAuthenticated, rule) => {
// Custom logic here
// Return NextResponse to override default behavior
// Return undefined to use default behavior
});
import { createSession } from "@/lib/auth";
import { redirect } from "next/navigation";
export async function loginAction(formData: FormData) {
"use server";
// Validate credentials...
const user = await validateUser(formData);
if (!user) {
return { error: "Invalid credentials" };
}
const result = await createSession(user, {
accessToken: "optional-token",
refreshToken: "optional-refresh-token",
});
if (result.success) {
redirect("/dashboard");
}
return { error: result.error };
}
import { getSession } from "@/lib/auth";
export default async function DashboardPage() {
const session = await getSession();
if (!session) {
return <div>Not authenticated</div>;
}
return <div>Welcome, {session.user.name}</div>;
}
For client components, use the SessionProvider:
// app/providers.tsx
"use client";
import { SessionProvider } from "next-auth-session/client";
export function Providers({ children }: { children: React.ReactNode }) {
return <SessionProvider>{children}</SessionProvider>;
}
// app/layout.tsx
import { Providers } from "./providers";
export default function RootLayout({ children }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}
// components/UserMenu.tsx
"use client";
import { useSession } from "next-auth-session/client";
export function UserMenu() {
const { session, status, logout, refresh } = useSession();
if (status === "loading") return <div>Loading...</div>;
if (status === "unauthenticated") return <LoginButton />;
return (
<div>
<span>{session.user.name}</span>
<button onClick={logout}>Logout</button>
</div>
);
}
The actions object contains server-ready functions for client components:
// lib/actions.ts
"use server";
import { actions } from "@/lib/auth";
export async function refreshAction() {
return actions.refresh();
}
export async function logoutAction() {
return actions.logout();
}
export async function updateAction(updates: { name?: string }) {
return actions.update({ user: updates });
}
// components/RefreshButton.tsx
"use client";
import { refreshAction, logoutAction } from "@/lib/actions";
export function RefreshButton() {
return (
<button onClick={() => refreshAction()}>
Refresh Session
</button>
);
}
NextAuthSession({
session: {
// Required: Secret key (defaults to SESSION_SECRET env var)
secretKey: process.env.SESSION_SECRET,
// Session expiry in seconds (default: 7 days)
sessionExpiry: 60 * 60 * 24 * 7,
// Auto-refresh threshold in seconds (default: 1 day)
refreshThreshold: 60 * 60 * 24,
// Cookie name (default: "session")
cookieName: "session",
// Auto-refresh session on read (default: true)
autoRefresh: true,
// Secure cookie in production (default: true in production)
secureCookie: process.env.NODE_ENV === "production",
// SameSite cookie attribute (default: "lax")
sameSite: "lax",
// Custom validation callback
onValidate: async (session) => {
// Return true if valid, false to invalidate
return true;
},
// Custom refresh callback
onRefresh: async (session) => {
// Return updated session or null
return session;
},
},
});
NextAuthSession({
middleware: {
// Route rules
routes: [
route.public("/login"), // Only for unauthenticated users
route.auth("/dashboard/*"), // Only for authenticated users
route.hybrid("/"), // For everyone
],
// Default route type (default: "hybrid")
defaultType: "hybrid",
// Redirect paths
loginPath: "/login",
homePath: "/",
// i18n locale prefixes to strip
locales: ["en", "ar"],
// Custom authorization
authorize: async (isAuthenticated, request, rule) => {
// Return NextResponse to override
// Return undefined for default behavior
},
},
});
public: Only accessible to unauthenticated users. Authenticated users are redirected to homePath.auth: Only accessible to authenticated users. Unauthenticated users are redirected to loginPath.hybrid: Accessible to everyone. No redirects.import { route, patterns } from "next-auth-session";
// Built-in patterns
patterns.api // "/api/*"
patterns.dashboard // "/dashboard/*"
patterns.admin // "/admin/*"
patterns.settings // "/settings/*"
patterns.profile // "/profile/*"
// Custom patterns
route.auth("/dashboard/*") // Matches /dashboard and /dashboard/anything
route.public("/login") // Exact match
route.hybrid("/blog*") // Matches /blog, /blog/post, /blogs
interface Session<T extends User> {
user: T;
accessToken?: string;
refreshToken?: string;
expiresAt: number; // Unix timestamp in ms
expiryDate: string; // ISO date string
createdAt: number; // Unix timestamp in ms
lastActivity: number; // Unix timestamp in ms
}
interface User {
id: string;
email?: string;
name?: string;
image?: string;
[key: string]: unknown;
}
| Function | Description |
|---|---|
createSession(user, tokens?) | Create a new session |
getSession() | Get current session |
updateSession(updates) | Update session data |
refreshSession() | Extend session expiry |
deleteSession() | Delete session |
isAuthenticated() | Check if user is authenticated |
middleware(customHandler?) | Create middleware function |
handlers() | Create API route handlers |
encryptSession(session) | Encrypt session to JWT |
decryptSession(token) | Decrypt JWT to session |
isSessionExpired(session) | Check if session is expired |
shouldRefreshSession(session) | Check if session needs refresh |
actions | Server actions: { refresh, update, logout } |
config | The resolved session config |
clientConfig | Pre-configured props for SessionProvider |
import { useSession, useUser, useSessionExpiry, useRequireAuth } from "next-auth-session/client";
// useSession - Full session access
const {
session, // Current session or null
status, // "loading" | "authenticated" | "unauthenticated"
refresh, // Refresh session function
update, // Update session function
} = useSession();
// useUser - User-focused access
const {
user, // User object or null
isAuthenticated, // Boolean
isLoading, // Boolean
} = useUser();
// useSessionExpiry - Track session expiry
const {
expiresAt, // Unix timestamp or null
expiresIn, // Milliseconds until expiry
isExpiringSoon, // True if expiring within 1 hour
recalculate, // Manually recalculate values
} = useSessionExpiry();
// useRequireAuth - Redirect if not authenticated
const { isLoading, isAuthenticated } = useRequireAuth("/login");
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | required | Child components |
initialSession | Session | null | null | Pre-fetched session from server |
fetchOnMount | boolean | true | Fetch session when component mounts |
refreshInterval | number | undefined | Auto-refresh interval in ms |
refetchOnPathChange | boolean | true | Refetch session on route change |
autoActivityRefresh | boolean | true | Auto-refresh based on refreshThreshold when user is active |
activityRefreshInterval | number | undefined | Custom activity refresh interval (overrides server) |
onSessionChange | (session) => void | undefined | Callback when session changes |
The SessionProvider automatically fetches refreshThreshold from the API and uses it for activity-based session refresh. When a user is active (mouse, keyboard, touch, scroll), the session is refreshed at the configured threshold interval.
import { useSessionExpiry } from "next-auth-session/client";
function SessionTimer() {
const { expiresAt, expiresIn, isExpiringSoon, recalculate } = useSessionExpiry();
// Manually recalculate expiry time
useEffect(() => {
const interval = setInterval(recalculate, 1000);
return () => clearInterval(interval);
}, [recalculate]);
if (!expiresAt) return null;
return (
<div>
{isExpiringSoon && <span>Session expiring soon!</span>}
<span>Expires in: {Math.floor((expiresIn ?? 0) / 1000)}s</span>
</div>
);
}
| Property | Type | Description |
|---|---|---|
expiresAt | number | null | Unix timestamp when session expires |
expiresIn | number | null | Milliseconds until expiry |
isExpiringSoon | boolean | True if expiring within 1 hour |
recalculate | () => void | Manually recalculate expiry values |
import type { User, Session } from "next-auth-session";
interface MyUser extends User {
id: string;
email: string;
name: string;
role: "admin" | "user";
permissions: string[];
}
// Type-safe session operations
const session = await getSession<MyUser>();
session?.user.role; // "admin" | "user"
// lib/auth.ts
export const { middleware } = NextAuthSession({
middleware: {
routes: [
route.public("/login"),
route.auth("/dashboard/*"),
],
locales: ["en", "ar", "es"], // Strip locale prefix when matching
loginPath: "/login",
homePath: "/dashboard",
},
});
// middleware.ts
import { middleware } from "@/lib/auth";
import createIntlMiddleware from "next-intl/middleware";
const intlMiddleware = createIntlMiddleware({
locales: ["en", "ar", "es"],
defaultLocale: "en",
});
export default async function combinedMiddleware(request: NextRequest) {
const response = intlMiddleware(request);
if (response.ok) {
const authResponse = await middleware()(request);
if (authResponse.status !== 200) {
return authResponse;
}
}
return response;
}
// app/layout.tsx
import { getSession } from "@/lib/auth";
import { SessionProvider } from "next-auth-session/client";
export default async function RootLayout({ children }) {
const session = await getSession();
return (
<html>
<body>
<SessionProvider
initialSession={session}
fetchOnMount={false}
>
{children}
</SessionProvider>
</body>
</html>
);
}
interface User {
id: string;
[key: string]: unknown;
}
interface Session<T extends User = User> {
user: T;
accessToken?: string;
refreshToken?: string;
expiresAt: number;
expiryDate: string;
createdAt: number;
lastActivity?: number;
}
interface SessionConfig {
secretKey?: string;
sessionExpiry?: number;
refreshThreshold?: number;
cookieName?: string;
autoRefresh?: boolean;
secureCookie?: boolean;
sameSite?: "strict" | "lax" | "none";
onRefresh?: (session: Session) => Promise<Session | null>;
onValidate?: (session: Session) => Promise<boolean>;
}
interface MiddlewareConfig {
routes?: RouteRule[];
defaultType?: RouteType;
loginPath?: string;
homePath?: string;
locales?: string[];
authorize?: (isAuth: boolean, req: NextRequest, rule?: RouteRule) => NextResponse | void;
}
type RouteType = "public" | "auth" | "hybrid";
interface RouteRule {
pattern: string;
type: RouteType;
}
MIT
FAQs
Session management package for Next.js with JWT encryption, route protection, and React hooks
We found that next-auth-session demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.

Security News
Open source is under attack because of how much value it creates. It has been the foundation of every major software innovation for the last three decades. This is not the time to walk away from it.

Security News
Socket CEO Feross Aboukhadijeh breaks down how North Korea hijacked Axios and what it means for the future of software supply chain security.