【Supabase 教學】#02 Supabase Auth 身份驗證完整指南

測驗:Supabase Auth 身份驗證完整指南

共 5 題,點選答案後會立即顯示結果

1. Supabase Auth 是基於哪個開源專案建構的?

  • A. Firebase Auth
  • B. GoTrue
  • C. Auth0
  • D. Passport.js

2. 在 Supabase Auth 的 Session 結構中,access_token 的預設有效期是多久?

  • A. 15 分鐘
  • B. 1 小時
  • C. 24 小時
  • D. 7 天

3. 使用 supabase.auth.signUp() 註冊新使用者後,Supabase 預設會做什麼?

  • A. 立即建立完整 session 讓使用者登入
  • B. 只建立使用者記錄,不發送任何郵件
  • C. 在 auth.users 建立記錄並發送驗證郵件
  • D. 要求使用者設定雙因素驗證

4. 在 onAuthStateChange 監聽器中,當 Token 自動刷新時會觸發哪個事件?

  • A. SIGNED_IN
  • B. USER_UPDATED
  • C. SESSION_RENEWED
  • D. TOKEN_REFRESHED

5. 設定 OAuth 登入時,在 Supabase Dashboard 設定的授權重新導向 URI 格式應該是什麼?

  • A. https://your-project.supabase.co/auth/v1/callback
  • B. https://your-project.supabase.co/oauth/redirect
  • C. https://localhost:3000/auth/callback
  • D. https://supabase.com/auth/{project-id}/callback

前言

在上一篇文章中,我們認識了 Supabase 的基本架構與專案設定。本篇將深入探討 Supabase Auth,這是 Supabase 提供的身份驗證服務,讓你能快速為應用程式加入使用者管理功能。

讀完本篇後,你將能夠:

  • 理解 Supabase Auth 的架構設計
  • 實作 Email/Password 註冊登入
  • 設定 OAuth 社群登入(Google, GitHub)
  • 處理使用者 session 管理

Supabase Auth 架構介紹

Supabase Auth 基於 GoTrue 開源專案建構,提供完整的身份驗證解決方案。讓我們先看看它的核心架構:

┌─────────────────────────────────────────────────────┐
│                    你的應用程式                       │
│                                                     │
│  ┌─────────────┐    ┌─────────────┐                │
│  │  登入表單   │    │  註冊表單   │                │
│  └──────┬──────┘    └──────┬──────┘                │
│         │                  │                        │
│         └────────┬─────────┘                        │
│                  ▼                                  │
│         ┌───────────────┐                          │
│         │ Supabase SDK  │                          │
│         └───────┬───────┘                          │
└─────────────────┼───────────────────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────────────────────┐
│              Supabase Auth (GoTrue)                 │
│                                                     │
│  ┌─────────────────────────────────────────────┐   │
│  │              認證方法                        │   │
│  │  ┌─────────┐ ┌─────────┐ ┌─────────────┐   │   │
│  │  │ Email/  │ │  OAuth  │ │ Magic Link  │   │   │
│  │  │Password │ │ Provider│ │   / OTP     │   │   │
│  │  └─────────┘ └─────────┘ └─────────────┘   │   │
│  └─────────────────────────────────────────────┘   │
│                                                     │
│  ┌─────────────────────────────────────────────┐   │
│  │        Session / Token 管理                  │   │
│  │  • Access Token (JWT)                       │   │
│  │  • Refresh Token                            │   │
│  │  • 自動刷新機制                              │   │
│  └─────────────────────────────────────────────┘   │
│                                                     │
│  ┌─────────────────────────────────────────────┐   │
│  │         auth.users 資料表                    │   │
│  │  • id (UUID)                                │   │
│  │  • email                                    │   │
│  │  • created_at                               │   │
│  │  • user_metadata                            │   │
│  └─────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────┘

關鍵概念

當你在專案中使用 Supabase Auth 時,會遇到以下幾個核心元件:

1. auth.users 資料表

這是 Supabase 自動建立的系統資料表,儲存所有使用者資訊:

-- 這是 Supabase 自動管理的,你不需要手動建立
-- 但了解其結構有助於理解 Auth 運作方式

-- auth.users 主要欄位
id              -- UUID,使用者唯一識別碼
email           -- 電子郵件
encrypted_password  -- 加密後的密碼
email_confirmed_at  -- 郵件驗證時間
created_at      -- 建立時間
updated_at      -- 更新時間
raw_user_meta_data  -- 自訂使用者資料(JSONCode language: JavaScript (javascript)

2. Session 與 Token

Supabase Auth 使用 JWT (JSON Web Token) 管理使用者 session:

// 登入成功後,你會得到這樣的 session 物件
{
  access_token: "eyJhbG...",     // JWT,有效期較短(預設 1 小時)
  refresh_token: "v1.Mxyz...",  // 用於刷新 access_token
  expires_in: 3600,             // access_token 有效秒數
  token_type: "bearer",
  user: {
    id: "123e4567-e89b...",
    email: "[email protected]",
    // ...其他使用者資訊
  }
}
Code language: JavaScript (javascript)

Email/Password 驗證流程

最基本的身份驗證方式就是 Email/Password。讓我們看看完整的實作流程。

註冊新使用者

import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  'https://your-project.supabase.co',
  'your-anon-key'
)

// 註冊新使用者
async function signUp(email, password) {
  const { data, error } = await supabase.auth.signUp({
    email: email,
    password: password,
    options: {
      // 可選:附加使用者資料
      data: {
        display_name: 'John Doe',
        avatar_url: 'https://example.com/avatar.png'
      }
    }
  })

  if (error) {
    console.error('註冊失敗:', error.message)
    return null
  }

  console.log('註冊成功,請檢查郵件確認信箱')
  return data.user
}
Code language: JavaScript (javascript)

當你呼叫 signUp() 後,Supabase 會:

  1. auth.users 建立一筆新記錄
  2. 發送驗證郵件到使用者信箱(預設行為)
  3. 回傳 user 物件(但 session 可能為 null,視設定而定)

登入

// Email/Password 登入
async function signIn(email, password) {
  const { data, error } = await supabase.auth.signInWithPassword({
    email: email,
    password: password
  })

  if (error) {
    console.error('登入失敗:', error.message)
    return null
  }

  // 登入成功,data 包含 user 和 session
  console.log('登入成功')
  console.log('使用者:', data.user.email)
  console.log('Session 有效至:', new Date(data.session.expires_at * 1000))

  return data
}
Code language: JavaScript (javascript)

取得當前使用者

// 取得當前登入的使用者
async function getCurrentUser() {
  const { data: { user }, error } = await supabase.auth.getUser()

  if (error || !user) {
    console.log('使用者未登入')
    return null
  }

  return user
}

// 取得當前 session
async function getSession() {
  const { data: { session }, error } = await supabase.auth.getSession()

  if (error || !session) {
    console.log('沒有有效的 session')
    return null
  }

  return session
}
Code language: JavaScript (javascript)

設定 OAuth Provider

OAuth 讓使用者能用現有的 Google、GitHub 等帳號登入,不需要另外記密碼。以下以 Google OAuth 為例說明設定流程。

步驟一:在 Google Cloud Console 建立 OAuth 憑證

  1. 前往 Google Cloud Console
  2. 建立或選擇專案
  3. 進入「API 和服務」>「憑證」
  4. 點擊「建立憑證」>「OAuth 用戶端 ID」
  5. 應用程式類型選擇「網頁應用程式」
  6. 設定授權重新導向 URI:
  7. 記下 Client ID 和 Client Secret

步驟二:在 Supabase Dashboard 設定

  1. 進入 Supabase Dashboard > Authentication > Providers
  2. 找到 Google,點擊啟用
  3. 填入剛才取得的 Client ID 和 Client Secret
  4. 儲存設定

步驟三:實作 OAuth 登入

// Google OAuth 登入
async function signInWithGoogle() {
  const { data, error } = await supabase.auth.signInWithOAuth({
    provider: 'google',
    options: {
      // 登入成功後重導向的 URL
      redirectTo: 'http://localhost:3000/auth/callback',
      // 可選:要求的權限範圍
      scopes: 'email profile'
    }
  })

  if (error) {
    console.error('Google 登入失敗:', error.message)
    return
  }

  // OAuth 會重導向到 Google 登入頁面
  // 成功後會導回 redirectTo 指定的 URL
}

// GitHub OAuth 登入(設定方式類似)
async function signInWithGitHub() {
  const { data, error } = await supabase.auth.signInWithOAuth({
    provider: 'github',
    options: {
      redirectTo: 'http://localhost:3000/auth/callback'
    }
  })

  if (error) {
    console.error('GitHub 登入失敗:', error.message)
  }
}
Code language: JavaScript (javascript)

OAuth 回調處理

當使用者從 OAuth Provider 登入成功後,會被重導向回你的應用程式。你需要處理這個回調:

// 在你的 callback 頁面(例如 /auth/callback)
async function handleAuthCallback() {
  // Supabase SDK 會自動從 URL 解析並設定 session
  const { data: { session }, error } = await supabase.auth.getSession()

  if (error) {
    console.error('驗證回調錯誤:', error.message)
    // 重導向到登入頁面
    window.location.href = '/login?error=auth_failed'
    return
  }

  if (session) {
    console.log('OAuth 登入成功')
    // 重導向到主頁面
    window.location.href = '/dashboard'
  }
}
Code language: JavaScript (javascript)

Session 管理與 Token 處理

理解 session 管理是使用 Supabase Auth 的關鍵。

Session 生命週期

使用者登入
    │
    ▼
┌─────────────────────────────────────────────────┐
│  發放 Session                                   │
│  • access_token (有效期 1 小時)                 │
│  • refresh_token (有效期較長)                   │
└───────────────────────┬─────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────┐
│  正常使用期間                                   │
│  • 每個 API 請求帶上 access_token              │
│  • SDK 自動處理 token 附加                     │
└───────────────────────┬─────────────────────────┘
                        │
          access_token 即將過期
                        │
                        ▼
┌─────────────────────────────────────────────────┐
│  自動刷新 (SDK 自動處理)                        │
│  • 使用 refresh_token 取得新的 access_token    │
│  • 對使用者透明,無感知                        │
└───────────────────────┬─────────────────────────┘
                        │
          refresh_token 過期 或 使用者登出
                        │
                        ▼
┌─────────────────────────────────────────────────┐
│  Session 結束                                   │
│  • 需要重新登入                                │
└─────────────────────────────────────────────────┘

手動刷新 Session

雖然 SDK 會自動刷新,但有時你可能需要手動處理:

// 手動刷新 session
async function refreshSession() {
  const { data: { session }, error } = await supabase.auth.refreshSession()

  if (error) {
    console.error('Session 刷新失敗:', error.message)
    // 可能需要重新登入
    return null
  }

  console.log('Session 已刷新')
  return session
}
Code language: JavaScript (javascript)

監聽認證狀態變化 (onAuthStateChange)

onAuthStateChange 是處理認證狀態的核心方法。它讓你能即時反應使用者的登入/登出狀態:

// 設定認證狀態監聽器
function setupAuthListener() {
  const { data: { subscription } } = supabase.auth.onAuthStateChange(
    (event, session) => {
      console.log('認證狀態變化:', event)

      switch (event) {
        case 'INITIAL_SESSION':
          // 初始化時檢查現有 session
          if (session) {
            console.log('發現現有 session')
          } else {
            console.log('沒有現有 session')
          }
          break

        case 'SIGNED_IN':
          // 使用者登入
          console.log('使用者已登入:', session.user.email)
          // 更新 UI、導航到主頁面等
          break

        case 'SIGNED_OUT':
          // 使用者登出
          console.log('使用者已登出')
          // 清除本地狀態、導航到登入頁面等
          break

        case 'TOKEN_REFRESHED':
          // Token 已刷新
          console.log('Token 已自動刷新')
          break

        case 'PASSWORD_RECOVERY':
          // 密碼重設流程
          console.log('密碼重設流程中')
          // 顯示密碼重設表單
          break

        case 'USER_UPDATED':
          // 使用者資料已更新
          console.log('使用者資料已更新')
          break
      }
    }
  )

  // 記得在元件卸載時取消訂閱
  // subscription.unsubscribe()

  return subscription
}
Code language: JavaScript (javascript)

在 React 中使用

import { useEffect, useState } from 'react'

function App() {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    // 取得初始 session
    supabase.auth.getSession().then(({ data: { session } }) => {
      setUser(session?.user ?? null)
      setLoading(false)
    })

    // 監聽狀態變化
    const { data: { subscription } } = supabase.auth.onAuthStateChange(
      (event, session) => {
        setUser(session?.user ?? null)
      }
    )

    // 清理
    return () => subscription.unsubscribe()
  }, [])

  if (loading) {
    return <div>載入中...</div>
  }

  return user ? <Dashboard user={user} /> : <LoginPage />
}
Code language: JavaScript (javascript)

登出與密碼重設功能

登出

// 登出
async function signOut() {
  const { error } = await supabase.auth.signOut()

  if (error) {
    console.error('登出失敗:', error.message)
    return false
  }

  console.log('已登出')
  return true
}

// 在所有裝置上登出(撤銷所有 session)
async function signOutEverywhere() {
  const { error } = await supabase.auth.signOut({
    scope: 'global'  // 撤銷所有 session
  })

  if (error) {
    console.error('全域登出失敗:', error.message)
    return false
  }

  return true
}
Code language: JavaScript (javascript)

密碼重設

密碼重設分為兩個步驟:發送重設郵件和設定新密碼。

// 步驟一:發送密碼重設郵件
async function sendPasswordReset(email) {
  const { error } = await supabase.auth.resetPasswordForEmail(email, {
    // 重設連結點擊後導向的 URL
    redirectTo: 'http://localhost:3000/reset-password'
  })

  if (error) {
    console.error('發送重設郵件失敗:', error.message)
    return false
  }

  console.log('密碼重設郵件已發送')
  return true
}

// 步驟二:在重設頁面設定新密碼
// 當使用者點擊郵件中的連結時,會導向你的 reset-password 頁面
// Supabase 會自動處理 URL 中的 token
async function updatePassword(newPassword) {
  const { data, error } = await supabase.auth.updateUser({
    password: newPassword
  })

  if (error) {
    console.error('更新密碼失敗:', error.message)
    return false
  }

  console.log('密碼已更新')
  return true
}
Code language: JavaScript (javascript)

常見錯誤處理

使用 Supabase Auth 時,你會遇到各種錯誤。以下是常見錯誤及處理方式:

// 統一的錯誤處理函式
function handleAuthError(error) {
  // 錯誤物件結構
  // {
  //   message: "錯誤訊息",
  //   status: 400,  // HTTP 狀態碼
  //   name: "AuthApiError"
  // }

  const errorMessages = {
    // 註冊相關
    'User already registered': '此 Email 已被註冊',
    'Password should be at least 6 characters': '密碼至少需要 6 個字元',

    // 登入相關
    'Invalid login credentials': 'Email 或密碼錯誤',
    'Email not confirmed': '請先驗證您的 Email',

    // Session 相關
    'JWT expired': '登入已過期,請重新登入',
    'Invalid Refresh Token': '登入憑證無效,請重新登入',

    // OAuth 相關
    'OAuth callback error': '第三方登入失敗',

    // 通用
    'Rate limit exceeded': '請求太頻繁,請稍後再試'
  }

  // 找到對應的中文訊息,或使用原始訊息
  const friendlyMessage = errorMessages[error.message] || error.message

  console.error(`Auth Error [${error.status}]:`, friendlyMessage)

  return friendlyMessage
}

// 使用範例
async function safeSignIn(email, password) {
  const { data, error } = await supabase.auth.signInWithPassword({
    email,
    password
  })

  if (error) {
    const message = handleAuthError(error)
    // 顯示給使用者
    alert(message)
    return null
  }

  return data
}
Code language: JavaScript (javascript)

常見錯誤情境

錯誤訊息 原因 解決方式
User already registered Email 已被使用 提示使用者登入或重設密碼
Invalid login credentials 帳密錯誤 提示重新輸入
Email not confirmed 未驗證 Email 提示檢查郵件或重發驗證信
JWT expired Token 過期 SDK 通常自動處理,否則重新登入
Rate limit exceeded 請求過於頻繁 加入延遲或防抖機制

小結

本篇涵蓋了 Supabase Auth 的核心功能:

  1. 架構理解:Supabase Auth 基於 GoTrue,使用 JWT 管理 session
  2. Email/Password 驗證:signUp、signIn、getUser 等基本操作
  3. OAuth 設定:Google、GitHub 等第三方登入的設定與實作
  4. Session 管理:Token 生命週期與自動刷新機制
  5. 狀態監聽:onAuthStateChange 即時追蹤認證狀態
  6. 輔助功能:登出與密碼重設
  7. 錯誤處理:常見錯誤的識別與友善提示

下一篇我們將深入 Supabase Database,學習如何使用 PostgreSQL 資料庫並結合 Row Level Security (RLS) 保護資料安全。


延伸閱讀

進階測驗:Supabase Auth 身份驗證完整指南

測驗目標:驗證你是否能在實際情境中應用所學。
共 5 題,包含情境題與錯誤診斷題。

1. 你正在開發一個 React 應用程式,需要在使用者登入狀態改變時更新 UI。你希望在元件載入時取得現有 session,並持續監聽認證狀態變化。最佳的實作方式是? 情境題

  • A. 只使用 getSession() 在元件載入時檢查一次
  • B. 設定 setInterval 每秒呼叫 getUser() 檢查狀態
  • C. 在 useEffect 中先呼叫 getSession(),再設定 onAuthStateChange 監聽器,並在清理函式中 unsubscribe
  • D. 將 session 存在 localStorage 並自己管理狀態

2. 你的應用程式需要支援 Google OAuth 登入。你已經在 Google Cloud Console 建立了 OAuth 憑證,接下來在 Supabase Dashboard 設定完成後,前端應該如何呼叫登入? 情境題

// 使用者點擊「使用 Google 登入」按鈕後
  • A. supabase.auth.signInWithPassword({ provider: 'google' })
  • B. supabase.auth.signInWithOAuth({ provider: 'google', options: { redirectTo: 'http://localhost:3000/auth/callback' } })
  • C. supabase.auth.signUp({ provider: 'google' })
  • D. supabase.auth.createOAuthSession('google')

3. 小華在實作登入功能時遇到錯誤,以下是他的程式碼和錯誤訊息: 錯誤診斷

const { data, error } = await supabase.auth.signInWithPassword({ email: ‘[email protected]’, password: ‘12345’ }) // 錯誤訊息:Password should be at least 6 characters

這個錯誤最可能發生在什麼情況?

  • A. 使用者在註冊時設定的密碼不符合最低 6 字元要求
  • B. 登入時輸入的密碼太短,被前端驗證擋下
  • C. Supabase 伺服器連線逾時
  • D. 使用者的 Email 尚未驗證

4. 你需要實作「在所有裝置上登出」的功能,讓使用者能夠撤銷所有現有的 session。應該如何呼叫 signOut? 情境題

  • A. supabase.auth.signOut({ all: true })
  • B. supabase.auth.signOutAll()
  • C. supabase.auth.revokeAllSessions()
  • D. supabase.auth.signOut({ scope: 'global' })

5. 小明的應用程式在使用者閒置一段時間後,API 請求開始失敗並顯示以下錯誤。根據這個錯誤,最適當的處理方式是什麼? 錯誤診斷

AuthApiError: JWT expired status: 401
  • A. 要求使用者清除瀏覽器快取後重新整理頁面
  • B. 檢查 SDK 是否正確設定,因為 Supabase SDK 應該會自動刷新 Token;若無法自動刷新則導向使用者重新登入
  • C. 將 access_token 的有效期設定延長到 24 小時
  • D. 這是 Supabase 伺服器的 Bug,應該提交 Issue

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *