I use NextJS 15 and Auth.js / NextAuth with the App Router.
I would like to protect my API route so that you can only gain access with the appropriate role. I would like to find this out from the session. However, it is not possible for me to access the session, and I simply don't know why.
Can anyone help me?
I make the API call in src/app/dashboard/accounts/page.tsx. In general, I can only enter this site if the session and role are valid.
src/app/dashboard/accounts/page.tsx
import { auth } from "@/lib/auth";
import { redirect } from "next/navigation";
import { checkPermissions } from "@/lib/permissions";
const Accounts = async () => {
const session = await auth();
if (!session || !checkPermissions(session.user, "view", "accounts")) {
redirect("/dashboard");
}
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/accounts`,
{
method: "GET",
credentials: "include",
}
);
Here it works that the session is checked and that the user role is checked.
What I don't understand, however, is why I can't access the session here?
src/app/api/accounts/route.ts
import { NextResponse } from "next/server";
import { auth } from "@/lib/auth";
export const GET = auth(function GET(req) {
console.log(req)
if (!req.auth) {
return NextResponse.json({ message: "Not authenticated" }, { status: 401 });
}
console.log(req.auth);
return NextResponse.json(req.auth);
});
I get this output here:
✓ Compiled /api/accounts in 615ms
Request {
method: 'GET',
url: 'https://localhost:3000/api/accounts',
headers: Headers {
accept: '*/*',
'accept-encoding': 'br, gzip, deflate',
'accept-language': '*',
connection: 'keep-alive',
host: 'localhost:3000',
'sec-fetch-mode': 'cors',
'user-agent': 'node',
'x-forwarded-for': '::1',
'x-forwarded-host': 'localhost:3000',
'x-forwarded-port': '3000',
'x-forwarded-proto': 'https'
},
destination: '',
referrer: 'about:client',
referrerPolicy: '',
mode: 'cors',
credentials: 'same-origin',
cache: 'default',
redirect: 'follow',
integrity: '',
keepalive: false,
isReloadNavigation: false,
isHistoryNavigation: false,
signal: AbortSignal { aborted: false }
}
I do not use middleware.ts.
I have tried to use the Auth.js docs as a guide.
Link to Auth.js session management docs.
I really don't know what to do, can anyone help me?
Thanks in advance
For context, here are a few important files:
/src/lib/auth.ts
import { v4 as uuid } from "uuid";
import bcrypt from "bcrypt";
import { encode as defaultEncode } from "next-auth/jwt";
import db from "@/lib/db/db";
import { PrismaAdapter } from "@auth/prisma-adapter";
import NextAuth from "next-auth";
import Credentials from "next-auth/providers/credentials";
import GitHub from "next-auth/providers/github";
import { schema } from "@/lib/schema";
import { User as PrismaUser, Role } from "@prisma/client";
const adapter = PrismaAdapter(db);
export const { handlers, signIn, signOut, auth } = NextAuth({
adapter,
providers: [
GitHub,
Credentials({
credentials: {
email: {},
password: {},
},
authorize: async (credentials) => {
const validatedCredentials = schema.parse(credentials);
const user = await db.user.findFirst({
where: {
email: validatedCredentials.email,
},
});
if (!user) {
throw new Error("Invalid credentials.");
}
const passwordMatch =
user.password &&
(await bcryptpare(validatedCredentials.password, user.password));
if (!passwordMatch) {
throw new Error("Invalid credentials.");
}
return user;
},
}),
],
callbacks: {
async jwt({ token, account, user }) {
if (account?.provider === "credentials") {
token.credentials = true;
token.accessToken = uuid();
}
if (user) {
const prismaUser = user as PrismaUser & { role: string };
token.id = String(prismaUser.id);
token.name = prismaUser.name;
token.email = prismaUser.email;
token.picture = prismaUser.image;
// token.role = prismaUser.role;
// token.sub = prismaUser.id;
}
return token;
},
async session({ token, session }) {
if (token) {
session.user.id = token.sub!;
session.accessToken = token.accessToken as string;
session.user.name = token.name;
session.user.image = token.picture;
// session.user.role = token.role;
}
return session;
},
authorized: async ({ auth }) => {
return !!auth;
},
},
jwt: {
encode: async function (params) {
if (params.token?.credentials) {
const sessionToken = uuid();
if (!params.token.sub) {
throw new Error("No user ID found in token");
}
const createdSession = await adapter?.createSession?.({
sessionToken: sessionToken,
userId: params.token.sub,
expires: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
});
if (!createdSession) {
throw new Error("Failed to create session");
}
return sessionToken;
}
return defaultEncode(params);
},
},
});
src/lib/permissions.ts
const PERMISSIONS: Record<string, string[]> = {
ADMIN: ["view:accounts", "edit:accounts"],
};
export const checkPermissions = (
user: { role?: string } | null,
action: string,
resource: string
): boolean => {
if (!user || !user.role) return false;
const permissions = PERMISSIONS[user.role];
if (!permissions) return false;
return permissions.includes(`${action}:${resource}`);
};
src/types/next-auth.d.ts
import { Role } from "@prisma/client";
import type { User } from "next-auth";
import "next-auth/jwt";
type UserId = string;
declare module "next-auth/jwt" {
interface JWT {
id: UserId;
role: Role;
accessToken?: string;
}
}
declare module "next-auth" {
interface Session {
user: User & {
id: UserId;
role: Role;
name?: string | null;
email?: string | null;
image?: string | null;
};
accessToken?: string;
}
}
I use NextJS 15 and Auth.js / NextAuth with the App Router.
I would like to protect my API route so that you can only gain access with the appropriate role. I would like to find this out from the session. However, it is not possible for me to access the session, and I simply don't know why.
Can anyone help me?
I make the API call in src/app/dashboard/accounts/page.tsx. In general, I can only enter this site if the session and role are valid.
src/app/dashboard/accounts/page.tsx
import { auth } from "@/lib/auth";
import { redirect } from "next/navigation";
import { checkPermissions } from "@/lib/permissions";
const Accounts = async () => {
const session = await auth();
if (!session || !checkPermissions(session.user, "view", "accounts")) {
redirect("/dashboard");
}
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/accounts`,
{
method: "GET",
credentials: "include",
}
);
Here it works that the session is checked and that the user role is checked.
What I don't understand, however, is why I can't access the session here?
src/app/api/accounts/route.ts
import { NextResponse } from "next/server";
import { auth } from "@/lib/auth";
export const GET = auth(function GET(req) {
console.log(req)
if (!req.auth) {
return NextResponse.json({ message: "Not authenticated" }, { status: 401 });
}
console.log(req.auth);
return NextResponse.json(req.auth);
});
I get this output here:
✓ Compiled /api/accounts in 615ms
Request {
method: 'GET',
url: 'https://localhost:3000/api/accounts',
headers: Headers {
accept: '*/*',
'accept-encoding': 'br, gzip, deflate',
'accept-language': '*',
connection: 'keep-alive',
host: 'localhost:3000',
'sec-fetch-mode': 'cors',
'user-agent': 'node',
'x-forwarded-for': '::1',
'x-forwarded-host': 'localhost:3000',
'x-forwarded-port': '3000',
'x-forwarded-proto': 'https'
},
destination: '',
referrer: 'about:client',
referrerPolicy: '',
mode: 'cors',
credentials: 'same-origin',
cache: 'default',
redirect: 'follow',
integrity: '',
keepalive: false,
isReloadNavigation: false,
isHistoryNavigation: false,
signal: AbortSignal { aborted: false }
}
I do not use middleware.ts.
I have tried to use the Auth.js docs as a guide.
Link to Auth.js session management docs.
I really don't know what to do, can anyone help me?
Thanks in advance
For context, here are a few important files:
/src/lib/auth.ts
import { v4 as uuid } from "uuid";
import bcrypt from "bcrypt";
import { encode as defaultEncode } from "next-auth/jwt";
import db from "@/lib/db/db";
import { PrismaAdapter } from "@auth/prisma-adapter";
import NextAuth from "next-auth";
import Credentials from "next-auth/providers/credentials";
import GitHub from "next-auth/providers/github";
import { schema } from "@/lib/schema";
import { User as PrismaUser, Role } from "@prisma/client";
const adapter = PrismaAdapter(db);
export const { handlers, signIn, signOut, auth } = NextAuth({
adapter,
providers: [
GitHub,
Credentials({
credentials: {
email: {},
password: {},
},
authorize: async (credentials) => {
const validatedCredentials = schema.parse(credentials);
const user = await db.user.findFirst({
where: {
email: validatedCredentials.email,
},
});
if (!user) {
throw new Error("Invalid credentials.");
}
const passwordMatch =
user.password &&
(await bcryptpare(validatedCredentials.password, user.password));
if (!passwordMatch) {
throw new Error("Invalid credentials.");
}
return user;
},
}),
],
callbacks: {
async jwt({ token, account, user }) {
if (account?.provider === "credentials") {
token.credentials = true;
token.accessToken = uuid();
}
if (user) {
const prismaUser = user as PrismaUser & { role: string };
token.id = String(prismaUser.id);
token.name = prismaUser.name;
token.email = prismaUser.email;
token.picture = prismaUser.image;
// token.role = prismaUser.role;
// token.sub = prismaUser.id;
}
return token;
},
async session({ token, session }) {
if (token) {
session.user.id = token.sub!;
session.accessToken = token.accessToken as string;
session.user.name = token.name;
session.user.image = token.picture;
// session.user.role = token.role;
}
return session;
},
authorized: async ({ auth }) => {
return !!auth;
},
},
jwt: {
encode: async function (params) {
if (params.token?.credentials) {
const sessionToken = uuid();
if (!params.token.sub) {
throw new Error("No user ID found in token");
}
const createdSession = await adapter?.createSession?.({
sessionToken: sessionToken,
userId: params.token.sub,
expires: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
});
if (!createdSession) {
throw new Error("Failed to create session");
}
return sessionToken;
}
return defaultEncode(params);
},
},
});
src/lib/permissions.ts
const PERMISSIONS: Record<string, string[]> = {
ADMIN: ["view:accounts", "edit:accounts"],
};
export const checkPermissions = (
user: { role?: string } | null,
action: string,
resource: string
): boolean => {
if (!user || !user.role) return false;
const permissions = PERMISSIONS[user.role];
if (!permissions) return false;
return permissions.includes(`${action}:${resource}`);
};
src/types/next-auth.d.ts
import { Role } from "@prisma/client";
import type { User } from "next-auth";
import "next-auth/jwt";
type UserId = string;
declare module "next-auth/jwt" {
interface JWT {
id: UserId;
role: Role;
accessToken?: string;
}
}
declare module "next-auth" {
interface Session {
user: User & {
id: UserId;
role: Role;
name?: string | null;
email?: string | null;
image?: string | null;
};
accessToken?: string;
}
}
Share
Improve this question
edited Mar 21 at 2:19
Hamed Jimoh
1,3898 silver badges20 bronze badges
asked Mar 17 at 12:35
HannesHannes
11 bronze badge
1 Answer
Reset to default 0In Next.js v15, you should use getToken method to access your session in an api route
Here's an example
import { getToken, JWT } from "next-auth/jwt"
export async function GET(request: Request) {
const token: JWT | null = await getToken({ req: request, secret: process.env.AUTH_SECRET })
if (!token)
return Response.json({
success: false,
message: "Unauthorized",
data: null,
}, { status: 401 })
// then you can get your access token like this: token?.accessToken
}