使用者身分驗證與帳號

註冊、登入、電子郵件驗證、社群登入、2FA 與帳號中心。依產品需求完成設定後即可使用,不必重新實作身分驗證系統。

Saavo 範本內建完整的帳號系統,涵蓋註冊、登入、電子郵件驗證、忘記密碼、社群登入、雙重驗證、帳號中心與管理員身分等常見功能。

開發自己的產品時,通常不必重新實作這些功能。只要依產品需求調整身分驗證政策,設定好電子郵件、OAuth 等所需服務,再於頁面、API 與前端元件中串接並驗證使用者身分即可。

既有功能

範本初始化後,即可使用下列身分驗證功能:

功能預設入口預設狀態
註冊/auth/signup已啟用
登入/auth/login已啟用
忘記密碼/auth/forgot-password已啟用
電子郵件驗證/auth/verify-email註冊後寄送驗證郵件,但預設不強制驗證
Google 登入登入頁面設定金鑰後啟用
GitHub 登入登入頁面設定金鑰後啟用
雙重驗證/account使用者可自行啟用
帳號中心/account已登入的使用者可存取
管理後台/dashboard管理員可存取

除了上述功能,Saavo 也提供工作階段(Session)管理、登入彈出視窗、復原碼、敏感操作的再次驗證,以及其他與身分驗證相關的安全限制。

以 Saavo 範本開發自己的產品時,不必另外建立一套登入狀態,也不需要在業務程式碼中自行解析 Cookie 或直接操作 Session 資料。請以 Saavo 提供的身分驗證結果為基礎,開發自己的功能。

先體驗註冊與登入

修改身分驗證設定前,建議先使用預設設定,完整操作一次驗證流程,了解範本已提供的功能。

啟動開發伺服器後,開啟首頁,右上角會顯示登入按鈕:

登入按鈕

按下登入按鈕,即可在彈出視窗中完成登入。Saavo 也提供獨立的登入頁面:

登入頁面

如果還沒有帳號,可以前往註冊頁面,填寫電子郵件地址與密碼完成註冊:

註冊頁面

預設情況下,Saavo 會寄送電子郵件驗證信,但不強制使用者完成驗證,尚未驗證也能繼續使用產品。

本機開發時,範本不會實際寄送郵件,而是將模擬郵件輸出到開發伺服器的主控台。你可以直接從記錄中找到驗證碼,例如:

[DEV EMAIL PREVIEW] {
  recipients: [ 'saavo@live.com' ],
  subject: 'Welcome to Saavo Starter',
  text: 'Hello, saavo,\n' +
    '\n' +
    'Thank you for registering with Saavo Starter! To ensure the security of your account, we need to verify your email address.\n' +
    '\n' +
    'Your verification code is 2RBFH7IA\n' +
    '\n' +
    'If you did not sign up for a Saavo Starter account, please disregard this email.\n' +
    '\n' +
    'If you have any questions, feel free to contact our support team.\n' +
    '\n' +
    'Best regards,\n' +
    'The Saavo Starter Team',
  html: ''
}

使用上述郵件中的驗證碼,即可完成電子郵件驗證。如果沒有啟用強制驗證,也可以先略過:

略過電子郵件驗證

登入後,透過右上角的頭像進入帳號中心:

帳號中心

帳號中心(/account)已包含個人資料、安全性設定(密碼、電子郵件、雙重驗證)、已購產品與付款紀錄等功能。

除了上述功能,範本也為管理員提供獨立的主控台頁面:

管理後台

管理員的電子郵件地址設定於 adminEmails。只有地址符合設定,且已完成電子郵件驗證的使用者,才能取得管理員身分並存取 /dashboard。

提示

開發人員通常不必重新實作這些頁面,只要依產品需求調整身分驗證政策,讓業務功能沿用既有的驗證狀態即可。

設定使用者身分驗證

預設設定的目的是讓你快速啟動並體驗範本。正式開發產品時,建議至少確認以下幾個關鍵選項。

是否強制驗證電子郵件

預設情況下,使用者註冊後會收到驗證郵件,即使尚未完成驗證,也可以登入。

產品正式對外提供服務後,尤其是資源消耗較高、需要防範大量註冊,或需要確認帳號真實性的情境,建議啟用強制電子郵件驗證:

emailVerification: {
    defaultRequired: true,
}

啟用後,尚未完成電子郵件驗證的使用者存取受保護頁面時,會被引導至電子郵件驗證流程。

註冊後是否寄送驗證郵件,由另一個設定決定:

emailVerification: {
    sendRegisterEmail: true,
}

也就是說,「是否寄送驗證郵件」與「是否必須完成電子郵件驗證」是兩個獨立的設定。

電子郵件驗證預設採用驗證碼:

emailVerification: {
    sendVerifyType: 'code',
}

如果想讓使用者直接按下郵件中的連結完成驗證,請改為:

emailVerification: {
    sendVerifyType: 'link',
}

注意事項

啟用強制電子郵件驗證前,請先確認正式環境的郵件服務能正常運作,否則新註冊的使用者將無法通過驗證,繼續使用產品。

選擇登入方式

電子郵件與密碼登入預設已啟用。

此外,Saavo 也支援 Google 與 GitHub 登入。第三方登入會依目前的環境變數啟用,未設定對應的 OAuth 金鑰時,不會顯示該登入入口。

如果你的 SaaS 產品主要面向一般消費者,啟用 Google 登入通常能降低註冊門檻。如果產品面向開發人員,可以再依目標使用者決定是否提供 GitHub 登入。若需要其他第三方登入方式,例如 Apple 登入,則需參考相關文件自行實作。

使用 Google 登入時,也可以透過以下設定啟用 Google One Tap:

enableGoogleOneTap: true

Client ID、Client Secret 與 Callback URL 的設定方式,請參考後文「準備身分驗證服務」。

是否強制使用雙重驗證

Saavo 已提供以驗證器 App 為基礎的雙重驗證與復原碼。

預設設定如下:

twoFactorAuth: {
    defaultRequired: false,
}

2FA 是選用功能。上述選項表示所有使用者預設不啟用,但使用者隨時都能在 /account 的安全性設定中自行啟用。

雙重驗證設定

這種方式適合大多數 SaaS 產品。需要額外安全保護的使用者可以自行啟用,其他使用者也不必增加註冊與使用上的步驟。

如果產品有更高的安全需求,可以設定為:

twoFactorAuth: {
    defaultRequired: true,
}

啟用後,使用者必須先完成雙重驗證設定,才能使用產品。

復原碼的數量可以透過以下設定調整:

twoFactorAuth: {
    recoveryCodeCount: 5,
}

另外,初始化產品時,記得修改 twoFactorAuth.issuer:

twoFactorAuth: {
    issuer: 'Acme',
}

issuer 會顯示在 Google Authenticator、1Password 等驗證器 App 中,請改成自己的產品名稱。

設定登入後的重新導向頁面

範本預設在註冊、登入與登出後返回首頁。

實際的 SaaS 產品通常會讓使用者登入後直接進入應用程式。例如,產品的主要介面位於 /app:

ui: {
    redirectTo: {
        afterSignup: '/app',
        afterLogin: '/app',
        afterLogout: '/',
    },
}

調整後的流程如下:

註冊成功 → /app
登入成功 → /app
登出 → /

建議及早調整這部分設定,避免使用者登入後仍被帶回範本的預設首頁。

選擇登入彈出視窗或登入頁面

Saavo 預設啟用登入彈出視窗。使用者可以按下右上角的登入按鈕開啟視窗,也可能在操作業務功能時,由系統提示登入。

例如,尚未登入的使用者按下購買按鈕時,可以在目前頁面完成登入,接著繼續原本的付款流程,不必先前往登入頁面。

如果不需要登入彈出視窗,可以關閉 ui.useLoginModal:

ui: {
    useLoginModal: false,
}

關閉後,需要登入時,系統會統一重新導向完整的登入頁面。

兩種方式各有適合的情境。如果使用者經常在操作途中被要求登入,彈出視窗較自然。如果登入本身就是明確的獨立步驟,使用完整的登入頁面會比較單純。

新增管理員

Saavo 透過 config/base.ts 中的 adminEmails 設定管理員:

adminEmails: ['admin@saavo.dev'],

使用該電子郵件地址註冊帳號並完成驗證後,即可取得管理員身分並存取:

/dashboard

adminEmails 不會建立可略過身分驗證的超級帳號。管理員首先是一般使用者,只有已完成驗證的電子郵件地址與設定中的地址完全相符,才會取得管理員身分。

建議在產品初始化後,及早建立自己的管理員帳號,並確認能正常進入管理後台。

準備身分驗證服務

範本已內建身分驗證邏輯,但電子郵件、OAuth、Turnstile 等功能仍需要外部服務與環境變數。

SAAS_SECRET

每個專案都需要設定 SAAS_SECRET 環境變數,請避免在不同專案中重複使用相同的值:

SAAS_SECRET=...

Session 與其他敏感資料的加密都會用到這個值,建議每個專案獨立設定。

電子郵件服務

下列身分驗證流程都需要寄送郵件:

  • 註冊後的電子郵件驗證
  • 重新寄送驗證碼
  • 忘記密碼
  • 修改電子郵件地址
  • 其他需要透過電子郵件確認的安全操作

本機開發時,Saavo 會直接使用主控台輸出的模擬郵件,不會實際寄送。

正式部署後,需要設定實際寄送郵件的服務。支援的郵件服務與設定方式,請參考:

電子郵件

如果啟用了強制電子郵件驗證,應先確認郵件服務能正常寄送郵件。

Google 與 GitHub 登入

如果需要 Google 登入,請在 .env 中設定:

GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...

Google OAuth 應用程式中的 Redirect URI 應設為:

https://your-domain.com/api/auth/oauth2/google

如果需要 GitHub 登入:

GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...

對應的 Redirect URI 為:

https://your-domain.com/api/auth/oauth2/github

未設定對應的金鑰時,不會顯示該登入方式。

修改 .env 後,需要重新啟動開發伺服器。

完整的 Google 登入設定流程,請參考:

串接 Google 登入

Turnstile 人機驗證

Saavo 支援使用 Cloudflare Turnstile,為註冊、登入等身分驗證頁面加入人機驗證。

預設不會每次開啟頁面都顯示 Turnstile,而是依身分驗證操作的風險計數觸發:

useTurnstile: {
    threshold: 2,
    interval: '1h',
}

其中:

  • threshold 是觸發人機驗證的風險計數門檻。
  • interval 是風險計數的有效期間。

依預設設定,對應操作的風險計數達到 2 時,就會要求人機驗證。參數錯誤、身分驗證失敗與部分檢查事件會增加計數,成功事件則可以降低計數,因此不能將它理解為「重新整理頁面超過兩次」。

Turnstile 人機驗證

如果希望更早觸發驗證,可以調低門檻。不需要 Turnstile 時,也可以直接設定:

useTurnstile: false

啟用 Turnstile 時,需要設定:

CLOUDFLARE_TURNSTILE_SITE_KEY=...
CLOUDFLARE_TURNSTILE_SECRET_KEY=...

本機開發可以使用 Cloudflare 提供的測試金鑰,正式環境則應使用為自己網站申請的正式金鑰。

在業務功能中驗證使用者身分

完成設定後,下一步是讓業務程式碼讀取目前使用者的身分驗證狀態。

基本原則是:

不要在業務程式碼中自行讀取 Cookie,也不要直接查詢或修改 Session 資料表。

Saavo 已透過 Guard 統一處理登入狀態、電子郵件驗證、雙重驗證等條件,業務程式碼直接使用驗證結果即可。

保護頁面

許多 SaaS 頁面只允許已登入的使用者存取,例如帳號中心、工作台,或使用者自己的專案頁面。

由伺服器端呈現的頁面,可以在頁面的 Handler 中呼叫 authenticatedGuard:

const accountPageHandlers = createLayoutHandlers(
    createLayoutRenderer('page'),
    (c: Ctx) => {
        const locale = c.var.getLocale();
        const guardResult = authenticatedGuard(c);

        if (!guardResult.success) {
            return c.redirect(
                getFullPath(guardResult.details?.redirectUrl as string, locale),
            );
        }

        const { authContext } = guardResult.data;

        return <AccountPage c={c} />;
    },
);

export default accountPageHandlers;

authenticatedGuard 不只判斷使用者是否有 Session。

它會依目前的身分驗證政策,自動處理不同的驗證狀態:

尚未登入
↓
登入頁面

需要驗證電子郵件,但尚未驗證
↓
電子郵件驗證

要求 2FA,但尚未完成
↓
雙重驗證流程

已滿足所有身分驗證要求
↓
繼續執行業務頁面

因此,業務頁面不必重複實作登入、電子郵件驗證與 2FA 的判斷。

如果頁面還需要管理員權限、角色或 Capability,在通過身分驗證後,再執行對應的檢查即可。

完整範例請參考:

登入後才能存取的頁面

保護 API

API 使用同一套身分驗證 Guard,只是在失敗時回傳 JSON,而不是重新導向:

const guardResult = authenticatedGuard(c);

if (!guardResult.success) {
    return c.json(
        guardResult,
        guardResult.code === gResultCode.authLoginRequired ? 401 : 403,
    );
}

const { authContext } = guardResult.data;

這樣就能讓頁面與 API 的身分驗證規則保持一致。

日後將電子郵件驗證或 2FA 從選用改為強制時,也不必逐一修改業務 API 的驗證邏輯。

使用 authContext 處理使用者的業務操作

驗證成功後,可以從 authContext 物件取得目前請求的身分驗證資訊,其中包含目前使用者與 Session 的資訊。後續業務邏輯會透過它取得使用者身分,也以此作為權限控制的基礎。

例如,使用者正在建立自己的專案:

POST /api/projects

伺服器端需要知道這個專案屬於誰。

此時應使用身分驗證執行脈絡中的目前使用者身分,而不是直接信任前端送出的 userId。

類似的業務操作還包括:

  • 建立屬於目前使用者的資料
  • 查詢目前使用者的專案
  • 修改自己的資源
  • 判斷目前的角色
  • 檢查 Entitlement
  • 檢查 Capability
  • 判斷目前使用者是否可以執行某項操作

整體流程通常如下:

請求
↓
authenticatedGuard()
↓
authContext
↓
角色 / 使用權益 / 能力
↓
業務邏輯

身分驗證負責確認「目前是誰」,Role、Entitlement 與 Capability 則進一步決定「目前使用者可以做什麼」。

在前端讀取目前使用者

除了伺服器端的身分驗證,前端元件有時也需要知道目前使用者是否已登入。

Saavo 採用 MPA 頁面呈現方式,跨元件共用的前端狀態需要透過 Store 管理。目前的使用者狀態以 nanostores 實作,你可以透過 useCurrentUser 取得:

import { setLoginModalOpen } from '@/stores/login-modal';
import { useCurrentUser } from '@/stores/user';

const { isLoggedIn } = useCurrentUser();

if (!isLoggedIn) {
    setLoginModalOpen(true);
    return;
}

這個範例會在使用者尚未登入時,開啟登入彈出視窗。

其他元件修改使用者資料後,也可以透過 setCurrentUser 更新共用狀態。

請注意,前端的 isLoggedIn 主要用來控制介面行為,例如顯示登入按鈕、開啟登入彈出視窗,或隱藏某些入口。

資料存取與權限控制仍必須在伺服器端透過 Guard、Role、Entitlement 或 Capability 檢查,不能以前端狀態作為安全邊界。

擴充帳號設定

/account 已涵蓋大多數帳號層級的設定,不必重複實作。

適合繼續放在 /account 的內容包括:

  • 使用者資料
  • 電子郵件地址
  • 密碼
  • 雙重驗證
  • 復原碼
  • 已購產品
  • 付款與帳單資訊

這些功能都與使用者帳號本身有關。

屬於特定 SaaS 業務的設定,則應放在該業務的頁面中。例如,AI 產品可能還需要:

預設 AI 模型
生成參數
工作區設定
通知設定
團隊設定

這些內容應由業務模組自行實作,不要全部加入 /account。

可以用一個簡單的方式判斷:

與「使用者是誰」有關的設定放在 /account,與「產品如何運作」有關的設定放在業務頁面。

上線檢查

身分驗證是產品上線前必須完整驗證的基礎功能,建議至少檢查下列流程:

  • 註冊、電子郵件驗證、登入與登出流程正常。
  • 可以順利完成忘記密碼流程。
  • /account 中的帳號功能可以正常使用。
  • 已啟用的 Google / GitHub 第三方登入可以順利完成。
  • 若已啟用 2FA,可以完成啟用、驗證與復原碼流程。
  • 尚未登入的使用者無法存取受保護的頁面與 API。
  • 一般使用者無法存取 /dashboard。
  • 管理員可以正常存取 /dashboard。
  • 正式環境使用專案專屬的 SAAS_SECRET。
  • 正式環境可以正常寄送郵件。
  • OAuth 的 Redirect URI 已改為正式環境的網域。
  • Turnstile 等身分驗證安全設定已使用正式環境的金鑰。

建議使用新註冊的一般帳號與管理員帳號,完整測試一次,不要只使用開發期間一直沿用的舊測試帳號。

常見問題

接下來

依接下來要開發的功能,可以繼續閱讀:

大多數產品完成本章的設定後,就不需要再修改身分驗證系統本身。

接下來,只要以目前登入的使用者為基礎,串接產品實際需要的業務功能即可。