-
Notifications
You must be signed in to change notification settings - Fork 30.3k
Description
Issue: Logging Out Users on 401 Error in Next.js with auth.js
Scenario:
While fetching data on the server side, I use a custom wrapper function based on fetch as a centralized API handler for my application. When the server returns a 401 (Unauthorized) error, I expected to be able to log the user out (or take similar actions), just like how Axios interceptors handle such cases.
Initial Implementation:
To handle this, I implemented the following logic in my fetch function:
- I use
auth.js (next-auth)to manage user sessions. - If a 401 error occurs, I call
signOutto log the user out.
Problem:
When executing signOut, the following error occurs:
Error: Cookies can only be modified in a Server Action or Route Handler.
This makes sense, as fetch runs on the server, and Next.js does not allow direct modification of cookies at this level.
(This issue has also been discussed in a previous GitHub Issue.)
Even manually attempting to delete cookies did not work.
I tried the following approaches, but none resolved the issue:
- Calling
signOutdirectly on the server - Executing
signOutinside a Server Action - Calling
signOutfrom within a Route Handler
Proposed Solution:
To resolve this issue, I created a Route Handler and implemented the logout operation within it.
Instead of calling signOut directly inside the fetch function, I created an API Route that handles the logout process.
🚀 The trick:
- In the
fetchfunction, when a 401 error is detected, I redirect the user to this API Route. - The
GETmethod of this route immediately logs the user out. - This approach is similar to how many authentication services handle session invalidation.
Implementation:
1. Centralized Fetch Function
/* eslint-disable @typescript-eslint/no-explicit-any */
"server-only";
import { redirectTo } from "@/actions";
import { auth } from "@/auth";
type FetchApiResponse<T, ET> = {
status: number;
isError: boolean;
errorData: ET | null;
data: T | null;
message: string | null;
};
// Fetch API Proxy (Middleware)
async function fetchApiProxy<T, ET = any>(
url: string,
options?: RequestInit & { authorize?: boolean },
) {
const res = await fetchApi<T, ET>(url, options);
if (res.status === 401 || res.message === "UnAuthentication") {
await redirectTo("/api/logout");
}
return res;
}
// Main Fetch API
async function fetchApi<T, ET = any>(
url: string,
options?: RequestInit & { authorize?: boolean },
): Promise<FetchApiResponse<T, ET>> {
try {
const session = await auth();
const res = await fetch(`${process.env.API_BASE_URL}${url}`, {
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
...(options?.authorize && {
Authorization: `Bearer ${session?.accessToken}`,
}),
},
});
const data = await res.json();
if (!res.ok) {
return {
isError: true,
message: data?.message || res.statusText || "Unknown error",
data: null,
status: res.status,
errorData: data?.data ?? null,
};
}
return {
isError: false,
message: data?.message || null,
data: data?.data,
errorData: null,
status: res.status,
};
} catch (error) {
console.log({ fetchApiError: error });
return {
isError: true,
message: error instanceof Error ? error.message : "Unknown error",
data: null,
status: 500,
errorData: null,
};
}
}
export { fetchApiProxy as fetchApi };
export { fetchApi as fetchApiRaw };
2. Logout Route Handler
import { signOut } from "@/auth";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
export async function GET() {
const cookieStore = cookies();
const cookieKeys = ["authjs.session-token", "next-auth.session-token", "access_token"];
cookieKeys.forEach((key) => cookieStore.delete(key));
await signOut({ redirect: false });
return NextResponse.redirect("/login");
}
Outcome:
🔹 401 errors are now handled in a clean and structured manner.
🔹 The issue with modifying cookies on the server is resolved.
🔹 The fetch function remains clean, without relying on client-side methods that cannot execute on the server.
⚡ This approach not only fixes the issue but also provides a standardized pattern for handling authentication in Next.js + auth.js.
✅ Suggestion for Next.js:
Adding support for handling signOut on the server without requiring an API Route or providing an official solution for this challenge.