// config/authConfig.ts (or wherever your authOptions live) import { AuthOptions } from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; import { LOGIN_API_PATH } from "./api"; import { Session } from "next-auth"; // Extend the built-in types declare module "next-auth" { interface Session { accessToken: string | null; refreshToken?: string; abilities: string[]; id?: string; /** JWT expiry (seconds since epoch); used to avoid redirecting to dashboard when token is expired */ exp?: number; } interface User { id?: string; accessToken: string | null; refreshToken?: string; abilities: string[]; } } declare module "next-auth/jwt" { interface JWT { id?: string; accessToken: string | null; refreshToken?: string; abilities: string[]; } } export const authOptions: AuthOptions = { debug: process.env.NODE_ENV === "development", providers: [ CredentialsProvider({ id: "credentials", name: "Credentials", credentials: { username: { label: "Username", type: "text" }, password: { label: "Password", type: "password" }, }, async authorize(credentials) { if (!credentials?.username || !credentials?.password) return null; const res = await fetch(LOGIN_API_PATH, { method: "POST", body: JSON.stringify(credentials), headers: { "Content-Type": "application/json" }, }); if (!res.ok) return null; const user = await res.json(); // Important: next-auth expects the user object returned here // to be serializable and contain the fields you want in token/session // Ensure your backend returns: { id, accessToken, abilities, ...other fields } if (user && user.abilities) { return user; // this will be passed to jwt callback as `user` } return null; }, }), ], pages: { signIn: "/login", }, callbacks: { // Persist custom fields into the JWT token async jwt({ token, user }) { // First sign-in: `user` is available if (user) { token.id = user.id ?? token.sub; // fallback to sub if no id token.accessToken = user.accessToken; token.refreshToken = user.refreshToken; token.abilities = user.abilities ?? []; } // On subsequent calls (token refresh, session access), user is not present // so we just return the existing token with custom fields preserved return token; }, // Expose custom fields to the client session async session({ session, token }) { session.id = token.id as string | undefined; session.accessToken = token.accessToken as string | null; session.refreshToken = token.refreshToken as string | undefined; session.abilities = token.abilities as string[]; session.exp = token.exp as number | undefined; // Also add abilities to session.user for easier client-side access if (session.user) { session.user.abilities = token.abilities as string[]; } return session; }, }, }; export type SessionWithTokens = Session & { accessToken: string | null; refreshToken?: string; abilities: string[]; /** Backend / JWT subject — often numeric string or number */ id?: string | number; }; export default authOptions;