|
- // 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;
|