FrontendAuthentication
NextAuth Configuration
Credentials provider and JWT callback setup.
Provider Setup
NextAuth is configured in lib/auth/providers.ts with a single credentials provider:
import NextAuth from "next-auth";
import Credentials from "next-auth/providers/credentials";
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Credentials({
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Password", type: "password" },
},
async authorize(credentials) {
// 1. Request JWT tokens from Django
const tokenRes = await fetch(`${BACKEND_URL}/auth/jwt/create/`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email: credentials.email,
password: credentials.password,
}),
});
if (!tokenRes.ok) return null;
const tokens = await tokenRes.json();
// 2. Fetch user profile
const userRes = await fetch(`${BACKEND_URL}/auth/users/me/`, {
headers: { Authorization: `JWT ${tokens.access}` },
});
if (!userRes.ok) return null;
const user = await userRes.json();
// 3. Return user + tokens
return {
...user,
accessToken: tokens.access,
refreshToken: tokens.refresh,
};
},
}),
],
// ...callbacks
});JWT Callback
The JWT callback manages token storage and refresh:
callbacks: {
async jwt({ token, user, trigger }) {
// First sign-in: store tokens from authorize()
if (user) {
token.accessToken = user.accessToken;
token.refreshToken = user.refreshToken;
token.accessTokenExpires = Date.now() + ACCESS_TOKEN_LIFETIME;
token.user = user;
return token;
}
// Token still valid: return as-is
if (Date.now() < token.accessTokenExpires - 60_000) {
return token;
}
// Token expiring: refresh
try {
const refreshed = await refreshToken(token.refreshToken);
token.accessToken = refreshed.access;
token.accessTokenExpires = Date.now() + ACCESS_TOKEN_LIFETIME;
return token;
} catch {
// Refresh failed: invalidate session
return { ...token, error: "RefreshTokenError" };
}
},
}Session Callback
The session callback exposes the token data to client components:
callbacks: {
async session({ session, token }) {
session.user = {
...session.user,
...token.user,
accessToken: token.accessToken,
refreshToken: token.refreshToken,
accessTokenExpires: token.accessTokenExpires,
};
if (token.error) {
session.error = token.error;
}
return session;
},
}Session Error Handler
The SessionErrorHandler component watches for refresh token failures:
function SessionErrorHandler({ children }) {
const { data: session } = useSession();
useEffect(() => {
if (session?.error === "RefreshTokenError") {
toast.error("Session expired. Please sign in again.");
signOut({ callbackUrl: "/login" });
}
}, [session?.error]);
return children;
}Pages Configuration
pages: {
signIn: "/login", // Custom login page
error: "/login", // Redirect errors to login
newUser: "/dashboard", // Redirect after first sign-in
},Session Strategy
session: {
strategy: "jwt", // JWT-only (no database sessions)
maxAge: 14 * 24 * 60 * 60, // 14 days
},