Bài 8. Xác thực người dùng và bảo vệ trang

Ứng dụng quản lý cần đăng nhập và phân quyền. Bài này xây dựng xác thực bằng Auth.js (NextAuth) với đăng nhập email/mật khẩu và Google, đọc phiên trong Server Component, bảo vệ route bằng middleware (proxy) và kiểm tra quyền trong Server Action.

60 phútNâng cao
Bạn sẽ học được
  • Cài đặt và cấu hình Auth.js
  • Đăng nhập bằng mật khẩu (bcrypt) và Google
  • Đọc phiên, hiện thông tin người dùng
  • Bảo vệ trang và action theo vai trò

Một số khái niệm

  • Xác thực: xác định người dùng là ai (đăng nhập).
  • Phân quyền: người đó được làm gì (admin, giảng viên, sinh viên).
  • Phiên (session): Auth.js lưu trong cookie mã hóa (JWT) hoặc CSDL.
  • Provider: cách đăng nhập: Credentials, Google, GitHub, Microsoft...

Cấu hình Auth.js

npm install next-auth@beta bcryptjs
npx auth secret          # tạo AUTH_SECRET trong .env.local
// src/auth.ts
import NextAuth from 'next-auth';
import Credentials from 'next-auth/providers/credentials';
import Google from 'next-auth/providers/google';
import bcrypt from 'bcryptjs';
import { db } from '@/lib/db';

export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [
    Google,
    Credentials({
      credentials: { email: {}, mat_khau: {} },
      async authorize(c) {
        const u = await db.nguoiDung.findUnique({ where: { email: String(c.email) } });
        if (!u || !(await bcrypt.compare(String(c.mat_khau), u.mat_khau))) return null;
        return { id: String(u.id), name: u.ho_ten, email: u.email, role: u.vai_tro };
      },
    }),
  ],
  callbacks: {
    jwt({ token, user }) { if (user) token.role = (user as any).role; return token; },
    session({ session, token }) { (session.user as any).role = token.role; return session; },
  },
  pages: { signIn: '/dang-nhap' },
});
// src/app/api/auth/[...nextauth]/route.ts
export { GET, POST } from '@/auth';   // hoặc: import { handlers } ... export const { GET, POST } = handlers;

Dùng phiên

// Server Component
import { auth, signOut } from '@/auth';
export default async function ThanhTaiKhoan() {
  const session = await auth();
  if (!session) return <a href="/dang-nhap">Đăng nhập</a>;
  return (
    <form action={async () => { 'use server'; await signOut(); }}>
      Xin chào {session.user?.name} <button>Đăng xuất</button>
    </form>
  );
}

Bảo vệ trang và action

// src/middleware.ts (Next.js 16: proxy.ts)
export { auth as middleware } from '@/auth';
export const config = { matcher: ['/quan-tri/:path*', '/sinhvien/them'] };
// src/lib/quyen.ts
import { auth } from '@/auth';
import { redirect } from 'next/navigation';
export async function canVaiTro(...vaiTro: string[]) {
  const s = await auth();
  if (!s) redirect('/dang-nhap');
  if (!vaiTro.includes((s.user as any).role)) throw new Error('Không có quyền');
  return s;
}
// trong Server Action: await canVaiTro('admin', 'giangvien');
Mẹo hay

Middleware chỉ là lớp chặn sớm; vẫn phải kiểm tra quyền ở nơi đọc/ghi dữ liệu (Server Component, Server Action, Route Handler).

Lưu ý

Không lưu mật khẩu dạng thô; luôn băm bằng bcrypt. Giữ AUTH_SECRET và khóa Google trong biến môi trường, không đưa lên GitHub.

Thực hành
  1. Tạo trang /dang-nhap với form Credentials.
  2. Thêm đăng nhập Google.
  3. Bảo vệ khu /quan-tri chỉ cho admin.
  4. Hiển thị nút Sửa/Xóa theo vai trò.
Tóm tắt bài học
  • Auth.js: providers, callbacks, pages.
  • auth() đọc phiên ở máy chủ.
  • Middleware chặn sớm, kiểm tra quyền tại chỗ.
  • bcrypt, AUTH_SECRET, biến môi trường.