Chuyển đến nội dung chính

Lesson 12: Authentication with NextAuth.js

NextAuth.js (Auth.js) setup. OAuth providers (Google, GitHub). Credentials provider, JWT vs Session. Middleware protection, role-based access control.

💻 Programming — Lesson 12 Lesson 12: Authentication with NextAuth.js

React & Next.js: From Basics to Advanced

Part 4: Next.js Advanced

xdev.asia

1. Setup Auth.js (NextAuth v5)

npm install next-auth@beta
// auth.ts
import NextAuth from 'next-auth';
import Google from 'next-auth/providers/google';
import GitHub from 'next-auth/providers/github';
import Credentials from 'next-auth/providers/credentials';
import { PrismaAdapter } from '@auth/prisma-adapter';
import { db } from '@/lib/db';
import bcrypt from 'bcryptjs';

export const { handlers, signIn, signOut, auth } = NextAuth({
  adapter: PrismaAdapter(db),
  providers: [
    Google({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
    GitHub({
      clientId: process.env.GITHUB_ID!,
      clientSecret: process.env.GITHUB_SECRET!,
    }),
    Credentials({
      credentials: {
        email: { label: 'Email', type: 'email' },
        password: { label: 'Password', type: 'password' },
      },
      async authorize(credentials) {
        const user = await db.user.findUnique({
          where: { email: credentials.email as string },
        });
        if (!user?.hashedPassword) return null;
        const valid = await bcrypt.compare(
          credentials.password as string,
          user.hashedPassword
        );
        return valid ? user : null;
      },
    }),
  ],
  session: { strategy: 'jwt' },
  callbacks: {
    async jwt({ token, user }) {
      if (user) {
        token.role = user.role;
      }
      return token;
    },
    async session({ session, token }) {
      session.user.id = token.sub!;
      session.user.role = token.role as string;
      return session;
    },
  },
});

2. Route Handler

// app/api/auth/[...nextauth]/route.ts
import { handlers } from '@/auth';
export const { GET, POST } = handlers;

3. Middleware Protection

// middleware.ts
import { auth } from '@/auth';

export default auth((req) => {
  const isLoggedIn = !!req.auth;
  const isAuthPage = req.nextUrl.pathname.startsWith('/auth');
  const isProtected = req.nextUrl.pathname.startsWith('/dashboard');

  if (isProtected && !isLoggedIn) {
    return Response.redirect(new URL('/auth/login', req.nextUrl));
  }

  if (isAuthPage && isLoggedIn) {
    return Response.redirect(new URL('/dashboard', req.nextUrl));
  }
});

export const config = {
  matcher: ['/dashboard/:path*', '/auth/:path*'],
};

4. Use in Server Component

import { auth } from '@/auth';
import { redirect } from 'next/navigation';

export default async function DashboardPage() {
  const session = await auth();

  if (!session) redirect('/auth/login');

  return (
    <div>
      <h1>Xin chào, {session.user?.name}</h1>
      <p>Role: {session.user?.role}</p>
      <img src={session.user?.image || ''} alt="Avatar" />
    </div>
  );
}

5. Use in Client Component

'use client';
import { useSession, signIn, signOut } from 'next-auth/react';

export function AuthButton() {
  const { data: session, status } = useSession();

  if (status === 'loading') return <div>Loading...</div>;

  if (session) {
    return (
      <div>
        <span>{session.user?.name}</span>
        <button onClick={() => signOut()}>Đăng xuất</button>
      </div>
    );
  }

  return (
    <div>
      <button onClick={() => signIn('google')}>Google</button>
      <button onClick={() => signIn('github')}>GitHub</button>
    </div>
  );
}

6. Role-Based Access Control

// lib/auth-utils.ts
import { auth } from '@/auth';

export async function requireRole(role: string) {
  const session = await auth();
  if (!session || session.user?.role !== role) {
    throw new Error('Unauthorized');
  }
  return session;
}

// app/admin/page.tsx
import { requireRole } from '@/lib/auth-utils';

export default async function AdminPage() {
  const session = await requireRole('admin');
  return <div>Admin Dashboard — {session.user?.name}</div>;
}

7. Login Form with Server Action

'use server';
import { signIn } from '@/auth';
import { AuthError } from 'next-auth';

export async function loginAction(prevState: any, formData: FormData) {
  try {
    await signIn('credentials', {
      email: formData.get('email'),
      password: formData.get('password'),
      redirectTo: '/dashboard',
    });
  } catch (error) {
    if (error instanceof AuthError) {
      return { error: 'Email hoặc mật khẩu không đúng' };
    }
    throw error;
  }
}

Next article: Middleware, API Routes & Route Handlers — advanced request/response handling.