在開發 SaaS 或工具型應用時,「使用者驗證系統」往往是關鍵基礎設施。這篇文章將分享我如何使用 Next.js(App Router)+ MongoDB + NextAuth,同時支援 Google OAuth 和 自定義帳密登入,並分享專案 mongoDB 資料結構與 API 架構。
MongoDB 資料模型設計
為了支援多元登入方式,我設計了以下幾個集合(Collections):
- users - 使用者基本資訊
 
1 2 3 4 5 6 7 8
   | {   _id: ObjectId,   email: String,   name: String,   createdAt: Date,   updatedAt: Date }
 
  | 
 
- authProviders - 自定義驗證方式(帳密)
 
1 2 3 4 5 6 7
   | {   userId: ObjectId,    type: "credentials",   passwordHash: String,    createdAt: Date,   updatedAt: Date }
  | 
 
- folders - 使用者資料夾
 
1 2 3 4 5 6 7 8 9
   | {   _id: ObjectId,   userId: ObjectId,   name: String,   description: String,   createdAt: Date,   updatedAt: Date }
 
  | 
 
- snippets 
 
1 2 3 4 5 6 7 8 9 10 11
   | {   _id: ObjectId,   userId: ObjectId,   folderId: ObjectId,   name: String,   content: String,   shortcut: String,   createdAt: Date,   updatedAt: Date }
 
  | 
 
NextAuth 與驗證流程設計
在這個專案中,我將登入驗證分為兩條路徑:
| 驗證方式 | 
實作方式 | 
資料表使用 | 
| Google 登入 | 
NextAuth OAuth Provider | 
accounts (NextAuth 自動建立) | 
| 帳號密碼 | 
自定義 API + bcrypt 驗證 | 
authProviders | 
NextAuth
使用 NextAuth 搭配 Google 登入時,NextAuth 會自動在資料庫中建立並管理一個 accounts collection(資料表)
這是 NextAuth 的內建行為,用來儲存與第三方驗證(如 Google、GitHub 等)相關的資訊。
下面詳細解說 NextAuth 如何使用 MongoDB 中的 accounts collection:
NextAuth 自動建立的 Collections
當使用 NextAuth 與 MongoDB 整合時,NextAuth 會建立 accounts:儲存第三方驗證資訊
accounts Collection 的結構
當使用者透過 Google 登入時,NextAuth 會在 accounts collection 中建立一筆資料,結構如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | {   "_id": "ObjectId",   "userId": "ObjectId",    "provider": "google",    "providerAccountId": "google-account-id",    "type": "oauth",    "access_token": "google-access-token",   "id_token": "google-id-token",   "refresh_token": "google-refresh-token",   "expires_at": 1683500000,    "scope": "email profile",   "token_type": "Bearer",   "session_state": "session-state" }
  | 
 
與自定義 authProviders 的區別
NextAuth 自動建立的 accounts collection 與自定義的 authProviders collection 有以下區別:
accounts:由 NextAuth 自動管理,專門用於第三方驗證(如 Google、GitHub 等)。 
authProviders:由你自定義,專門用於自己實現的驗證方式(如帳號密碼)。 
在你目前的登入 API 執行流程中(如 route.ts 檔案所示),你使用 authProviders collection 來儲存和驗證使用者的密碼:
1 2 3 4 5 6 7 8 9 10
   |  const authProvider = await db   .collection("authProviders")   .findOne({ userId: user._id, type: "credentials" }); if (!authProvider || !authProvider.passwordHash) {   return NextResponse.json(     { message: "invalid credentials" },     { status: 401 }   ); }
 
  | 
 
NextAuth 整合設計
 使用 MongoDB Adapter
1 2 3 4 5 6 7 8 9 10
   | 
  import { MongoDBAdapter } from "@auth/mongodb-adapter";
  const handler = NextAuth({   adapter: MongoDBAdapter(clientPromise, {     databaseName: process.env.MONGODB_DB || "snippets-auth",   }),   ... });
 
  | 
 
支援多種 Provider
1 2 3 4 5 6 7 8 9 10 11 12 13
   |  providers: [   CredentialsProvider({     name: "Credentials",     authorize: async (credentials) => {            },   }),   GoogleProvider({     clientId: process.env.GOOGLE_CLIENT_ID!,     clientSecret: process.env.GOOGLE_CLIENT_SECRET!,   }), ]
 
  | 
 
自定義 JWT 與 Session 回傳資料
為了讓 API 能拿到使用者資訊,我自定義了 jwt() 與 session() callback:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
   | 
  async jwt({ token, user, account }) {   if (account?.provider === 'google' && user) {     token.id = user.id;     token.accessToken = account.access_token;   }   if (account?.provider === 'credentials' && user) {     token.id = user.id;     token.email = user.email;     token.token = user.token;   }   return token; }
 
  async session({ session, token }) {   session.user = {     id: token.id,     email: token.email,     token: token.token,     accessToken: token.accessToken,     image: typeof token.image === 'string' ? token.image : undefined   };   return session; }
 
  | 
 
這讓我們在前端取得使用者資訊,或是讓 Middleware 驗證身份更方便。
API 設計架構
所有 API 採用 RESTful 命名方式:
** 認證 API **
POST /api/v1/auth/signup – 註冊帳號
POST /api/v1/auth/login – 登入取得權杖
** 資料管理 API  **
GET/POST /api/v1/folders
GET/PUT/DELETE /api/v1/folders/[id]
GET/POST /api/v1/snippets
GET/PUT/DELETE /api/v1/snippets/[id]
** 健康檢查 **
GET /api/health – MongoDB 狀態回報
API Middleware 權限驗證
使用 next-auth/jwt 驗證 JWT:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   |  export async function middleware(request: NextRequest) {      const publicPaths = ["/api/v1/auth/login", "/api/v1/auth/signup", "/api/health"];   if (publicPaths.includes(path)) {     return NextResponse.next();   }
       const token = await getToken({ req: request, secret: process.env.NEXTAUTH_SECRET });   if (!token) {     return NextResponse.json({ message: "Unauthorized" }, { status: 401 });   }
       const res = NextResponse.next();   res.headers.set("x-user-id", token.id as string);   return res; }
 
  | 
 
 確保所有保護的 API 都檢查 x-user-id,實現使用者權限控管。
實作涵蓋了完整驗證設計:
- Google OAuth 登入(透過 NextAuth 自動化)
 
- 自定義帳密登入(使用 bcrypt + MongoDB)
 
- 多種登入方式共用同一個 users 集合
 
- API 權限驗證 + Middleware 控制
 
- 使用 MongoDB Adapter 整合資料庫與 session 管理