Loading...
Loading...
Loading...
วิธี setup Auth.js v5 (NextAuth) กับ Next.js — install, config, callbacks, type extension
ขั้นตอนการ setup Auth.js v5 กับ Next.js App Router — Credentials provider + JWT strategy
pnpm add next-auth@beta# .env
AUTH_SECRET="your-secret-key" # openssl rand -base64 32
AUTH_URL="http://localhost:3000"import NextAuth from 'next-auth';
import Credentials from 'next-auth/providers/credentials';
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Credentials({
async authorize(credentials) {
// validate credentials กับ API / database
const user = await authService.login(credentials);
if (!user) return null;
return {
id: user.id,
email: user.email,
name: user.name,
role: user.role,
accessToken: user.accessToken,
};
},
}),
],
session: { strategy: 'jwt', maxAge: 7 * 24 * 60 * 60 },
pages: {
signIn: '/features/next-auth/login',
},
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id;
token.role = user.role;
token.accessToken = user.accessToken;
}
return token;
},
async session({ session, token }) {
session.user.id = token.id;
session.user.role = token.role;
session.user.accessToken = token.accessToken;
return session;
},
},
});// src/app/api/auth/[...nextauth]/route.ts
export { GET, POST } from '@/lib/auth/auth';// src/@types/next-auth.d.ts
import type { DefaultSession, DefaultUser } from 'next-auth';
import type { DefaultJWT } from 'next-auth/jwt';
import type { UserRole } from '@/constants/role.constant';
declare module 'next-auth' {
interface Session {
user: {
id: string;
role: UserRole;
accessToken: string;
} & DefaultSession['user'];
error?: string;
}
interface User extends DefaultUser {
role: UserRole;
accessToken: string;
refreshToken: string;
accessTokenExpiresAt: number;
}
}
declare module 'next-auth/jwt' {
interface JWT extends DefaultJWT {
id: string;
role: UserRole;
accessToken: string;
refreshToken: string;
accessTokenExpiresAt: number;
error?: string;
}
}import { auth } from '@/lib/auth/auth';
// ใน Server Component / Server Action
const session = await auth();
if (!session?.user) {
redirect('/features/next-auth/login');
}
// session.user.id, session.user.role, session.user.name// Layout: ครอบด้วย SessionProvider
import { SessionProvider } from 'next-auth/react';
import { auth } from '@/lib/auth/auth';
export default async function Layout({ children }) {
const session = await auth();
return (
<SessionProvider session={session}>
{children}
</SessionProvider>
);
}
// Client Component: ใช้ useSession
'use client';
import { useSession } from 'next-auth/react';
export function UserInfo() {
const { data: session, status } = useSession();
if (status === 'loading') return <Skeleton />;
if (!session) return <p>Not logged in</p>;
return <p>{session.user.name}</p>;
}// lib/actions/login/actions.ts
'use server';
import { ROUTES } from '@/constants/route.constant';
import { signIn } from '@/lib/auth/auth';
import type { LoginInput } from '@/lib/schemas/login.schema';
import { AuthError } from 'next-auth';
import { redirect } from 'next/navigation';
export async function loginAction(data: LoginInput) {
try {
await signIn('credentials', {
email: data.email,
password: data.password,
redirect: false, // ให้ Server Action จัดการ redirect เอง
});
} catch (error) {
if (error instanceof AuthError) {
return { success: false, error: 'Invalid email or password' };
}
throw error;
}
redirect(ROUTES.NEXT_AUTH);
// ✅ redirect() ฝั่ง server → layout re-render → await auth() ได้ session ใหม่
// ✅ ถ้า component รับ session เป็น prop จาก layout → UI update ทันที
}// ❌ ปัญหา: useSession ไม่ update หลัง login/logout
// signIn/signOut เปลี่ยน session cookie ฝั่ง Server
// แต่ SessionProvider ฝั่ง Client ยังถือ session เก่าอยู่
// → ถ้า component ใช้ useSession() อย่างเดียว → UI ไม่เปลี่ยนจนกว่าจะ refresh
// ✅ แนะนำ: ส่ง session จาก Server เป็น prop
// Layout (Server) → await auth() → ส่ง prop ให้ Client Component
// redirect() ใน Server Action → layout re-render → auth() ได้ session ใหม่ → prop update
export default async function Layout({ children }) {
const session = await auth();
return (
<SessionProvider session={session}>
{children}
<AuthHeader session={session} /> {/* ✅ session จาก server เสมอ */}
</SessionProvider>
);
}
// Client Component รับ session เป็น prop — ไม่ต้องพึ่ง useSession
function AuthHeader({ session }: { session: Session | null }) {
const user = session?.user;
// ✅ redirect() ใน Server Action → layout re-render → prop ใหม่อัตโนมัติ
return user ? <LogoutButton /> : <LoginButton />;
}
// ❌ ห้ามทำ
// - ใช้ useSession() เป็นตัวตัดสินใจ UI หลังเปลี่ยนสถานะ login/logout
// → Server รู้ แต่ Client ไม่รู้ ต้อง refresh เอง
// - เก็บ session ใน state เอง (ใช้ auth() หรือ prop จาก server เท่านั้น)