跳到內容

驗證

理解驗證對於保護應用程式的資料至關重要。本頁面將引導您瞭解使用 React 和 Next.js 的哪些功能來實作驗證。

在開始之前,將流程分解為三個概念會有所幫助

  1. 驗證 (Authentication):驗證使用者是否為他們聲稱的身分。這需要使用者提供身分證明,例如使用者名稱和密碼。
  2. 工作階段管理 (Session Management):追蹤使用者跨請求的驗證狀態。
  3. 授權 (Authorization):決定使用者可以存取哪些路由和資料。

此圖表顯示使用 React 和 Next.js 功能的驗證流程

Diagram showing the authentication flow with React and Next.js features

本頁面上的範例逐步介紹了用於教育目的的基本使用者名稱和密碼驗證。雖然您可以實作自訂驗證解決方案,但為了提高安全性和簡化性,我們建議使用驗證函式庫。這些函式庫為驗證、工作階段管理和授權提供內建解決方案,以及社交登入、多因素驗證和基於角色的存取控制等額外功能。您可以在驗證函式庫章節中找到清單。

驗證

註冊和登入功能

您可以將 <form> 元素與 React 的 伺服器行為useActionState 結合使用,以擷取使用者憑證、驗證表單欄位,並呼叫您的驗證提供者的 API 或資料庫。

由於伺服器行為始終在伺服器上執行,因此它們為處理驗證邏輯提供了安全的環境。

以下是實作註冊/登入功能的步驟

1. 擷取使用者憑證

若要擷取使用者憑證,請建立一個表單,該表單在提交時調用伺服器行為。例如,一個註冊表單,接受使用者的姓名、電子郵件和密碼

app/ui/signup-form.tsx
import { signup } from '@/app/actions/auth'
 
export function SignupForm() {
  return (
    <form action={signup}>
      <div>
        <label htmlFor="name">Name</label>
        <input id="name" name="name" placeholder="Name" />
      </div>
      <div>
        <label htmlFor="email">Email</label>
        <input id="email" name="email" type="email" placeholder="Email" />
      </div>
      <div>
        <label htmlFor="password">Password</label>
        <input id="password" name="password" type="password" />
      </div>
      <button type="submit">Sign Up</button>
    </form>
  )
}
app/actions/auth.ts
export async function signup(formData: FormData) {}

2. 在伺服器上驗證表單欄位

使用伺服器行為在伺服器上驗證表單欄位。如果您的驗證提供者未提供表單驗證,您可以使用架構驗證函式庫,例如 ZodYup

以 Zod 為例,您可以定義一個包含適當錯誤訊息的表單架構

app/lib/definitions.ts
import { z } from 'zod'
 
export const SignupFormSchema = z.object({
  name: z
    .string()
    .min(2, { message: 'Name must be at least 2 characters long.' })
    .trim(),
  email: z.string().email({ message: 'Please enter a valid email.' }).trim(),
  password: z
    .string()
    .min(8, { message: 'Be at least 8 characters long' })
    .regex(/[a-zA-Z]/, { message: 'Contain at least one letter.' })
    .regex(/[0-9]/, { message: 'Contain at least one number.' })
    .regex(/[^a-zA-Z0-9]/, {
      message: 'Contain at least one special character.',
    })
    .trim(),
})
 
export type FormState =
  | {
      errors?: {
        name?: string[]
        email?: string[]
        password?: string[]
      }
      message?: string
    }
  | undefined

為了防止不必要地呼叫驗證提供者的 API 或資料庫,如果任何表單欄位與定義的架構不符,您可以在伺服器行為中儘早 return

app/actions/auth.ts
import { SignupFormSchema, FormState } from '@/app/lib/definitions'
 
export async function signup(state: FormState, formData: FormData) {
  // Validate form fields
  const validatedFields = SignupFormSchema.safeParse({
    name: formData.get('name'),
    email: formData.get('email'),
    password: formData.get('password'),
  })
 
  // If any form fields are invalid, return early
  if (!validatedFields.success) {
    return {
      errors: validatedFields.error.flatten().fieldErrors,
    }
  }
 
  // Call the provider or db to create a user...
}

回到您的 <SignupForm />,您可以使用 React 的 useActionState Hook 在表單提交時顯示驗證錯誤

app/ui/signup-form.tsx
'use client'
 
import { signup } from '@/app/actions/auth'
import { useActionState } from 'react'
 
export default function SignupForm() {
  const [state, action, pending] = useActionState(signup, undefined)
 
  return (
    <form action={action}>
      <div>
        <label htmlFor="name">Name</label>
        <input id="name" name="name" placeholder="Name" />
      </div>
      {state?.errors?.name && <p>{state.errors.name}</p>}
 
      <div>
        <label htmlFor="email">Email</label>
        <input id="email" name="email" placeholder="Email" />
      </div>
      {state?.errors?.email && <p>{state.errors.email}</p>}
 
      <div>
        <label htmlFor="password">Password</label>
        <input id="password" name="password" type="password" />
      </div>
      {state?.errors?.password && (
        <div>
          <p>Password must:</p>
          <ul>
            {state.errors.password.map((error) => (
              <li key={error}>- {error}</li>
            ))}
          </ul>
        </div>
      )}
      <button disabled={pending} type="submit">
        Sign Up
      </button>
    </form>
  )
}

要知道

  • 在 React 19 中,useFormStatus 在傳回的物件上包含額外的鍵,例如 data、method 和 action。如果您未使用 React 19,則僅 pending 鍵可用。
  • 在變更資料之前,您應始終確保使用者也已獲得執行操作的授權。請參閱驗證和授權

3. 建立使用者或檢查使用者憑證

驗證表單欄位後,您可以呼叫驗證提供者的 API 或資料庫,以建立新的使用者帳戶或檢查使用者是否存在。

繼續之前的範例

app/actions/auth.tsx
export async function signup(state: FormState, formData: FormData) {
  // 1. Validate form fields
  // ...
 
  // 2. Prepare data for insertion into database
  const { name, email, password } = validatedFields.data
  // e.g. Hash the user's password before storing it
  const hashedPassword = await bcrypt.hash(password, 10)
 
  // 3. Insert the user into the database or call an Auth Library's API
  const data = await db
    .insert(users)
    .values({
      name,
      email,
      password: hashedPassword,
    })
    .returning({ id: users.id })
 
  const user = data[0]
 
  if (!user) {
    return {
      message: 'An error occurred while creating your account.',
    }
  }
 
  // TODO:
  // 4. Create user session
  // 5. Redirect user
}

成功建立使用者帳戶或驗證使用者憑證後,您可以建立工作階段來管理使用者的驗證狀態。根據您的工作階段管理策略,工作階段可以儲存在 Cookie 或資料庫中,或兩者都儲存。繼續工作階段管理章節以瞭解更多資訊。

提示

  • 上面的範例很冗長,因為它為了教育目的而分解了驗證步驟。這突顯了實作您自己的安全解決方案可能很快變得複雜。考慮使用驗證函式庫來簡化流程。
  • 為了改善使用者體驗,您可能希望在註冊流程的早期檢查重複的電子郵件或使用者名稱。例如,當使用者輸入使用者名稱或輸入欄位失去焦點時。這有助於防止不必要的表單提交,並為使用者提供即時回饋。您可以使用諸如 use-debounce 等函式庫來管理這些檢查的頻率,以進行請求防抖。

工作階段管理

工作階段管理確保使用者的驗證狀態在跨請求時保持不變。它涉及建立、儲存、重新整理和刪除工作階段或權杖。

有兩種工作階段類型

  1. 無狀態:工作階段資料 (或權杖) 儲存在瀏覽器的 Cookie 中。Cookie 會隨每個請求一起傳送,以便在伺服器上驗證工作階段。此方法更簡單,但如果實作不正確,則可能安全性較低。
  2. 資料庫:工作階段資料儲存在資料庫中,使用者的瀏覽器僅接收加密的工作階段 ID。此方法更安全,但可能很複雜且使用更多伺服器資源。

要知道: 雖然您可以使用任一方法或兩種方法都使用,但我們建議使用工作階段管理函式庫,例如 iron-sessionJose

無狀態工作階段

若要建立和管理無狀態工作階段,您需要遵循幾個步驟

  1. 產生一個密鑰,它將用於簽署您的工作階段,並將其儲存為環境變數
  2. 編寫邏輯以使用工作階段管理函式庫加密/解密工作階段資料。
  3. 使用 Next.js cookies API 管理 Cookie。

除了上述內容外,請考慮新增功能,以便在使用者返回應用程式時更新 (或重新整理)工作階段,並在使用者登出時刪除工作階段。

要知道: 檢查您的 驗證函式庫 是否包含工作階段管理。

1. 產生密鑰

您可以透過多種方式產生密鑰來簽署您的工作階段。例如,您可以選擇在終端機中使用 openssl 命令

終端機
openssl rand -base64 32

此命令會產生一個 32 個字元的隨機字串,您可以將其用作密鑰並儲存在您的環境變數檔案

.env
SESSION_SECRET=your_secret_key

然後,您可以在工作階段管理邏輯中參考此金鑰

app/lib/session.js
const secretKey = process.env.SESSION_SECRET

2. 加密和解密工作階段

接下來,您可以使用您偏好的工作階段管理函式庫來加密和解密工作階段。繼續之前的範例,我們將使用 Jose (與 Edge Runtime 相容) 和 React 的 server-only 套件,以確保您的工作階段管理邏輯僅在伺服器上執行。

app/lib/session.ts
import 'server-only'
import { SignJWT, jwtVerify } from 'jose'
import { SessionPayload } from '@/app/lib/definitions'
 
const secretKey = process.env.SESSION_SECRET
const encodedKey = new TextEncoder().encode(secretKey)
 
export async function encrypt(payload: SessionPayload) {
  return new SignJWT(payload)
    .setProtectedHeader({ alg: 'HS256' })
    .setIssuedAt()
    .setExpirationTime('7d')
    .sign(encodedKey)
}
 
export async function decrypt(session: string | undefined = '') {
  try {
    const { payload } = await jwtVerify(session, encodedKey, {
      algorithms: ['HS256'],
    })
    return payload
  } catch (error) {
    console.log('Failed to verify session')
  }
}

提示:

  • payload 應包含最少、唯一的使用者資料,這些資料將在後續請求中使用,例如使用者的 ID、角色等。它不應包含個人身分識別資訊,例如電話號碼、電子郵件地址、信用卡資訊等,或密碼等敏感資料。

若要將工作階段儲存在 Cookie 中,請使用 Next.js cookies API。Cookie 應在伺服器上設定,並包含建議的選項

  • HttpOnly:防止用戶端 JavaScript 存取 Cookie。
  • Secure:使用 https 傳送 Cookie。
  • SameSite:指定是否可以將 Cookie 與跨網站請求一起傳送。
  • Max-Age 或 Expires:在一段時間後刪除 Cookie。
  • Path:定義 Cookie 的 URL 路徑。

請參閱 MDN 以取得有關這些選項中每個選項的更多資訊。

app/lib/session.ts
import 'server-only'
import { cookies } from 'next/headers'
 
export async function createSession(userId: string) {
  const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
  const session = await encrypt({ userId, expiresAt })
  const cookieStore = await cookies()
 
  cookieStore.set('session', session, {
    httpOnly: true,
    secure: true,
    expires: expiresAt,
    sameSite: 'lax',
    path: '/',
  })
}

回到您的伺服器行為,您可以調用 createSession() 函數,並使用 redirect() API 將使用者重新導向到適當的頁面

app/actions/auth.ts
import { createSession } from '@/app/lib/session'
 
export async function signup(state: FormState, formData: FormData) {
  // Previous steps:
  // 1. Validate form fields
  // 2. Prepare data for insertion into database
  // 3. Insert the user into the database or call an Library API
 
  // Current steps:
  // 4. Create user session
  await createSession(user.id)
  // 5. Redirect user
  redirect('/profile')
}

提示:

  • Cookie 應在伺服器上設定,以防止用戶端篡改。
  • 🎥 觀看:瞭解有關使用 Next.js 的無狀態工作階段和驗證的更多資訊 → YouTube (11 分鐘)

更新 (或重新整理) 工作階段

您也可以延長工作階段的到期時間。這對於在使用者再次存取應用程式後保持使用者登入狀態很有用。例如

app/lib/session.ts
import 'server-only'
import { cookies } from 'next/headers'
import { decrypt } from '@/app/lib/session'
 
export async function updateSession() {
  const session = (await cookies()).get('session')?.value
  const payload = await decrypt(session)
 
  if (!session || !payload) {
    return null
  }
 
  const expires = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
 
  const cookieStore = await cookies()
  cookieStore.set('session', session, {
    httpOnly: true,
    secure: true,
    expires: expires,
    sameSite: 'lax',
    path: '/',
  })
}

提示: 檢查您的驗證函式庫是否支援重新整理權杖,重新整理權杖可用於延長使用者的工作階段。

刪除工作階段

若要刪除工作階段,您可以刪除 Cookie

app/lib/session.ts
import 'server-only'
import { cookies } from 'next/headers'
 
export async function deleteSession() {
  const cookieStore = await cookies()
  cookieStore.delete('session')
}

然後您可以在您的應用程式中重複使用 deleteSession() 函數,例如,在登出時

app/actions/auth.ts
import { cookies } from 'next/headers'
import { deleteSession } from '@/app/lib/session'
 
export async function logout() {
  deleteSession()
  redirect('/login')
}

資料庫工作階段

若要建立和管理資料庫工作階段,您需要遵循以下步驟

  1. 在資料庫中建立一個表格以儲存工作階段和資料 (或檢查您的驗證函式庫是否處理此問題)。
  2. 實作插入、更新和刪除工作階段的功能
  3. 在將工作階段 ID 儲存在使用者的瀏覽器中之前對其進行加密,並確保資料庫和 Cookie 保持同步 (這是選用的,但建議用於中介層中的樂觀驗證檢查)。

例如

app/lib/session.ts
import cookies from 'next/headers'
import { db } from '@/app/lib/db'
import { encrypt } from '@/app/lib/session'
 
export async function createSession(id: number) {
  const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
 
  // 1. Create a session in the database
  const data = await db
    .insert(sessions)
    .values({
      userId: id,
      expiresAt,
    })
    // Return the session ID
    .returning({ id: sessions.id })
 
  const sessionId = data[0].id
 
  // 2. Encrypt the session ID
  const session = await encrypt({ sessionId, expiresAt })
 
  // 3. Store the session in cookies for optimistic auth checks
  const cookieStore = await cookies()
  cookieStore.set('session', session, {
    httpOnly: true,
    secure: true,
    expires: expiresAt,
    sameSite: 'lax',
    path: '/',
  })
}

提示:

  • 為了更快地檢索資料,請考慮使用像 Vercel Redis 這樣的資料庫。但是,您也可以將工作階段資料保留在您的主要資料庫中,並組合資料請求以減少查詢次數。
  • 您可以選擇使用資料庫工作階段來處理更進階的使用案例,例如追蹤使用者上次登入的時間或活動裝置的數量,或讓使用者能夠登出所有裝置。

在實作工作階段管理後,您需要新增授權邏輯,以控制使用者可以在應用程式中存取和執行的操作。繼續授權章節以瞭解更多資訊。

授權

一旦使用者通過驗證並建立工作階段,您就可以實作授權來控制使用者可以在您的應用程式中存取和執行的操作。

有兩種主要的授權檢查類型

  1. 樂觀:檢查使用者是否已獲得授權,可以使用儲存在 Cookie 中的工作階段資料來存取路由或執行操作。這些檢查適用於快速操作,例如顯示/隱藏 UI 元素或根據權限或角色重新導向使用者。
  2. 安全:檢查使用者是否已獲得授權,可以使用儲存在資料庫中的工作階段資料來存取路由或執行操作。這些檢查更安全,用於需要存取敏感資料或操作的操作。

對於這兩種情況,我們建議

使用中介層進行樂觀檢查 (選用)

在某些情況下,您可能想要使用中介層並根據權限重新導向使用者

  • 執行樂觀檢查。由於中介層在每個路由上執行,因此它是集中重新導向邏輯和預先篩選未經授權使用者的好方法。
  • 保護在使用者之間共用資料的靜態路由 (例如,付費牆後面的內容)。

然而,由於 Middleware 會在每個路由上執行,包括預先載入(prefetch)的路由,因此務必僅從 Cookie 讀取 session(樂觀檢查),並避免資料庫檢查,以防止效能問題。

例如

middleware.ts
import { NextRequest, NextResponse } from 'next/server'
import { decrypt } from '@/app/lib/session'
import { cookies } from 'next/headers'
 
// 1. Specify protected and public routes
const protectedRoutes = ['/dashboard']
const publicRoutes = ['/login', '/signup', '/']
 
export default async function middleware(req: NextRequest) {
  // 2. Check if the current route is protected or public
  const path = req.nextUrl.pathname
  const isProtectedRoute = protectedRoutes.includes(path)
  const isPublicRoute = publicRoutes.includes(path)
 
  // 3. Decrypt the session from the cookie
  const cookie = (await cookies()).get('session')?.value
  const session = await decrypt(cookie)
 
  // 4. Redirect to /login if the user is not authenticated
  if (isProtectedRoute && !session?.userId) {
    return NextResponse.redirect(new URL('/login', req.nextUrl))
  }
 
  // 5. Redirect to /dashboard if the user is authenticated
  if (
    isPublicRoute &&
    session?.userId &&
    !req.nextUrl.pathname.startsWith('/dashboard')
  ) {
    return NextResponse.redirect(new URL('/dashboard', req.nextUrl))
  }
 
  return NextResponse.next()
}
 
// Routes Middleware should not run on
export const config = {
  matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
}

雖然 Middleware 對於初始檢查可能很有用,但不應作為保護資料的唯一防線。大多數的安全檢查應盡可能靠近您的資料來源執行,詳情請參閱資料存取層

提示:

  • 在 Middleware 中,您也可以使用 req.cookies.get('session').value 讀取 Cookie。
  • Middleware 使用 Edge Runtime,請檢查您的 Auth library 和 session 管理 library 是否相容。
  • 您可以使用 Middleware 中的 matcher 屬性來指定 Middleware 應在哪些路由上執行。雖然對於身份驗證,建議 Middleware 在所有路由上執行。

建立資料存取層 (DAL)

我們建議建立 DAL 以集中管理您的資料請求和授權邏輯。

DAL 應包含一個函式,用於驗證使用者與應用程式互動時的 session。至少,該函式應檢查 session 是否有效,然後重新導向或傳回使用者資訊,以進行後續請求。

例如,為您的 DAL 建立一個單獨的檔案,其中包含一個 verifySession() 函式。然後使用 React 的 cache API,在 React render pass 期間記憶(memoize)該函式的傳回值

app/lib/dal.ts
import 'server-only'
 
import { cookies } from 'next/headers'
import { decrypt } from '@/app/lib/session'
 
export const verifySession = cache(async () => {
  const cookie = (await cookies()).get('session')?.value
  const session = await decrypt(cookie)
 
  if (!session?.userId) {
    redirect('/login')
  }
 
  return { isAuth: true, userId: session.userId }
})

然後,您可以在資料請求、Server Actions、Route Handlers 中調用 verifySession() 函式

app/lib/dal.ts
export const getUser = cache(async () => {
  const session = await verifySession()
  if (!session) return null
 
  try {
    const data = await db.query.users.findMany({
      where: eq(users.id, session.userId),
      // Explicitly return the columns you need rather than the whole user object
      columns: {
        id: true,
        name: true,
        email: true,
      },
    })
 
    const user = data[0]
 
    return user
  } catch (error) {
    console.log('Failed to fetch user')
    return null
  }
})

提示:

  • DAL 可用於保護在請求時提取的資料。但是,對於在使用者之間共享資料的靜態路由,資料將在建置時提取,而不是在請求時提取。使用Middleware來保護靜態路由。
  • 為了安全檢查,您可以透過將 session ID 與您的資料庫進行比較,來檢查 session 是否有效。使用 React 的 cache 函式,以避免在 render pass 期間向資料庫發出不必要的重複請求。
  • 您可能希望在 JavaScript 類別中整合相關的資料請求,該類別在任何方法之前執行 verifySession()

使用資料傳輸物件 (DTO)

在檢索資料時,建議您僅傳回應用程式中將使用的必要資料,而不是整個物件。例如,如果您要提取使用者資料,您可能只傳回使用者的 ID 和名稱,而不是可能包含密碼、電話號碼等的整個使用者物件。

但是,如果您無法控制傳回的資料結構,或者在團隊中工作,想要避免將整個物件傳遞給客戶端,您可以使用諸如指定哪些欄位可以安全地暴露給客戶端的策略。

app/lib/dto.ts
import 'server-only'
import { getUser } from '@/app/lib/dal'
 
function canSeeUsername(viewer: User) {
  return true
}
 
function canSeePhoneNumber(viewer: User, team: string) {
  return viewer.isAdmin || team === viewer.team
}
 
export async function getProfileDTO(slug: string) {
  const data = await db.query.users.findMany({
    where: eq(users.slug, slug),
    // Return specific columns here
  })
  const user = data[0]
 
  const currentUser = await getUser(user.id)
 
  // Or return only what's specific to the query here
  return {
    username: canSeeUsername(currentUser) ? user.username : null,
    phonenumber: canSeePhoneNumber(currentUser, user.team)
      ? user.phonenumber
      : null,
  }
}

透過將您的資料請求和授權邏輯集中在 DAL 中,並使用 DTO,您可以確保所有資料請求都是安全且一致的,從而更輕鬆地維護、稽核和除錯隨著應用程式擴展。

要知道:

  • 定義 DTO 有幾種不同的方法,從使用 toJSON(),到像上面範例中的個別函式,或 JS 類別。由於這些是 JavaScript 模式,而不是 React 或 Next.js 功能,我們建議您進行一些研究,找到最適合您應用程式的模式。
  • 在我們的Next.js 安全性文章中了解更多關於安全性最佳實踐的資訊。

Server Components

Server Components 中的身份驗證檢查對於基於角色的存取非常有用。例如,根據使用者的角色有條件地渲染元件

app/dashboard/page.tsx
import { verifySession } from '@/app/lib/dal'
 
export default function Dashboard() {
  const session = await verifySession()
  const userRole = session?.user?.role // Assuming 'role' is part of the session object
 
  if (userRole === 'admin') {
    return <AdminDashboard />
  } else if (userRole === 'user') {
    return <UserDashboard />
  } else {
    redirect('/login')
  }
}

在範例中,我們使用 DAL 中的 verifySession() 函式來檢查 'admin'、'user' 和未授權的角色。此模式確保每個使用者僅與適合其角色的元件互動。

Layouts 和身份驗證檢查

由於部分渲染,在 Layouts 中進行檢查時要謹慎,因為這些不會在導航時重新渲染,這表示使用者 session 不會在每次路由變更時進行檢查。

相反地,您應該在靠近資料來源或將有條件渲染的元件附近進行檢查。

例如,考慮一個共享的 layout,它提取使用者資料並在導航欄中顯示使用者圖像。您應該在 layout 中提取使用者資料 (getUser()),並在您的 DAL 中進行身份驗證檢查,而不是在 layout 中進行身份驗證檢查。

這保證了無論在應用程式中的何處調用 getUser(),都會執行身份驗證檢查,並防止開發人員忘記檢查使用者是否被授權存取資料。

app/layout.tsx
export default async function Layout({
  children,
}: {
  children: React.ReactNode;
}) {
  const user = await getUser();
 
  return (
    // ...
  )
}
app/lib/dal.ts
export const getUser = cache(async () => {
  const session = await verifySession()
  if (!session) return null
 
  // Get user ID from session and fetch data
})

要知道

  • SPA 中的常見模式是在 layout 或頂層元件中 return null,如果使用者未經授權。此模式不建議使用,因為 Next.js 應用程式具有多個入口點,這將無法阻止存取巢狀路由區段和 Server Actions。

Server Actions

Server Actions 視為與面向公眾的 API 端點相同的安全考量,並驗證使用者是否被允許執行 mutation。

在下面的範例中,我們在允許操作繼續之前檢查使用者的角色

app/lib/actions.ts
'use server'
import { verifySession } from '@/app/lib/dal'
 
export async function serverAction(formData: FormData) {
  const session = await verifySession()
  const userRole = session?.user?.role
 
  // Return early if user is not authorized to perform the action
  if (userRole !== 'admin') {
    return null
  }
 
  // Proceed with the action for authorized users
}

Route Handlers

Route Handlers 視為與面向公眾的 API 端點相同的安全考量,並驗證使用者是否被允許存取 Route Handler。

例如

app/api/route.ts
import { verifySession } from '@/app/lib/dal'
 
export async function GET() {
  // User authentication and role verification
  const session = await verifySession()
 
  // Check if the user is authenticated
  if (!session) {
    // User is not authenticated
    return new Response(null, { status: 401 })
  }
 
  // Check if the user has the 'admin' role
  if (session.user.role !== 'admin') {
    // User is authenticated but does not have the right permissions
    return new Response(null, { status: 403 })
  }
 
  // Continue for authorized users
}

上面的範例示範了一個具有雙層安全檢查的 Route Handler。它首先檢查是否有活動的 session,然後驗證登入的使用者是否為 'admin'。

Context Providers

由於交錯處理,將 context providers 用於身份驗證是可行的。但是,React context 在 Server Components 中不受支援,使其僅適用於 Client Components。

這樣做是可行的,但任何子 Server Components 都會先在伺服器上渲染,並且無法存取 context provider 的 session 資料

app/layout.ts
import { ContextProvider } from 'auth-lib'
 
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <ContextProvider>{children}</ContextProvider>
      </body>
    </html>
  )
}
"use client";
 
import { useSession } from "auth-lib";
 
export default function Profile() {
  const { userId } = useSession();
  const { data } = useSWR(`/api/user/${userId}`, fetcher)
 
  return (
    // ...
  );
}

如果 Client Components 中需要 session 資料(例如,用於客戶端資料提取),請使用 React 的 taintUniqueValue API,以防止敏感的 session 資料暴露給客戶端。

資源

既然您已經了解 Next.js 中的身份驗證,以下是一些與 Next.js 相容的 library 和資源,可協助您實作安全的身份驗證和 session 管理

Auth Libraries

Session 管理 Libraries

延伸閱讀

若要繼續學習關於身份驗證和安全性的知識,請查看以下資源