ضمیمه د: جزئیات کامل احراز هویت (فصل ۱۱)

این ضمیمه جزئیات کامل پیاده‌سازی سیستم احراز هویت با استفاده از ماژول @sidebase/nuxt-auth در اپلیکیشن مرکز اجاره کشتی‌های کروز را ارائه می‌دهد. این بخش شامل پیکربندی جامع، مدیریت سشن‌ها، پشتیبانی از چندین ارائه‌دهنده (مانند GitHub و Google)، و ادغام با رابط کاربری است. هدف این است که تمام جنبه‌های احراز هویت که در فصل یازدهم یا ضمیمه‌های اولیه به‌صورت خلاصه یا غایب بودند، به‌طور کامل توضیح داده شوند.


پیکربندی کامل @sidebase/nuxt-auth

ماژول @sidebase/nuxt-auth بر پایه Auth.js (NextAuth سابق) ساخته شده و امکان پیاده‌سازی احراز هویت OAuth، JWT، یا سایر روش‌ها را در Nuxt 3 فراهم می‌کند. این بخش پیکربندی کامل برای پشتیبانی از ارائه‌دهندگان GitHub و Google را شرح می‌دهد.

نصب و تنظیم اولیه

  1. نصب ماژول:
   npm install @sidebase/nuxt-auth
  1. به‌روزرسانی nuxt.config.ts:
   export default defineNuxtConfig({
     modules: [
       '@pinia/nuxt',
       '@nuxtjs/tailwindcss',
       '@nuxt/image',
       '@nuxt/content',
       '@sidebase/nuxt-auth',
       '@nuxtjs/supabase',
       '@nuxtjs/plausible',
     ],
     auth: {
       provider: {
         type: 'authjs',
         trustHost: true, // برای محیط‌های محلی و تولید
         defaultProvider: 'github', // ارائه‌دهنده پیش‌فرض
       },
       globalAppMiddleware: false, // میدلور جهانی غیرفعال
     },
     runtimeConfig: {
       public: {
         appUrl: process.env.APP_URL || 'http://localhost:3000',
       },
       authSecret: process.env.AUTH_SECRET,
     },
   });
  • توضیحات:

    • auth.provider: تنظیم Auth.js با نوع authjs و فعال‌سازی trustHost برای جلوگیری از خطاهای دامنه.
    • globalAppMiddleware: false: میدلور احراز هویت فقط برای مسیرهای خاص (مانند /bookings) اعمال می‌شود.
    • runtimeConfig.authSecret: برای امضای توکن‌های سشن استفاده می‌شود.
  • پیکربندی ارائه‌دهندگان در server/api/auth/[...].ts:

   // server/api/auth/[...].ts
   import { NuxtAuthHandler } from '#auth';
   import GithubProvider from 'next-auth/providers/github';
   import GoogleProvider from 'next-auth/providers/google';

   export default NuxtAuthHandler({
     secret: process.env.AUTH_SECRET, // کلید مخفی برای امضای JWT
     providers: [
       GithubProvider({
         clientId: process.env.GITHUB_CLIENT_ID,
         clientSecret: process.env.GITHUB_CLIENT_SECRET,
       }),
       GoogleProvider({
         clientId: process.env.GOOGLE_CLIENT_ID,
         clientSecret: process.env.GOOGLE_CLIENT_SECRET,
       }),
     ],
     callbacks: {
       async session({ session, token }) {
         session.user.id = token.sub; // افزودن شناسه کاربر به سشن
         session.user.role = token.role || 'user'; // نقش پیش‌فرض
         return session;
       },
       async jwt({ token, user }) {
         if (user) {
           token.role = user.role || 'user'; // ذخیره نقش در توکن
         }
         return token;
       },
     },
   });
  • توضیحات:

    • secret: کلید مخفی برای امضای توکن‌های JWT (از .env خوانده می‌شود).
    • providers: پشتیبانی از GitHub و Google برای ورود با OAuth.
    • callbacks.session: اطلاعات کاربر (مانند id و role) را به سشن اضافه می‌کند.
    • callbacks.jwt: اطلاعات اضافی (مانند نقش کاربر) را در توکن JWT ذخیره می‌کند.
  • متغیرهای محیطی در .env:

   AUTH_SECRET=your-secure-auth-secret
   GITHUB_CLIENT_ID=your-github-client-id
   GITHUB_CLIENT_SECRET=your-github-client-secret
   GOOGLE_CLIENT_ID=your-google-client-id
   GOOGLE_CLIENT_SECRET=your-google-client-secret
   APP_URL=https://your-app-url.com
  • توضیحات:
    • AUTH_SECRET: یک رشته تصادفی قوی برای امضای سشن‌ها (حداقل ۳۲ کاراکتر).
    • کلیدهای GitHub و Google از داشبورد توسعه‌دهندگان هر پلتفرم دریافت می‌شوند.
    • APP_URL: URL پایه اپلیکیشن برای ریدایرکت‌های OAuth.

مدیریت سشن در کلاینت

مدیریت سشن‌ها در سمت کلاینت برای نمایش اطلاعات کاربر، بررسی وضعیت ورود، و کنترل دسترسی به صفحات حساس (مانند /bookings) حیاتی است.

نمایش اطلاعات کاربر در pages/bookings.vue:

<!-- pages/bookings.vue -->
<script setup>
import { useAuth } from '#auth';
import { useBookingsStore } from '~/stores/bookings';
import { useFormatDate } from '~/composables/useFormatDate';

const { data: authData, status } = useAuth();
const bookingsStore = useBookingsStore();

onMounted(() => {
  if (status.value === 'authenticated') {
    bookingsStore.fetchBookings();
  }
});
</script>

<template>
  <div class="container mx-auto p-6">
    <h1 class="text-3xl font-bold mb-6">Your Bookings</h1>
    <div v-if="status === 'authenticated' && authData">
      <p class="mb-4">Welcome, {{ authData.user.name }} ({{ authData.user.email }})</p>
      <div v-for="booking in bookingsStore.bookings" :key="booking.id" class="card mb-4">
        <p>Ship ID: {{ booking.shipId }}</p>
        <p>Dates: {{ useFormatDate(booking.startDate) }} to {{ useFormatDate(booking.endDate) }}</p>
        <p>Total Price: ${{ booking.totalPrice }}</p>
      </div>
    </div>
    <div v-else class="text-red-500">
      Please <NuxtLink to="/login" class="underline">sign in</NuxtLink> to view your bookings.
    </div>
  </div>
</template>
  • توضیحات:

  • useAuth: اطلاعات کاربر (authData) و وضعیت احراز هویت (status) را ارائه می‌دهد.

  • اگر کاربر وارد شده باشد (status === 'authenticated')، رزروها از store بارگذاری می‌شوند.
  • در غیر این صورت، کاربر به صفحه ورود هدایت می‌شود.

کامپوزبل برای فرمت تاریخ (composables/useFormatDate.ts):

export const useFormatDate = (date: Date) => {
  return new Date(date).toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric',
  });
};

خروج از حساب و ادغام با هدر

برای تکمیل تجربه کاربری، قابلیت خروج از حساب به هدر اضافه شده و با رابط کاربری ادغام می‌شود.

به‌روزرسانی components/AppHeader.vue:

<!-- components/AppHeader.vue -->
<template>
  <header class="bg-blue-800 text-white p-4 sticky top-0 z-10">
    <nav class="container mx-auto flex justify-between items-center">
      <NuxtLink to="/" class="text-2xl font-bold tracking-tight">
        Cruise Rental
      </NuxtLink>
      <div class="space-x-4 flex items-center">
        <NuxtLink to="/ships" class="hover:underline hover:text-blue-200 transition duration-200">
          Ships
        </NuxtLink>
        <NuxtLink to="/bookings" class="hover:underline hover:text-blue-200 transition duration-200">
          Bookings
        </NuxtLink>
        <template v-if="authData">
          <span class="text-sm">{{ authData.user?.name }}</span>
          <button @click="signOut" class="btn btn-sm bg-red-500 hover:bg-red-600">
            Sign Out
          </button>
        </template>
        <NuxtLink v-else to="/login" class="btn btn-sm">
          Sign In
        </NuxtLink>
      </div>
    </nav>
  </header>
</template>

<script setup>
import { useAuth, signOut } from '#auth';

const { data: authData } = useAuth();

const handleSignOut = async () => {
  await signOut({ callbackUrl: '/' });
};
</script>
  • توضیحات:

  • دکمه Sign Out با استفاده از signOut از #auth پیاده‌سازی شده و کاربر را به صفحه اصلی (/) هدایت می‌کند.

  • نمایش پویا: اگر authData وجود داشته باشد، نام کاربر و دکمه خروج نمایش داده می‌شود؛ در غیر این صورت، دکمه ورود ظاهر می‌شود.

میدلور احراز هویت

برای محدود کردن دسترسی به صفحات حساس (مانند /bookings)، یک میدلور احراز هویت پیاده‌سازی شده است.

کد میدلور:

// middleware/auth.ts
import { useAuth } from '#auth';

export default defineNuxtRouteMiddleware(async (to) => {
  const { status } = useAuth();
  if (to.path === '/bookings' && status.value !== 'authenticated') {
    return navigateTo('/login');
  }
});
  • توضیحات:

  • این میدلور بررسی می‌کند که آیا کاربر برای دسترسی به /bookings احراز هویت شده است.

  • اگر کاربر وارد نشده باشد، به صفحه /login هدایت می‌شود.
  • در nuxt.config.ts، این میدلور برای مسیر /bookings اعمال شده است:
    routeRules: {
      '/bookings': { ssr: true, middleware: ['auth'] },
    },

صفحه ورود (login.vue)

صفحه ورود برای ارائه گزینه‌های ورود با GitHub و Google طراحی شده است.

کد کامل:

<!-- pages/login.vue -->
<template>
  <div class="container mx-auto p-6 max-w-md">
    <h1 class="text-3xl font-bold mb-6 text-center">Sign In</h1>
    <div class="space-y-4">
      <button @click="signInWithGithub" class="btn w-full flex items-center justify-center">
        <svg class="w-5 h-5 mr-2" viewBox="0 0 24 24" fill="currentColor">
          <!-- آیکون GitHub -->
          <path d="M12 2C6.48 2 2 6.48 2 12c0 4.42 2.87 8.17 6.84 9.5.5.09.66-.22.66-.48v-1.7c-2.78.61-3.37-1.34-3.37-1.34-.46-1.16-1.12-1.47-1.12-1.47-.91-.62.07-.61.07-.61 1.01.07 1.54 1.04 1.54 1.04.89 1.53 2.34 1.09 2.91.83.09-.65.35-1.09.63-1.34-2.22-.25-4.56-1.11-4.56-4.94 0-1.09.39-1.98 1.03-2.68-.1-.25-.45-1.26.1-2.63 0 0 .84-.27 2.75 1.02A9.57 9.57 0 0112 6.9c.85 0 1.71.11 2.52.33 1.91-1.29 2.75-1.02 2.75-1.02.55 1.37.2 2.38.1 2.63.64.7 1.03 1.59 1.03 2.68 0 3.84-2.34 4.69-4.57 4.94.36.31.67.94.67 1.9v2.81c0 .27.16.58.67.48A10.01 10.01 0 0022 12c0-5.52-4.48-10-10-10z" />
        </svg>
        Sign in with GitHub
      </button>
      <button @click="signInWithGoogle" class="btn w-full flex items-center justify-center">
        <svg class="w-5 h-5 mr-2" viewBox="0 0 24 24" fill="currentColor">
          <!-- آیکون Google -->
          <path d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147.978-2.758 1.595-4.493 1.595-3.42 0-6.316-2.837-6.316-6.337 0-3.5 2.896-6.337 6.316-6.337 1.536 0 2.943.54 4.02 1.435l2.395-2.395C18.695 3.117 15.776 1 12.48 1 6.15 1 1 6.15 1 12.48c0 6.33 5.15 11.48 11.48 11.48 5.775 0 10.65-4.104 11.773-9.614h-10.773v-3.934z" />
        </svg>
        Sign in with Google
      </button>
    </div>
  </template>
</script>

<script setup>
import { signIn } from '#auth';

const signInWithGithub = async () => {
  await signIn('github', { callbackUrl: '/bookings' });
};

const signInWithGoogle = async () => {
  await signIn('google', { callbackUrl: '/bookings' });
};
</script>
  • توضیحات:

  • دکمه‌های ورود با آیکون‌های SVG برای GitHub و Google طراحی شده‌اند.

  • signIn: تابع از #auth برای شروع فرآیند ورود با ریدایرکت به /bookings پس از موفقیت.
  • طراحی با Tailwind (مانند max-w-md, btn) برای پاسخ‌گویی و زیبایی.

چرا این بخش در ضمیمه‌ها خلاصه بود؟

ضمیمه‌های اولیه فقط به پیاده‌سازی اولیه احراز هویت با GitHub اشاره کردند و جزئیات زیر را پوشش ندادند:

-پشتیبانی از ارائه‌دهنده Google.

  • مدیریت سشن با استفاده از callbacks در Auth.js.

  • صفحه ورود کامل با گزینه‌های متعدد.

  • ادغام با هدر و نمایش پویای اطلاعات کاربر.


نکات عملی برای پیاده‌سازی

  1. دریافت کلیدهای OAuth:

  2. برای GitHub: در GitHub Developer Settings، یک OAuth App ایجاد کنید و clientId و clientSecret را کپی کنید.

  3. برای Google: در Google Cloud Console، یک پروژه OAuth ایجاد کنید.
  4. callbackUrl را به https://your-app-url.com/api/auth/callback/github و مشابه برای Google تنظیم کنید.

  5. امنیت:

  6. فایل .env را در .gitignore قرار دهید.

  7. از یک AUTH_SECRET قوی استفاده کنید (با ابزارهایی مانند openssl rand -base64 32).

  8. تست:

  9. با npm run dev، ورود و خروج را تست کنید.

  10. بررسی کنید که میدلور /bookings کاربران غیرمجاز را به /login هدایت می‌کند.

  11. گسترش:

  12. افزودن ارائه‌دهندگان دیگر (مانند Auth0 یا Twitter).

  13. پیاده‌سازی نقش‌های کاربری (مانند admin برای مدیریت کشتی‌ها):
     // server/api/auth/[...].ts
     callbacks: {
       async session({ session, token }) {
         session.user.id = token.sub;
         session.user.role = token.role || 'user';
         if (session.user.email === 'admin@example.com') {
           session.user.role = 'admin';
         }
         return session;
       },
     }

جمع‌بندی ضمیمه د

این ضمیمه جزئیات کامل پیاده‌سازی احراز هویت با @sidebase/nuxt-auth را ارائه داد، شامل پیکربندی ارائه‌دهندگان GitHub و Google، مدیریت سشن‌ها، صفحه ورود، و ادغام با هدر و میدلور. این سیستم اپلیکیشن را ایمن‌تر و کاربرپسندتر می‌کند. برای اطلاعات بیشتر، به فصل یازدهم یا مخزن فرضی GitHub (https://github.com/khosronz/cruise-rental-app) مراجعه کنید.