使用者驗證

  • 使用 Supabase 身份驗證與 Google 登入功能建立登入與註冊流程,以控制應用程式的存取權限
  • 在您的 Express.js 後端新增驗證中介軟體,以保護 API 路由免於未經授權的請求
  • 測試前端與後端安全性,以驗證僅登入用戶能觸發您的AI功能
  • 註冊與登入頁面,用於建立使用者帳戶並維持使用者登入狀態
  • 受保護的 API 路由,配備 後端中介軟體,在處理請求前會驗證身份驗證令牌

學生們已在 GitHub 儲存庫中建置出一個經過除錯的最小可行產品(MVP),該產品包含 React 前端與 Express.js 後端。前端與後端可在 Codespaces 中作為兩個獨立的程序執行。

透過驗證保護您的應用程式

目前,任何持有您的 Codespace 連結者皆可使用您的應用程式。系統未設置登入畫面,且您的 Express.js 後端會無條件處理所有接收到的請求。若有人發現您的網址,便能觸發耗費您金錢的 AI 調用。正如您在制定財務計畫時所探討的,每次 API 調用皆伴隨成本。保護這些調用不僅是技術決策,更是商業決策。

在本單元中,我們將以TutorMatch作為引導範例。您將先參照 TutorMatch 的範例,再將每個步驟調整應用於您自己的最小可行產品(MVP)。

本裝置增加了兩層防護。

  1. 使用者驗證——一個登入畫面,確保僅有註冊使用者能查看您的應用程式。 
  2. 安全檢查 在您的後端系統中,確保每項 API 請求確實來自已登入的使用者。

試想:登入頁面如同守護前門,後端安全檢查則如同守護窗戶。兩者相輔相成,意味著您的應用程式獲得了雙重防護。

為何不自己儲存密碼?

最直觀的用戶帳戶管理方式也是最危險的:建立一個用戶資料表,在用戶註冊時儲存其電子郵件和密碼,並在用戶登入時驗證密碼。

問題在於,此方法將密碼以純文字形式儲存,任何存取資料庫的人都能讀取。一旦資料庫遭外洩,所有使用者的密碼都將暴露無遺。專業級應用程式絕不會採用這種做法。

 安全的驗證需要多項要素協同運作:

  • 密碼雜湊化 – 將密碼轉換為不可讀取的字串後再進行儲存
  • 會話代幣 – 臨時識別碼,讓使用者無需在每個頁面輸入密碼即可保持登入狀態
  • 安全儲存 –憑證採用加密處理,確保其在傳輸中或靜止狀態下絕不外洩。

從頭開始建構所有這些內容既複雜又容易出錯。正因如此,驗證服務應運而生——它們負責處理安全性,讓您能專注於應用程式開發。

user authentication login screen

選擇用戶認證服務時,有以下幾種選項:

選項

運作原理

免費方案

複雜性

判決

自行儲存密碼

建立使用者資料表、對密碼進行雜湊處理、手動管理工作階段

自由的

高——安全失誤既容易發生又危險

不建議用於MVP

Google 登入 (OAuth)

使用者點擊「使用 Google 登入」。Google 負責處理所有安全事宜。

自由的

中階 — 需設定 Google Cloud Console

絕佳選擇,但需增加設定步驟

Auth0 / 職員

具備預建登入頁面的第三方認證服務

免費方案可用

Medium — 需新帳號及 SDK

不錯,但又增加了一項需要管理的服務

Supabase 身份驗證

內建於 Supabase。自動處理雜湊、會話及儲存功能。

免費(已包含)

低 — 您已擁有一個 Supabase 帳戶

MVP 的絕佳選擇

本課程將採用 Supabase Auth 進行用戶驗證。我們還會添加 Google 登入功能,因其是專業應用程式的標準登入選項,且設定難度不高。選擇 Supabase Auth 作為主要方案基於以下三點考量:

  1. 您已透過創辦人工具包取得 Supabase 帳戶。
  2. Supabase 開箱即用,即可處理上述所有複雜的安全性問題。
  3. 免費方案涵蓋您打造最小可行產品所需的一切功能。

還有一點需要考慮:Supabase Auth支援行級安全性(RLS), 這是一種可透過設定資料庫 規則來過濾資料的功能,確保每位使用者僅能查看自身記錄。我們在將 Supabase 加入創辦人工具包時曾簡要提及此功能。然而,RLS 無法保護您的 AI 路由,因此我們需要追加額外的防護措施。

目前階段無需使用 RLS,因為我們並未建立任何自訂資料表。Supabase 會內部處理使用者儲存機制。若您在 MVP 中儲存使用者專屬資料(例如保存使用者的歷史紀錄、偏好設定或交易紀錄),此時 RLS 才顯得重要。

Supabase 驗證

當您建立 Supabase 專案時,驗證功能會自動啟用。您無需在 Supabase 設定中進行任何特殊配置。我們已在創始人工具包中針對匿名使用者與團隊成員設定了特定的 RLS 安全策略,但此資料庫將採用 Supabase 預設的內建驗證機制。

Supabase 自動配置的項目:

  •  電子郵件/密碼驗證功能已啟用
  • 使用者資料表 (auth.users) 已建立
  • 已發行的 JSON 網路憑證 (JWT)
  • 會話管理已處理

在第一個活動中,您將為最小可行產品(MVP)添加登入與註冊流程,確保僅限註冊用戶能使用您的應用程式。我們將逐步完成一系列提示操作,每次完成後都會進行測試,確認功能正常運作後再繼續推進。 

本課活動中的每個提示皆採用 角色 + 任務 結構。這是值得練習的提示工程模式:

  • 角色 告訴人工智慧該成為哪種專家
  • 任務 精確地告訴它該做什麼。

將這兩者分開能使您的提示更清晰,並產生更佳的結果,尤其在使用較小的AI模型時。

活動 1

使用 Supabase 添加使用者驗證

預估時間:30分鐘

您將更新應用程式,要求使用者登入後方可使用。新用戶可註冊帳號,回訪用戶可登入,而未持有帳號者將被導向登入頁面。

  1. 前往 GitHub 並開啟您的 MVP 儲存庫。 
  2. 建立一個名為add-auth 的新分支。 
  3. 在新的分支上開啟您的 Codespace。

您即將進行重大變更——功能分支確保主分支安全無虞。僅在完成測試後,才會將變更合併至主分支。

  1. 前往 Supabase。您應已透過創辦人工具包取得帳戶與專案。
  2. 建立一個新專案。為您的最小可行產品(MVP)取一個合適的名稱。
  3. 您需要建立一個資料庫密碼。請建立密碼並將其存放在安全處,因為一旦儲存後便無法再查看該密碼。
  4. 設定驗證設定:
    1. 前往您的Supabase 儀表板 → 驗證 → 設定
    2. 向下滾動至登入/供應商區段
    3. 啟用電子郵件確認功能
    4. 將其關閉(用於測試)
    5. 切記在進入生產環境時重新開啟它!
supabase user authentications screen
  1. 取得您的專案網址與 API 金鑰
    1. 在左側邊欄中,點擊「專案設定」(底部齒輪圖示)
    2. 點擊資料 API。
    3. 您將看到API 網址。請複製並將其儲存至電腦中的某個位置。您的應用程式將需要此網址。
    4. 返回專案設定選單,點擊API 金鑰。
    5. 您將看到兩組金鑰:可發佈金鑰與私密金鑰。
    6. 複製可發佈金鑰。您的應用程式也需要此金鑰。

公開網址與可發佈金鑰皆為開放狀態,可直接用於應用程式的前端。由於 Supabase 採用 RLS(資料列層級安全性)機制,資料受到保護。使用者可註冊及登入,但僅能檢視自身資料,且不具備管理員權限。

我們在此不會使用私密金鑰。該金鑰絕不應在前端使用。我們將在後續處理付款流程時使用它。

  1. 在您的專案中,新增一個 .env 檔案於您的 根目錄。您已存在一個 .env 檔案。 server 目錄中,但由於 Supabase 整合發生於前端,因此該檔案需存在於專案的根目錄中。
  2. 在您的根目錄下新增兩行 .env 檔案中 檔案中新增兩行:
    VITE_SUPABASE_URL=您的公開網址
    VITE_SUPABASE_KEY=您的發佈金鑰
  3. 請將您的實際 Supabase 網址與可發佈金鑰加入檔案中的這些行。

注意:我們使用的是 React,因此需要在前端環境變數前加上 VITE_ 前綴。您的框架可能有所不同。

在您的 Codespace 終端機中輸入:
npm install @supabase/supabase-js

請提示 Copilot(或其他大型語言模型)協助生成您應用程式的登入/註冊代碼。以下為提示範例:

角色:您是一位網頁開發者,協助我在 React 應用程式中實作使用者驗證功能。

任務:新增 Supabase 註冊與登入表單,並與 Supabase 網址進行互動。開啟畫面為登入頁面,使用者登入後將顯示其他標籤頁,並可存取該兩個標籤頁。登入表單應以 Authform.jsx 檔案形式新增至 Pages 資料夾。 當使用者登入後,需在其他標籤頁按鈕旁新增登出按鈕,以便使用者能重新登出並返回主畫面。

請為「授權表單」採用與「尋找導師」及「獲取建議」標籤頁相同的格式設定。 

我已將 Supabase 專案網址與可發佈的 API 金鑰新增至 .env 檔案中,分別設定為 VITE_SUPABASE_URL 和 VITE_SUPABASE_URL。 

測試完整流程:

  1. 註冊:
    • 前往 /signup
    • 輸入電子郵件和密碼(至少6個字元)
    • 點擊「註冊」
    • 應重定向至首頁
  2. 在 Supabase 中驗證:
    • 開啟 Supabase 儀表板 → 驗證 → 使用者
    • 您應該會看到您的新使用者列在其中
  3. 登出:
    • 點擊登出按鈕
    • 應重定向至登入頁面
  4. 登入:
    • 請輸入相同的電子郵件/密碼
    • 應重定向至首頁
  5. 會話持久性:
    • 重新整理頁面
    • 仍應保持登入狀態

若任何步驟失敗,請使用「教我」模式與 Copilot 互動:描述您預期的結果、實際發生的狀況,並要求 Copilot 在提供修正方案前先說明原因。

請保持 Codespace 和分支開啟,以便進行下一項活動。

雖然添加Google登入選項並非強制要求,但您可免費實現此功能,這將為您的應用程式增添專業氣息。

活動 2

(可選)新增 Google 登入功能

預估時間:30分鐘

  1. 前往您的Supabase 儀表板 → 驗證 → 提供者。
  2. 在清單中找到Google並啟用它。 
  3. 尋找回呼網址並複製它。通常格式如下:https://[您的專案ID].supabase.co/auth/v1/callback。

重要提示:請忽略 畫面頂端的大型「免費試用」或「啟用 300 美元信用額度」橫幅廣告。若點擊這些按鈕,Google將要求您提供信用卡進行身分驗證。

您無需擁有 300 美元的信用額度或「帳單帳戶」即可使用身分識別/驗證 API。

  1. 前往Google Cloud 控制台。您 應已透過使用 Google AI Studio 取得帳戶。

  2. 建立一個新專案並為其命名。 Google允許每個帳戶建立多個免費專案,無需付費或使用信用卡。

  3. 前往API 與服務 → OAuth 同意畫面。

    • 點擊「開始使用」。
    • 請填寫您的應用程式名稱及用戶支援電子郵件。
    • 點擊 外部 為觀眾。

      重要提示:僅需提供「應用程式名稱」與「支援電子郵件」。若系統要求您添加標誌或敏感「權限範圍」(例如讀取用戶整個雲端硬碟),Google 可能會要求您使用信用卡進行驗證。

      MVP專業提示:
      只要維持「測試」模式或用戶數量較少,目前無需提交驗證申請。

  1. 前往API 與服務 → 憑證。
  2. 點擊+ 建立憑證 → OAuth 客戶端 ID。
  3. 應用程式類型:請選擇網頁應用程式。
  4. 授權重定向 URI:貼上您從 Supabase 複製的回呼網址。
  5. 點擊「建立」。您將收到客戶端識別碼與客戶端密鑰。
  6. 請務必複製這些內容,因為關閉視窗後將無法再存取它們。您可將其下載為 JSON 格式,如此便能同時保留兩種格式供您使用。
  1. 返回Supabase 儀表板 → 驗證 → 服務供應商 → Google。
  2. 請貼上您的客戶識別碼與客戶密鑰。
  3. 將「啟用 Google 服務提供者」切換至開啟狀態,然後點擊「儲存」。

當使用者使用 Google 登入時,系統需要知道驗證完成後應返回何處。預設會跳轉至http://localhost:3000/,但我們的前端服務實際運行於http://localhost:5173。

  1. 為確保功能正常運作,請在應用程式執行時複製 GitHub 的網址(格式類似https://codespace_name.app.github.dev/)。
  2. 在 Supabase 中,前往「驗證」→「URL 設定」。 
  3. 將您的 Github 網址新增為重定向網址。
  1. 在您的 .env 中新增 Github Codespace 的網址作為變數VITE_DEVELOPMENT_URL(僅當您的應用程式為 React 時需使用 VITE)。

您將要求 Copilot 添加 Google 登入功能。請注意,我們正在添加包含重定向網址的指令。 

  1. 請提示 Copilot 在您的登入畫面中新增 Google 登入作為選項。

角色:您是一位網頁開發者,協助我在 React 應用程式中實作使用者驗證功能。

任務:在登入頁面新增 Google 登入選項。使用 Supabase 的 signInWithOAuth 方法並指定 'Google' 供應商。重定向至環境變數 VITE_DEVELOPMENT_URL。

注意:若您切換 代碼空間,則需建立新的 .env檔案,其中需包含您的重定向設定(以及前端所需的 Supabase URL 與 API 金鑰)。

當您部署應用程式時,必須在 Supabase 中將您的生產環境應用程式網址設定為重定向,同時也需在程式碼中將其設定為重定向。

  1. 將您的變更提交至您的分支。

請保持分支與代碼空間開啟,以便進行下一項活動。

保護您的 API 路由

您的應用程式現已具備登入頁面,因此使用者必須登入才能瀏覽應用程式內容。然而,您的 Express.js 後端伺服器仍會處理所有接收到的請求,即使使用者並非透過前端介面登入。這意味著有人可能直接開啟瀏覽器控制台,繞過登入頁面直接呼叫您的 API。

這是「視窗」問題。您用登入頁面鎖住了前門,但視窗(您的 API 路由)仍敞開著。要解決此問題,您需要新增 安全檢查 (稱為 中間件)至後端,用以驗證每個 API 請求確實來自已登入的使用者。

您需要:

保護以下路徑:

  • 花費金錢(AI呼叫、API使用)
  • 修改資料(建立、更新、刪除)
  • 顯示使用者專屬資訊

保持公開:

  • 健康檢查
  • 靜態資產
  • 首頁(若對公眾可見)

當您的後端接收到請求時:

  1. 檢查:「此請求是否附有用戶已登入的證明?」
  2. 若為是→ 正常處理請求
  3. 若無→ 以「401 未授權」錯誤拒絕請求

該「證明」為 JWT(JSON網路憑證),這是 Supabase 賦予每位登入用戶的長字元串。您的前端在每次向後端發送請求時都會包含此憑證。後端會將憑證提交至 Supabase 進行驗證,以確認其真實性與有效期限。

內容物:

  • 使用者身分(其使用者ID、電子郵件)
  • 當它被發布時
  • 當它到期時
  • 一份加密簽名,用以證明該簽名由 Supabase 發出且未遭篡改
活動 3

保護您的 API 路由

預估時間:25分鐘

若您尚未進入 Codespace,請開啟它,並使用與活動 1 和 2 相同的分支。

  1. 在你的 伺服器 資料夾(您的 Express 後端程式所在位置),安裝 Supabase:
    • 在終端機視窗中輸入:
      cd server
      npm install @supabase/supabase-js
  1. 返回您的 Supabase 專案,並取得 秘密 API 金鑰 (非可發布密鑰)。
    • 前往專案設定 → API 金鑰
    • 向下滾動至「秘密金鑰」。
    • 複製它並將其儲存到電腦上的安全位置。
  2. 更新 .env 檔案 伺服器 目錄中的 server/.env 是後端專屬的獨立檔案)。
  3. 請添加以下行,並貼上您的 Supabase 網址與密鑰:
    SUPABASE_URL=您的_公開網址
    SUPABASE_SERVICE_ROLE_KEY=您的_密鑰

重要:

  • 請使用與前端相同的SUPABASE_URL
  • 請使用機密金鑰(非公開金鑰)作為SUPABASE_SERVICE_ROLE_KEY
  • 私密金鑰具備管理員權限——切勿在前端程式碼中暴露此金鑰
  1. 使用此提示要求 Copilot 在後端程式碼中添加程式碼,以確保 API 呼叫的安全性。

角色:您是協助我強化 Express.js API 安全性後端開發人員。

任務: 

  1. 導入 Supabase 客戶端函式庫 (@supabase/supabase-js)
  2. 使用環境變數中的 SUPABASE_URL 和 SUPABASE_SERVICE_ROLE_KEY 初始化 Supabase 客戶端。
  3. 在我的 Express.js 後端中添加一個驗證中間件函數。
    • 提取代幣
    • 請 Supabase 驗證代幣
    • 若為是 → 讓請求繼續
    • 若無 → 返回 401 錯誤
  4. 僅將此中間件套用至呼叫外部 API 的路由

  5. 請勿將其套用於首頁或健康檢查等公開路徑

  1. 檢視生成的程式碼。

    您應能看到一個名為requireAuth(或類似名稱)的函式,該函式會檢查 Authorization 標頭、擷取憑證,並透過 Supabase 進行驗證。您還應看到此函式已套用至您的 API 路由,但未套用至公開路由。

    關鍵部分應類似以下程式碼:
				
					async function requireAuth(req, res, next) {
  const authHeader = req.headers.authorization;

  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Unauthorized' });
  }

  const token = authHeader.split(' ')[1];
  const { data, error } = await supabase.auth.getUser(token);

  if (error || !data.user) {
    return res.status(401).json({ error: 'Unauthorized' });
  }

  req.user = data.user;
  next();
}

// Protected route — security check runs first
app.post('/api/ai-query', requireAuth, async (req, res) => {
  // This code only runs if the user is logged in
});

// Public route — no security check needed
app.get('/health', (req, res) => {
  res.json({ status: 'ok' });
});

				
			

對於 TutorMatch 而言,受保護的路由是app.post('/api/ai-query', requireAuth, …)。您的應用程式可能採用不同的路由名稱,但核心模式相同——保護那些執行時會產生費用的路由。

您的後端系統現在要求每次請求都附帶登入驗證,但前端尚未傳送此驗證資訊。

  1. 請提示 Copilot 更新您的前端程式碼。以下為範例提示語。

角色:您是協助我將 React 應用程式與安全 Express.js 後端連接的前端開發人員。

任務:將所有前端 API 呼叫更新為受保護路由,並在授權標頭中加入 Supabase 會話令牌。

  1. 查看 Copilot 更新的程式碼:

    您會發現現在的fetch呼叫已包含 Authorization 標頭。以下是變更內容:
				
					const { data: { session } } = await supabase.auth.getSession();

if (!session) {
  window.location.href = '/login';
  return;
}

const response = await fetch('/api/ai-query', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${session.access_token}`
  },
  body: JSON.stringify({ prompt: userInput })
});

				
			

這便是萬物相連之處:

  1. 當使用者登入時,Supabase Auth 會為您的前端提供一個令牌。
  2. 您的前端在每次請求時都會傳送該憑證。
  3. 後端會檢查它。
    • 若有效,請求便會通過。
    • 否則,它就被封鎖了。
  1. 在 Codespaces 中執行您的應用程式,並測試兩種情境:

測試 1 — 已登入使用者(應可正常運作):

    1. 開啟您的應用程式,並使用測試憑證登入。
    2. 請如常使用您的AI功能。
    3. 它應該會和以前完全一樣運作。

如果這能運作,表示您的前端正在傳送憑證,而後端也成功接收到了。

測試 2 — 無登入(應被封鎖):

    1. 從應用程式登出,使使用者處於未登入狀態。
    2. 開啟開發人員工具(按 F12 或右鍵點擊檢查),然後點擊「控制台」標籤頁。
    3. 請嘗試將以下內容貼入控制台直接呼叫您的 API(注意:我們的 擷取路徑為 /api/ai-query – 請將您的擷取指令修改為與前端程式碼中相同的版本。)
				
					fetch('/api/ai-query', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ prompt: 'test' })
}).then(r => r.json()).then(console.log);
				
			

You should see { error: “Unauthorized” } — NOT an AI response.

若兩項測試皆通過,您的應用程式即為安全。登入頁面如同守護前門,中間件則如同守護窗戶。

  1. 最後檢查一次,確保您的使用者驗證功能正常運作:

    • 完全登出並重新整理頁面——您應會看到登入頁面
    • 登入並驗證您的 AI 功能是否正常運作
    • 開啟開發人員工具控制台,嘗試在不提供憑證的情況下呼叫您的 API(如同測試 2 所示)—— 確認您會收到 401 錯誤。
  2. 在源代碼控制系統中提交並確認您的變更,附上訊息如「新增具驗證中介軟體的 API 路由保護」,並將其推送至您的分支。

  3. 前往 GitHub,從您的add-auth 分支 建立拉取請求,審閱變更內容,並合併至主分支。 

疑難排解

以下是一些可能出現的問題及其解決方案

開啟開發者工具,切換至「網路」分頁,點擊 API 請求項目,檢視「請求標頭」。您應能看到Authorization: Bearer eyJ… 字串。若標頭缺失,請確認您的getSession()呼叫是否正常運作。

請確認您已將requireAuth套用至正確的路由。

請確保您在後端使用的是SUPABASE_SERVICE_ROLE_KEY(而非SUPABASE_ANON_KEY)。服務角色金鑰具備驗證任何使用者所發送令牌的權限。

請確認已安裝 Supabase 客戶端函式庫(npm install @supabase/supabase-js),並在伺服器檔案頂端導入該函式庫。

反思

添加使用者驗證是讓您的應用程式更專業且安全的關鍵步驟。請考慮以下問題:

Sunset and reflection over lake
01

安全防護
若您僅設有登入頁面卻未配置後端安全驗證,可能發生什麼情況?
02

您的 API 路由
在您自己的最小可行產品(MVP)中,哪些路由需要驗證中介軟體,哪些可以保持公開?您是如何決定的?
03

提示
角色+任務結構如何影響AI生成的程式碼品質?其表現是否優於(或遜於)過往提示詞的生成結果?

關鍵術語

  • 驗證:確認使用者身分——核實其是否為自稱者(註冊與登入)。此概念有別於授權,後者用於管控使用者可存取的範圍。
  • 授權:控制經身份驗證的使用者可執行的操作。行級安全性(RLS)是於資料庫層級實現此功能的方式之一。
  • 密碼雜湊:將密碼轉換為不可讀取的字串後儲存。Supabase 認證系統會自動執行此步驟。儲存明文密碼將構成嚴重的安全風險。
  • 會話代幣:一種臨時識別碼,用於在使用者驗證後維持其登入狀態。Supabase 認證系統會自動管理這些代幣。
  • 驗證中介軟體:在路由處理程序執行前運行,用以驗證請求是否來自已登入用戶。若用戶未通過驗證,中介軟體將阻擋該請求。
  • 授權標頭:一種HTTP標頭,用於在每次請求中將驗證憑證(通常為代幣)從前端傳遞至後端。
  • Bearer Token: A type of authentication token sent in the Authorization header. The format is Bearer <token>. “Bearer” means “whoever carries this token is authorized.”
  • JSON Web Token (JWT):一種標準化的驗證憑證格式。內含經編碼處理的用戶資訊及有效期限。Supabase 採用 JWT 進行會話管理。
  • OAuth:一種認證 標準,允許使用者透過現有帳戶(如Google)登入,無需建立新密碼。全稱為「開放授權」。

其他資源