Mục lục

Authentication Đa chiều: Role-Based Access Control (RBAC)

Thiết kế hệ thống đăng nhập tập trung (Centralized Auth) và phân quyền chi tiết cho Admin, Merchant và Customer.

Xác thực (Authentication) cho một ứng dụng rât dễ. Nhưng xác thực cho 3 ứng dụng với 3 loại user khác nhau trên cùng một Database là bài toán khó.

1. Database Schema (Prisma)

Chúng ta cần thiết kế bảng User linh hoạt để hỗ trợ nhiều vai trò.

prisma:
model User {
  id            String    @id @default(cuid())
  email         String    @unique
  password      String    // Hashed
  role          UserRole  @default(CUSTOMER)
  
  // Relations
  merchantId    String?   // Nếu là MERCHANT STAFF
  orders        Order[]
}

enum UserRole {
  SUPER_ADMIN
  MERCHANT_OWNER
  MERCHANT_STAFF
  CUSTOMER
}

model Merchant {
    id          String   @id @default(cuid())
    name        String
    users       User[]   // Nhân viên của Merchant này
}

2. Authentication Flow

Sử dụng NextAuth.js v5 với Strategy chia sẻ cookie hoặc JWT.

Kịch bản 1: Login riêng biệt

  • admin.app.com/login -> Chỉ cho phép SUPER_ADMIN.
  • merchant.app.com/login -> Chỉ cho phép MERCHANT_OWNERMERCHANT_STAFF.
  • store.app.com/login -> Chỉ cho phép CUSTOMER.

Code Logic (Middleware Protection)

Tại mỗi ứng dụng, chúng ta dùng Middleware để chặn sai Role.

typescript:
// apps/admin/middleware.ts
import { auth } from "@/auth"

export default auth((req) => {
  const userRole = req.auth?.user?.role;
  
  if (userRole !== 'SUPER_ADMIN') {
    return Response.redirect(new URL('/unauthorized', req.url))
  }
})

3. Session Management

Nếu bạn muốn trải nghiệm Single Sign-On (SSO) - Đăng nhập 1 lần vào được tất cả:

  • Cần đặt các app dưới sub-domain: store.domain.com, admin.domain.com.
  • Cấu hình Cookie Domain là .domain.com.

Tuy nhiên, trong dự án này, để bảo mật cao nhất, chúng ta giữ Session riêng biệt (Isolated Sessions). Admin đăng nhập Admin Portal không có nghĩa là tự động đăng nhập vào Storefront với tư cách Customer (trừ khi có tính năng Impersonate).

4. Bảo mật API (Shared API)

Nếu chúng ta dùng chung một Backend (Monolith API) hoặc Server Actions trong Monorepo:

typescript:
// packages/actions/product.ts
export async function createProduct(data: ProductInput) {
  const session = await getSession();
  
  // Security Gate
  if (session.role !== 'MERCHANT_OWNER') {
    throw new Error("Forbidden");
  }
  
  // Logic tạo sản phẩm...
}

Việc đặt logic check quyền ngay trong Server Action (được share từ thư mục packages) giúp logic bảo mật nhất quán dù được gọi từ UI nào.

Quảng cáo
mdhorizontal