別再自己從頭刻 Login 系統了!

如果你曾經在全端專案裡自己用 Node.js 寫過會員註冊登入系統,你一定知道那有多痛苦。 你需要把密碼加上鹽巴 (Salt) 用 bcrypt 雜湊、發送 JWT Token、處理 Token 過期與 Refresh Token、實作「忘記密碼」發送信件的邏輯... 更別提還有煩死人的 OAuth (Google/GitHub 登入)。 而且一旦你寫錯一個環節,整個系統的會員資料就有可能被駭客整包端走。

在 2026 年的現代全端開發中,專業的工程師不會去重複造輪子。我們會使用 BASS (Backend as a Service) 服務。而 Supabase 就是我們 Vibe Tutor 萬中選一的超級大腦。


為什麼選擇 Supabase?

很多人會拿 Firebase 來比較。Firebase 雖然好用,但它底層是 NoSQL 資料庫,對於我們這種需要嚴謹關聯「會員」與「多筆訂單」的電商/知識付費系統來說,關聯式資料庫 (RDBMS) 絕對是更安全的選擇。 Supabase 底層是地球上最強大的開源關聯式資料庫 PostgreSQL。它不僅提供了極速的 API,更內建了一套完美無缺的 Auth (身份驗證) 系統。

在 Next.js 15 中實作 Supabase Auth

在 Next.js 的 App Router 架構下,因為環境被切分為 Server 端與 Client 端,所以我們必須準備兩種 Supabase Client 來應對。 這一切都已經在我們專案的 src/utils/supabase/ 資料夾中設定好了。

1. client.ts (前端瀏覽器專用)

這主要用在有 "use client" 的元件中,例如你的登入表單元件。

import { createBrowserClient } from '@supabase/ssr'

export function createClient() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  )
}

2. server.ts (後端伺服器專用)

這用在 Server Components 或是 API Routes 中,用來在後端驗證使用者的身份。它需要處理 Cookie 的讀取與寫入。

import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'

export async function createClient() {
  const cookieStore = await cookies()
  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return cookieStore.getAll()
        },
        setAll(cookiesToSet) {
          try {
            cookiesToSet.forEach(({ name, value, options }) => {
              cookieStore.set(name, value, options)
            })
          } catch (error) {
            // 在 Server Component 中,有時候不能直接設 cookie,這裡捕捉例外
          }
        },
      },
    }
  )
}

Email 魔法連結 (Magic Link) 登入實戰

在 Vibe Tutor 的登入頁面 (src/app/login/page.tsx) 中,我們採用了目前最流行、無摩擦的 Magic Link 登入方式。 使用者不需要設定密碼,也不需要記住密碼。只要輸入 Email,系統就會寄一封帶有安全連結的信件給他,點擊即可瞬間登入。

看看這段處理登入的核心邏輯:

"use client";
import { createClient } from "@/utils/supabase/client";

const handleLogin = async (e) => {
  e.preventDefault();
  
  const supabase = createClient();
  
  // 呼叫 Supabase 的 signInWithOtp 方法發送魔法連結
  const { error } = await supabase.auth.signInWithOtp({
    email,
    options: {
      // 登入成功後,重新導向到會員中心
      emailRedirectTo: `${location.origin}/auth/callback?next=/dashboard`,
    },
  });

  if (error) {
    alert("寄送信件失敗,請稍後再試。");
  } else {
    alert("魔法登入連結已寄送至您的信箱!請前往收信。");
  }
};

就這麼簡單!我們完全不需要去處理寄送 Email 的 SMTP 伺服器設定,Supabase 內部全部幫你打理好了。

Auth Callback 處理機制

當使用者在信箱中點擊了魔法連結,這是一個長得像 https://你的網站.com/auth/callback?code=xxx 的網址。 我們必須在 src/app/auth/callback/route.ts 這個 API Route 攔截它,並將 URL 上的 code 換成真實的登入憑證 (Session Cookie)。

import { NextResponse } from 'next/server'
import { createClient } from '@/utils/supabase/server'

export async function GET(request: Request) {
  const { searchParams, origin } = new URL(request.url)
  const code = searchParams.get('code')
  const next = searchParams.get('next') ?? '/dashboard' // 預設跳轉到會員中心

  if (code) {
    const supabase = await createClient()
    
    // 將 code 交換成使用者的登入 Session
    const { error } = await supabase.auth.exchangeCodeForSession(code)
    
    if (!error) {
      return NextResponse.redirect(`${origin}${next}`)
    }
  }

  // 驗證失敗的處理
  return NextResponse.redirect(`${origin}/login?error=InvalidToken`)
}

這個交換過程完成後,瀏覽器中就會寫入具有 HttpOnly 屬性的安全 Cookie。 從此以後,當使用者瀏覽任何一頁,我們都能在 Server 端透過 await supabase.auth.getUser() 瞬間認出他是誰,並讀取他的訂單權限。

掌握了強大且優雅的 Supabase 身份驗證,接下來,我們將進入系統的核心心臟:PostgreSQL 資料庫設計,看看我們是如何規劃那張神秘的 vt_purchases 訂單資料表的。

解鎖完整教學內容

本章為付費內容。加入專案即可解鎖超過 5000 字的深度解析,包含 10 個以上神級 Prompt 與真實 Source Code 範例!