8. NextAuth v5 (Auth.js) - A Detailed Guide
Configuring Auth.js / NextAuth v5 in Nova Shop: file structure, providers, callbacks, JWT session.
On this page
- Table of Contents
- 1. How Is NextAuth v5 Different from v4?
- 2. File Structure in Nova Shop
- 3. Exporting auth, signIn, handlers
- 4. Session Strategy: JWT
- 5. Provider: Credentials
- 6. Provider: Google OAuth
- 7. Callbacks: signIn, jwt, session, authorized
- 8. SessionProvider & useSession
- 9. Nova's Login / Logout Flows
- 10. FAQ & File Map
8. NextAuth v5 (Auth.js) - A Detailed Guide
Configuring Auth.js / NextAuth v5 in Nova Shop: file structure, providers, callbacks, JWT session.
Nova's package: "next-auth": "5.0.0-beta.29" - read authjs.dev, don't copy v4 tutorials.
Read in order: Sections 1–3 (files) → 4–6 (providers) → 7 (callbacks) → 8 (Nova flows) → 9 (FAQ).
Docs: Auth.js Next.js
Table of Contents
- How is NextAuth v5 different from v4?
- File structure in Nova Shop
- Exporting
auth,signIn,handlers - Session strategy: JWT
- Provider: Credentials
- Provider: Google OAuth
- Callbacks:
signIn,jwt,session,authorized - SessionProvider &
useSession - Nova's login / logout flows
- FAQ & file map
1. How Is NextAuth v5 Different from v4?
| v4 | v5 |
|---|---|
One big file, pages/api/auth/[...nextauth].ts | auth.ts + auth.config.ts |
getServerSession | await auth() |
| Docs at next-auth.org | authjs.dev |
| A lot of hand-written middleware | NextAuth(config).auth |
2. File Structure in Nova Shop
auth.config.ts → edge-safe: session, jwt, authorized (middleware)
auth.ts → providers, signIn callback, signOut events
middleware.ts → export default NextAuth(authConfig).auth
app/api/auth/[...nextauth]/route.ts → export { GET, POST } = handlers
app/providers.tsx → <SessionProvider> (client)
app/lib/actions.ts → authenticate() calls signIn
Why split out auth.config? Middleware runs on the Edge - it can't import heavy Node modules from auth.ts.
3. Exporting auth, signIn, handlers
// auth.ts
export const { auth, signIn, signOut, handlers } = NextAuth({
...authConfig,
secret: process.env.AUTH_SECRET ?? process.env.NEXTAUTH_SECRET,
providers: [ Google(...), Credentials(...) ],
callbacks: { ...authConfig.callbacks, signIn, jwt, session },
events: { signOut: async () => { ... } },
});
| Export | Used when |
|---|---|
auth() | Server Component: const session = await auth() |
signIn("credentials", {...}) | Server Action login |
signOut() | Logging out |
handlers | The /api/auth/* route |
4. Session Strategy: JWT
// auth.config.ts
session: {
strategy: "jwt",
maxAge: 24 * 60 * 60,
updateAge: 12 * 60 * 60,
},
jwt: { maxAge: 30 * 24 * 60 * 60 },
| JWT session | Database session | |
|---|---|---|
| Stored in | Signed session-token cookie | A sessions table |
| Revoke | Harder (must wait for expiry) | Delete the row |
| Nova | ✅ No DB adapter inside the Next app |
Note: the NextAuth JWT is different from the NestJS access_token JWT - two parallel systems (see file 9).
5. Provider: Credentials
Credentials({
async authorize(credentials) {
const parsed = z.object({
email: z.string().email(),
password: z.string().min(6),
}).safeParse(credentials);
if (!parsed.success) return null;
const response = await login({ email, password }); // POST /login
await setAuthCookies({
accessToken: response.accessToken,
refreshToken: response.refreshToken,
userId: response.userId,
});
return { id: email, name: email };
},
}),
| Step | Meaning |
|---|---|
safeParse | Validate input |
login() | Calls NestJS |
setAuthCookies | The backend JWT used by authFetch |
return user | Creates the NextAuth session |
return null | Login failed |
6. Provider: Google OAuth
Google({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
authorization: {
params: { prompt: "consent", access_type: "offline", response_type: "code" },
},
}),
signIn callback:
async signIn({ user, account }) {
if (account?.provider === "google" && user.email) {
const response = await googleAuthAction({ idToken: account.id_token! });
await setAuthCookies({ ... });
return true;
}
return true;
}
Google Console redirect URI: {ORIGIN}/api/auth/callback/google
7. Callbacks: signIn, jwt, session, authorized
authorized - middleware
See 6. Middleware for details. Decides allow vs. redirect to /login.
jwt - attaching data to the token
async jwt({ token, user, account }) {
if (account && user) {
return { ...token, accessToken: account.access_token, expiresAt: ... };
}
if (token.expiresAt && Date.now()/1000 > token.expiresAt) return null;
return token;
}
return null → forces a logout.
session - exposing data to the client
async session({ session, token }) {
session.accessToken = token.accessToken;
session.expiresAt = token.expiresAt;
if (token.sub) session.user.id = token.sub;
return session;
}
Module augmentation:
declare module "next-auth" {
interface Session {
accessToken?: string;
expiresAt?: number;
nearExpiry?: boolean;
}
}
Improvement for Nova: the jwt/session callbacks are duplicated between auth.ts and auth.config.ts - these should be consolidated into one source to avoid drift.
8. SessionProvider & useSession
// app/providers.tsx
"use client";
import { SessionProvider } from "next-auth/react";
export default function Providers({ children }) {
return <SessionProvider>{children}</SessionProvider>;
}
// navbar.tsx - client
const { data: session } = useSession();
The server doesn't need the Provider - it uses await auth().
9. Nova's Login / Logout Flows
Email login
Logout
events: {
async signOut() {
await logout(); // POST /logout to NestJS
await clearAuthCookies();
},
},
10. FAQ & File Map
FAQ
Q: Login redirect loop?
Check authorized + pages.signIn + that cookies get set after login.
Q: signIn throws?
That's an internal redirect - catch AuthError, rethrow.
Q: Session is null on the server?
Missing AUTH_SECRET.
Env
AUTH_SECRET=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
Next: 9. JWT Dual Auth · 6. Middleware
