I’m currently working on a Next.js 14 project where I was using NextAuth.js v4 for authentication. Recently, I decided to migrate to NextAuth.js v5 to take advantage of the latest features and improvements. So I was just testing in a separate file.
Here’s my current setup in v4:
I’m using CredentialsProvider for custom authentication.
I’ve implemented custom logic in the authOptions configuration.
I extended the User interface to include additional properties like accessToken, sessionId, isSuperAdmin, and systemRole.
For that I have created a next-auth.d.ts:
// importing next-auth so that other types from next-auth are also available in code
import 'next-auth';
import { Role } from '@/lib/types/role';
interface BaseUser {
data: {
id: number;
email: string;
phoneNumber: string;
forcePasswordChange: boolean;
};
accessToken: string;
sessionId: string;
isSuperAdmin: boolean;
systemRole: Role;
}
declare module 'next-auth' {
export type User = BaseUser;
interface Session {
user: User;
}
}
declare module 'next-auth/jwt' {
type JWT = BaseUser;
}
and authOptions.ts:
import { NextAuthOptions, Session, User } from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import api from '@/lib/api';
import { isSuperAdmin } from '@/lib/config/ability';
import { toast } from 'react-toastify';
// Define the shape of credentials passed during authentication
interface CredentialsProps {
email?: string;
phoneNumber?: string;
password: string;
}
// NextAuth configuration options
export const authOptions: NextAuthOptions = {
providers: [
CredentialsProvider({
id: 'credentials',
name: 'Credentials',
// Define credential input fields
credentials: {
phoneNumber: { label: 'Phone', type: 'number' },
password: { label: 'Password', type: 'text' },
email: { label: 'Email', type: 'email' }
},
// Custom authentication logic
async authorize(credentials: CredentialsProps | undefined) {
if (!credentials) {
throw new Error('Invalid credentials');
}
const { email, phoneNumber, password } = credentials;
try {
// Call API to authenticate user
const loginData = await api.authentication.login({
method: email ? 'email' : 'phone',
email: email,
phoneNumber: phoneNumber,
password: password
});
// Fetch user's system role
const userSystemRole = await api.systemRoles.getUserSystemRoles(
loginData.accessToken
);
// Construct user object
const user: User = {
data: {
id: loginData.id,
email: loginData.email,
phoneNumber: loginData.phoneNumber,
forcePasswordChange: loginData.forcePasswordChange
},
isSuperAdmin: isSuperAdmin(
userSystemRole.permissions,
loginData.id
),
accessToken: loginData.accessToken,
sessionId: loginData.sessionId,
systemRole: userSystemRole
};
return user;
} catch (error) {
// Handle authentication error
toast.error('Error during authentication');
console.error('Authentication Error:', error);
throw error;
}
}
})
],
secret: process.env.NEXTAUTH_SECRET,
// Define the maximum duration for session validity
session: {
strategy: 'jwt',
maxAge: 12 * 60 * 60
},
jwt: {
maxAge: 12 * 60 * 60
},
// Specify custom sign-in page
pages: { signIn: '/login' },
// Callbacks for JWT and session handling
callbacks: {
jwt: ({ token, user }) => {
// Merge user data into JWT token
return { ...token, ...user };
},
session: ({ session, token }) => {
// Modify session object
const modifiedSession: Session = {
...session,
user: {
data: token.data,
accessToken: token.accessToken,
sessionId: token.sessionId,
systemRole: token.systemRole,
isSuperAdmin: token.isSuperAdmin
}
};
return modifiedSession;
}
}
};
I’m aware that NextAuth.js v5 comes with some breaking changes and new patterns, so I decided to test the migration. However, I’m not sure what specific changes I need to make to my existing setup, especially regarding the authOption. Till now I understood that I have to create a auth.ts file in the root, which I did:
import NextAuth from "next-auth"
import CredentialsProvider from "next-auth/providers/credentials"
import api from '@/lib/api'
import { isSuperAdmin } from '@/lib/config/ability'
// Define the shape of credentials passed during authentication
interface CredentialsProps {
email?: string;
phoneNumber?: string;
password: string;
}
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
CredentialsProvider({
id: "credentials",
name: "Credentials",
credentials: {
phoneNumber: { label: "Phone", type: "number" },
password: { label: "Password", type: "password" },
email: { label: "Email", type: "email" }
},
async authorize(credentials) {
if (!credentials) {
return null;
}
const { email, phoneNumber, password } = credentials as CredentialsProps;
try {
const loginData = await api.testingAPI.login({
method: email ? 'email' : 'phone',
email,
phoneNumber,
password
});
const userSystemRole = await api.testingAPI.getUserSystemRoles(
loginData.accessToken
);
return {
id: loginData.id,
email: loginData.email,
name: loginData.phoneNumber,
data: {
id: loginData.id,
email: loginData.email,
phoneNumber: loginData.phoneNumber,
forcePasswordChange: loginData.forcePasswordChange
},
isSuperAdmin: isSuperAdmin(userSystemRole.permissions, loginData.id),
accessToken: loginData.accessToken,
sessionId: loginData.sessionId,
systemRole: userSystemRole
};
} catch (error) {
console.error('Authentication Error:', error);
return null;
}
}
})
],
pages: {
signIn: '/login'
},
callbacks: {
async jwt({ token, user }) {
if (user) {
token.user = user;
}
return token;
},
async session({ session, token }) {
session.user = token.user as any;
return session;
}
},
})
Another file I have created in “`@/app/api/auth/[…nextauth]/route.ts:
import { handlers } from "@/auth"
export const { GET, POST } = handlers
but it is not working and giving typescript error:
Type '(credentials: Partial<Record<"phoneNumber" | "password" | "email", unknown>>) => Promise<{ id: number; email: string; name: string; data: { id: number; email: string; phoneNumber: string; forcePasswordChange: boolean; }; isSuperAdmin: boolean; accessToken: string; sessionId: string; systemRole: RoleDetails; } | null>' is not assignable to type '(credentials: Partial<Record<"phoneNumber" | "password" | "email", unknown>>, request: Request) => Awaitable<User | null>'.
I’ve looked through the NextAuth.js documentation for v5, but I’m still unclear on some of the migration steps. Can anyone provide guidance or examples on how to approach this migration? Specifically, any tips on handling custom authentication logic and interface extensions would be greatly appreciated.
Analyzer is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.