跳至內容

驗證

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

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

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

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

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

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

驗證

以下是實作註冊和/或登入表單的步驟

  1. 使用者透過表單提交其憑證。
  2. 表單發送一個由 API 路由處理的請求。
  3. 驗證成功後,程序完成,表示使用者已成功驗證。
  4. 如果驗證失敗,則會顯示錯誤訊息。

考慮一個使用者可以輸入其憑證的登入表單

pages/login.tsx
import { FormEvent } from 'react'
import { useRouter } from 'next/router'
 
export default function LoginPage() {
  const router = useRouter()
 
  async function handleSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault()
 
    const formData = new FormData(event.currentTarget)
    const email = formData.get('email')
    const password = formData.get('password')
 
    const response = await fetch('/api/auth/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, password }),
    })
 
    if (response.ok) {
      router.push('/profile')
    } else {
      // Handle errors
    }
  }
 
  return (
    <form onSubmit={handleSubmit}>
      <input type="email" name="email" placeholder="Email" required />
      <input type="password" name="password" placeholder="Password" required />
      <button type="submit">Login</button>
    </form>
  )
}

上面的表單有兩個輸入欄位,用於擷取使用者的電子郵件和密碼。提交後,它會觸發一個函式,向 API 路由 (/api/auth/login) 發送 POST 請求。

然後,您可以在 API 路由中呼叫您的驗證提供者的 API 來處理驗證

pages/api/auth/login.ts
import type { NextApiRequest, NextApiResponse } from 'next'
import { signIn } from '@/auth'
 
export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  try {
    const { email, password } = req.body
    await signIn('credentials', { email, password })
 
    res.status(200).json({ success: true })
  } catch (error) {
    if (error.type === 'CredentialsSignin') {
      res.status(401).json({ error: 'Invalid credentials.' })
    } else {
      res.status(500).json({ error: 'Something went wrong.' })
    }
  }
}

工作階段管理

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

有兩種類型的工作階段

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

貼心小提醒:雖然兩種方法都能使用,或者兩種方法同時使用,但我們建議使用像是 iron-sessionJose 等 session 管理函式庫。

無狀態工作階段

設定和刪除 Cookie

您可以使用 API 路由 在伺服器上將工作階段設定為 Cookie。

pages/api/login.ts
import { serialize } from 'cookie'
import type { NextApiRequest, NextApiResponse } from 'next'
import { encrypt } from '@/app/lib/session'
 
export default function handler(req: NextApiRequest, res: NextApiResponse) {
  const sessionData = req.body
  const encryptedSessionData = encrypt(sessionData)
 
  const cookie = serialize('session', encryptedSessionData, {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    maxAge: 60 * 60 * 24 * 7, // One week
    path: '/',
  })
  res.setHeader('Set-Cookie', cookie)
  res.status(200).json({ message: 'Successfully set cookie!' })
}

資料庫工作階段

要建立和管理資料庫工作階段,您需要按照以下步驟操作:

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

在伺服器上建立工作階段:

pages/api/create-session.ts
import db from '../../lib/db'
import type { NextApiRequest, NextApiResponse } from 'next'
 
export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  try {
    const user = req.body
    const sessionId = generateSessionId()
    await db.insertSession({
      sessionId,
      userId: user.id,
      createdAt: new Date(),
    })
 
    res.status(200).json({ sessionId })
  } catch (error) {
    res.status(500).json({ error: 'Internal Server Error' })
  }
}

授權

使用者通過身份驗證並建立工作階段後,您可以實作授權來控制使用者在應用程式內的存取權限和操作。

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

  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$).*)'],
}

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

提示:

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

建立資料存取層 (DAL)

保護 API 路由

Next.js 中的 API 路由對於處理伺服器端邏輯和資料管理至關重要。保護這些路由以確保只有授權的使用者才能存取特定功能至關重要。這通常涉及驗證使用者的身份驗證狀態及其基於角色的權限。

以下是如何保護 API 路由的範例:

pages/api/route.ts
import { NextApiRequest, NextApiResponse } from 'next'
 
export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const session = await getSession(req)
 
  // Check if the user is authenticated
  if (!session) {
    res.status(401).json({
      error: 'User is not authenticated',
    })
    return
  }
 
  // Check if the user has the 'admin' role
  if (session.user.role !== 'admin') {
    res.status(401).json({
      error: 'Unauthorized access: User does not have admin privileges.',
    })
    return
  }
 
  // Proceed with the route for authorized users
  // ... implementation of the API Route
}

此範例示範了一個 API 路由,具有兩層安全檢查機制,分別用於驗證和授權。它首先檢查是否有有效的連線階段,然後驗證登入的使用者是否為「管理員」。這種方法可確保安全存取,僅限已驗證和授權的使用者,維護請求處理的穩固安全性。

資源

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

驗證函式庫

連線階段管理函式庫

延伸閱讀

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