跳至內容

驗證

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

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

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

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

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

本頁面上的範例以使用者名稱和密碼驗證為例,說明基本概念。雖然您可以實作自訂驗證解決方案,但為了提高安全性和簡化流程,我們建議使用驗證函式庫。這些函式庫提供內建的驗證、工作階段管理和授權解決方案,以及其他功能,例如社群登入、多因素驗證和基於角色的存取控制。您可以在驗證函式庫區段中找到列表。

驗證

註冊和登入功能

您可以使用 <form> 元素搭配 React 的 伺服器動作useFormState 來擷取使用者憑證、驗證表單欄位,並呼叫您的驗證提供者的 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>
  )
}

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 的 useFormState hook 在表單提交時顯示驗證錯誤。

app/ui/signup-form.tsx
'use client'
 
import { useFormState, useFormStatus } from 'react-dom'
import { signup } from '@/app/actions/auth'
 
export function SignupForm() {
  const [state, action] = useFormState(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>
      )}
      <SubmitButton />
    </form>
  )
}
 
function SubmitButton() {
  const { pending } = useFormStatus()
 
  return (
    <button disabled={pending} type="submit">
      Sign Up
    </button>
  )
}

注意事項

  • 這些範例使用 React 的 useFormState hook,它與 Next.js App Router 捆綁在一起。如果您使用的是 React 19,請改用 useActionState。有關更多資訊,請參閱 React 文件
  • 在 React 19 中,useFormStatus 在返回的物件中包含額外的鍵,例如 data、method 和 action。如果您沒有使用 React 19,則只有 pending 鍵可用。
  • 在 React 19 中,useActionState 也在返回的狀態中包含 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 執行環境相容)和 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')
  }
}

提示:

  • 有效負載應包含後續請求中將使用的**最少**、唯一的使用者資料,例如使用者的 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 })(await cookies()).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. 安全型:使用儲存在資料庫中的工作階段數據檢查使用者是否有權存取路由或執行操作。這些檢查更安全,用於需要存取敏感數據或操作的操作。

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

使用中間件進行樂觀型檢查(選用)

在某些情況下,您可能需要使用中間件並根據權限重新導向使用者。

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

然而,由於中間件在每個路由上運行,包括預取的路由,因此僅從 Cookie 中讀取工作階段(樂觀型檢查)非常重要,並避免資料庫檢查以防止效能問題。

例如:

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) 對於初步檢查很有用,但不應成為保護資料的唯一防線。大多數安全性檢查應盡可能靠近資料來源執行,詳情請參閱資料存取層 (Data Access Layer)

提示:

  • 在中間件中,您也可以使用 req.cookies.get('session').value 讀取 cookie。
  • 中間件使用邊緣運行時 (Edge Runtime),請檢查您的驗證程式庫和工作階段管理程式庫是否相容。
  • 您可以使用中間件中的 matcher 屬性來指定中間件應在哪個路由上運行。雖然,對於驗證,建議中間件在所有路由上運行。

建立資料存取層 (DAL)

我們建議建立一個 DAL 來集中處理您的資料請求和授權邏輯。

DAL 應包含一個函式,用於在使用者與您的應用程式互動時驗證使用者的工作階段。此函式至少應檢查工作階段是否有效,然後重新導向或返回進行進一步請求所需的使用者資訊。

例如,為您的 DAL 建立一個單獨的檔案,其中包含一個 verifySession() 函式。然後使用 React 的 cache API 在 React 渲染過程中記憶體化函式的返回值。

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 可用於保護請求時擷取的資料。但是,對於使用者之間共用資料的靜態路由,資料將在建置時而不是請求時擷取。請使用中間件來保護靜態路由。
  • 為了進行安全檢查,您可以透過將工作階段 ID 與您的資料庫進行比較來檢查工作階段是否有效。使用 React 的 cache 函式,避免在渲染過程中向資料庫發送不必要的重複請求。
  • 您可能希望將相關的資料請求整合到一個 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 安全性文章中了解更多關於安全最佳實務的資訊。

伺服器元件

伺服器元件中的驗證檢查對於基於角色的存取控制很有用。例如,根據使用者角色有條件地渲染元件

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() 函式來檢查「管理員」、「使用者」和未授權的角色。這種模式可確保每個使用者僅與適合其角色的元件互動。

佈局和驗證檢查

由於部分渲染的關係,在佈局中進行檢查時要小心,因為這些佈局在導覽時不會重新渲染,這意味著使用者工作階段不會在每次路由更改時都進行檢查。

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

例如,考慮一個共用的佈局,它會擷取使用者資料並在導覽列中顯示使用者圖片。您不應該在佈局中進行驗證檢查,而應該在佈局中擷取使用者資料 (getUser()),並在您的 DAL 中進行驗證檢查。

這可以確保無論在應用程式中的何處呼叫 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 中的常見模式是在佈局或頂層元件中,如果使用者未經授權,則返回 null。由於 Next.js 應用程式有多個進入點,因此**不建議**使用此模式,因為它不會阻止存取巢狀路由區段和伺服器動作。

伺服器動作

處理 伺服器動作 時,應採取與面向公眾的 API 端點相同的安全考量,並驗證使用者是否被允許執行變更。

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

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
}

路由處理程式

處理 路由處理程式 時,應採取與面向公眾的 API 端點相同的安全考量,並驗證使用者是否被允許存取路由處理程式。

例如:

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
}

上面的範例展示了一個具有雙層安全檢查的路由處理程式。它首先檢查是否有有效的連線階段,然後驗證登入的使用者是否為「管理員」。

上下文提供者

由於 交錯 的特性,使用上下文提供者進行驗證是可行的。然而,React 的 context 在伺服器元件中不受支援,因此它們僅適用於客戶端元件。

這種方法有效,但任何子伺服器元件都會先在伺服器上渲染,並且無法存取上下文提供者的階段資料

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 (
    // ...
  );
}

如果在客戶端元件中需要階段資料(例如,用於客戶端資料擷取),請使用 React 的 taintUniqueValue API,以防止敏感的階段資料暴露給客戶端。

資源

既然您已經了解了 Next.js 中的驗證,以下是一些與 Next.js 相容的函式庫和資源,可協助您實作安全的驗證和階段管理

驗證函式庫

工作階段管理函式庫

延伸閱讀

若要繼續學習驗證和安全性,請查看下列資源