Next.js 認證機制詳細說明


在現代網頁開發中,認證(Authentication)是一個既複雜又微妙的話題。隨著Next.js引入了Server Components、Server Actions和Middleware,這使得我們有更多方式來實現認證機制。在這篇文章中,我們將逐步解析Next.js中的認證機制,並展示一些最佳實踐及常見的陷阱。讓我們開始吧!

Youtube影片來源

GitHub:https://github.com/vercel-labs/app-router-auth/tree/main

註冊過程

認證通常從註冊過程開始。首先,我們需要創建一個表單來捕捉用戶的姓名、電子郵件和密碼。當表單提交時,會觸發一個Server Action。在新的文件中,我們會使用use server指令來創建可以從客戶端代碼調用的服務端函數。

"use server";

import { z } from "zod";

export async function signup(formData) {
  const { name, email, password } = formData;

  // 使用Zod進行表單驗證
  const schema = z.object({
    name: z.string().min(1, "姓名是必填項"),
    email: z.string().email("無效的電子郵件"),
    password: z.string().min(6, "密碼至少6位"),
  });

  const result = schema.safeParse(formData);

  if (!result.success) {
    return { errors: result.error.errors };
  }

  // 後續的註冊邏輯...
}

在這個例子中,我們使用了Zod來驗證輸入字段。如果驗證失敗,我們會及早返回錯誤。在註冊表單中,我們會使用React的useActionState來捕捉錯誤並在相應的字段旁邊顯示。

用戶創建和數據庫插入

當表單通過驗證後,我們將創建用戶並將其插入到數據庫中。這裡我們只展示基本的密碼和電子郵件認證原則,但你可能會考慮使用一個外部庫或第三方提供商來處理更複雜的認證需求。

import bcrypt from "bcrypt";
import { prisma } from "../lib/prisma";

export async function signup(formData) {
  // 前面的驗證代碼...

  const hashedPassword = await bcrypt.hash(password, 10);
  const newUser = await prisma.user.create({
    data: {
      name,
      email,
      password: hashedPassword,
    },
  });

  // 後續的會話管理邏輯...
}

會話管理

註冊成功後,我們需要創建一個持久化的會話,這樣用戶在導航到新頁面時不需要重新登錄。我們將創建一個文件來管理會話邏輯,包括創建、驗證、更新和刪除會話的輔助函數。

import { serialize, parse } from "cookie";
import jwt from "jsonwebtoken";

const secret = process.env.JWT_SECRET;

export function createSession(user) {
  const token = jwt.sign({ id: user.id }, secret, { expiresIn: "1h" });

  const cookie = serialize("session", token, {
    httpOnly: true,
    maxAge: 3600,
    path: "/",
  });

  return cookie;
}

export function verifySession(cookie) {
  try {
    const decoded = jwt.verify(cookie, secret);
    return decoded;
  } catch (err) {
    return null;
  }
}

中間件和授權

在Next.js中,我們可以使用Middleware來處理一些授權邏輯。例如,我們可以檢查當前路由是否受保護,如果是,我們會從Cookie中獲取會話信息,解密後檢查用戶是否有有效的會話,如果沒有,則重定向到登錄頁面。

import { NextResponse } from "next/server";
import { verifySession } from "./session";

export function middleware(req) {
  const cookie = req.cookies.get("session");

  if (!cookie || !verifySession(cookie)) {
    return NextResponse.redirect("/login");
  }

  return NextResponse.next();
}

Middleware在每個路由上運行,因此它是執行樂觀驗證檢查的好地方。但是,應該避免在中間件中進行數據庫查詢,因為這會阻塞渲染。

資料存取層(Data Access Layer)

為了提高安全性,最好將授權邏輯放在靠近數據獲取的位置。這樣可以確保即使中間件文件被刪除,資料存取層中的驗證邏輯仍然會檢查用戶是否有權限訪問數據。

import { prisma } from "../lib/prisma";
import { verifySession } from "./session";

export async function getUser(cookie) {
  const session = verifySession(cookie);

  if (!session) {
    throw new Error("無效的會話");
  }

  const user = await prisma.user.findUnique({
    where: { id: session.id },
  });

  return user;
}

API 最小化原則

API 最小化原則是一個良好的安全性原則,它意味著只返回應用程序需要的特定數據。如果我們的應用程序只需要用戶的名稱和電子郵件,那麼就只應該返回這些字段。

export async function getUser(cookie) {
  // 前面的驗證代碼...

  const user = await prisma.user.findUnique({
    where: { id: session.id },
    select: { name: true, email: true },
  });

  return user;
}

總結

Next.js的認證機制包括了多個重要的部分:從用戶註冊、會話管理、到授權檢查和資料存取層,每一步都需要仔細設計和實施。我們介紹了如何使用中間件進行樂觀驗證,如何在資料存取層進行權限檢查,以及API最小化原則以提高安全性。

希望這篇文章能幫助你更好地理解和實現Next.js中的認證機制。如果你想了解更多,請參考Next.js的官方文檔,並在GitHub上查看完整的示例。

Next.js認證機制相關的Mermaid圖表。

用戶註冊流程圖

graph TD
  A[用戶提交註冊表單] --> B[Server Action]
  B --> C[表單驗證]
  C -->|驗證成功| D[哈希密碼]
  D --> E[插入用戶到數據庫]
  E --> F[創建會話]
  F --> G[設置Cookie]
  G --> H[重定向到用戶儀表板]
  C -->|驗證失敗| I[返回錯誤信息]

認證中間件流程圖

graph TD
  A[用戶請求保護路由] --> B[Middleware]
  B --> C[檢查Cookie]
  C -->|有有效會話| D[允許訪問]
  C -->|無有效會話| E[重定向到登錄頁面]

資料存取層認證流程圖

graph TD
  A[資料請求] --> B[資料存取層]
  B --> C[驗證會話]
  C -->|會話有效| D[從數據庫獲取數據]
  C -->|會話無效| E[返回錯誤]
  D --> F[返回數據給客戶端]

這些圖表展示了Next.js認證機制中的三個關鍵流程:用戶註冊、認證中間件及資料存取層中的認證檢查。這將有助於您更好地理解每個步驟的詳細過程和相互關係。