iT邦幫忙

2025 iThome 鐵人賽

DAY 17
0
Vue.js

打造銷售系統30天修練 - 全集中・Vue之呼吸系列 第 17

Day 17:[Authの呼吸・參之型] 登入流程 - Token管理與安全性

  • 分享至 

  • xImage
  •  

在前兩天的修煉中,我們成功地引導使用者完成了 Google 的授權流程。現在,該如何將這個授權,轉化為我們系統內部的登入狀態?

今天,我們將學習如何從前端取得使用者身份憑證,並與後端配合,打造一套安全、可靠的登入機制。

隱含式流程與後端驗證

我們的核心策略是:前端負責快速取得 Google 的 ID Token,然後立即將其交給後端,由後端完成所有安全性驗證工作。

第一步:前端取得 ID Token

首先,我們使用 Google Identity Services (GSI) 函式庫來渲染登入按鈕,並在使用者成功登入後,於回呼函式中取得 ID Token

1. 在 HTML 中準備一個渲染按鈕的容器

<div id="googleLogin"></div>

2. 在 Vue 元件中初始化並渲染按鈕

import { onMounted } from 'vue';

const clientId = import.meta.env.VITE_GOOGLE_CLIENT_ID;

// 在 setup 中
onMounted(() => {
  if (window.google) {
    window.google.accounts.id.initialize({
      client_id: clientId,
      callback: handleCredentialResponse, // 登入成功後的回呼函式
    });

    window.google.accounts.id.renderButton(
      document.getElementById('buttonDiv'), // 渲染按鈕的容器
      { theme: 'outline', size: 'large' }, // 按鈕樣式設定
    );
  }
});

function handleCredentialResponse(response) {
  // 將這個 Token 發送到後端
  sendTokenToBackend(response.credential);
}

第二步:前端將 Token 發送至後端

前端的職責很單純:拿到 response.credential 後,立刻透過 API 請求將它發送到後端進行驗證。

// 前端發送請求
async function sendTokenToBackend(googleIdToken) {
  const backendResponse = await fetch('/api/auth/google-verify', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ token: googleIdToken }),
  });

  if (backendResponse.ok) {
    const { appToken } = await backendResponse.json();
    // 成功!拿到我們自家系統的 Token
    // 接下來就是儲存這個 appToken 的問題了
  }
}

第三步:後端驗證 Google ID Token

這是整個流程中最關鍵的安全環節。後端收到前端傳來的 googleIdToken 後:

  1. 使用 Google 的公鑰來驗證此 Token 的數位簽名。這確保了 Token 確實是由 Google 簽發的。
  2. 檢查 Token 的內容,至少包含:
    • aud (Audience):必須是我們在 Google Cloud Console 申請的那個 Client ID。
    • iss (Issuer):必須是 https://accounts.google.comaccounts.google.com
    • exp (Expiration Time):必須還沒過期。

各大後端語言通常都有官方或社群維護的函式庫(例如 Node.js 的 google-auth-library)可以輕鬆完成這項驗證工作。

參考:Google doc - 驗證伺服器端的 Google ID 權杖

第四步:後端簽發「自家」Token

一旦後端確認 Google ID Token 是真實有效的,就等於確認了使用者的合法身份。此時,後端會為這位使用者簽發一個屬於我們自己應用程式的 Token(我們自己的 JWT),並將其回傳給前端。

這個「自家 Token」才是前端後續用來證明「我在 POS 系統中已登入」的唯一憑證。

前端的職責:Token 的儲存與管理

當後端驗證成功並回傳我們自家的 appToken 後,下一個關鍵問題便是:前端該如何安全地儲存這個 Token?

你可能會問,身為一個 Vue 開發者,是否有專屬於 Vue 的儲存方法?這是一個很好的問題!答案是:Token 的儲存策略是瀏覽器層級的 Web 標準,而非框架層級的。 無論使用 Vue、React 還是 Angular,我們都得在瀏覽器提供的幾種機制中做選擇。

儲存策略分析

1. localStorage / sessionStorage

  • 說明:將 Token 直接存入瀏覽器的 Web Storage。
  • 優點:API 簡單易用,localStorage 可以在關閉瀏覽器後依然保存。
  • 缺點極易受到 XSS (跨站腳本) 攻擊。任何頁面上的 JavaScript 都能存取它,如果網站有 XSS 漏洞,駭客就能輕易竊取 Token。
  • Vue 實作方式
    • 你可以直接呼叫 localStorage.setItem('token', a_token)
    • 在 Vue 中,更優雅的做法是將這些操作封裝在 Pinia 的 action 中,讓狀態管理更集中。
    // store/auth.js (Pinia)
    actions: {
      setToken(token) {
        this.token = token;
        localStorage.setItem('token', token);
      },
      clearToken() {
        this.token = null;
        localStorage.removeItem('token');
      }
    }
    

2. 記憶體 (In-memory)

  • 說明:將 Token 存放在一個 JavaScript 變數中。

  • 優點:相對安全,不會被 XSS 輕易竊取。

  • 缺點頁面一重新整理,Token 就會消失,使用者需要重新登入,體驗較差。

  • Vue 實作方式

    • 這正是 Pinia (或 Vuex) 的用武之地。將 Token 作為 Store 的一個 state,是 Vue 生態中最常見的記憶體管理模式。
    • 整個應用程式都可以從這個 Store 中響應式地讀取 Token 狀態。
    // store/auth.js (Pinia)
    import { defineStore } from 'pinia';
    
    export const useAuthStore = defineStore('auth', {
      state: () => ({
        token: null,
        user: null,
      }),
      actions: {
        setToken(token) {
          this.token = token;
          // 你可以在這裡解析 token 獲取 user info
        },
      },
    });
    

3. HttpOnly Cookie (推薦)

  • 說明:由後端在回應時,透過 Set-Cookie 標頭將 Token 寫入瀏覽器的 Cookie 中,並設定 HttpOnly 屬性。
  • 優點
    • 無法被 JavaScript 存取:從根本上杜絕了 XSS 竊取 Token 的風險。
    • 自動發送:瀏覽器會自動處理,前端無感。
  • 缺點
    • 需要後端配合設定。
    • 需要防範 CSRF 攻擊(可透過 SameSite 屬性緩解)。
  • Vue 實作方式
    • 在這個模式下,Vue 應用程式完全不需要也無法直接操作 Token。
    • 前端唯一需要注意的,可能是在使用 axios 等工具發送跨來源 (Cross-origin) 請求時,需要設定 withCredentials: true,瀏覽器才會願意把 Cookie 一起帶上。
    // axios 設定
    const apiClient = axios.create({
      baseURL: 'https://api.your-domain.com',
      withCredentials: true, // 允許跨來源請求攜帶 Cookie
    });
    

即使在 Vue 3 中,我們依然是圍繞著這幾種瀏覽器標準來進行 Token 管理。

  • 最高安全性:首選 HttpOnly Cookie。這是業界公認的最佳實踐,雖然需要後端配合,但一勞永逸。
  • 前端純記憶體方案:如果想做一個純前端渲染、不考慮頁面刷新後保持登入狀態的應用,Pinia 是最優雅的選擇。
  • 不推薦的方案:應盡量避免將 JWT 這種敏感憑證直接存放在 localStorage

帶上通行證:發送需要驗證的請求

當我們需要請求受保護的後端資源時(例如:獲取訂單列表),就必須在請求中帶上我們的通行證,以證明身份。

  • 如果使用 HttpOnly Cookie:瀏覽器會自動處理。

  • 如果使用 localStorage 或記憶體:你需要手動在 HTTP 請求的標頭 (Header) 中加入 Authorization 欄位。

通常我們會使用 axios 這類的 HTTP 客戶端,並透過「攔截器 (Interceptor)」來統一處理。

// 使用 axios 攔截器
import axios from 'axios';
import { store } from './store'; // 假設是 Pinia store

const apiClient = axios.create({
  baseURL: '/api',
});

// 請求攔截器
apiClient.interceptors.request.use((config) => {
  const token = store.state.token; // 從記憶體獲取 token
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

// 後續所有透過 apiClient 發出的請求,都會自動帶上 Token
export default apiClient;

總結

今天,我們完成了登入流程的最後一哩路,並專注於「隱含式流程」的實現。我們確立的完整流程是:

前端取得 Google ID Token -> 後端驗證 Google ID Token -> 後端簽發自家 JWT -> 前端儲存 JWT -> 後續請求攜帶 JWT

我們理解到,為了安全性,向後端發送憑證並進行二次驗證的步驟也絕對不可省略。同時,我們也分析了前端儲存 Token 的各種策略,並強烈推薦使用 HttpOnly Cookie 作為最安全的方案。

至此,「Authの呼吸」的前三個型態已經讓我們擁有了一套清晰且安全的驗證機制。

明日,Day 18:[Routerの呼吸・壹之型] 導航系統 - 理解SPA路由機制。心を燃やせ 🔥!


上一篇
Day 16:[Authの呼吸・貳之型] SSO整合 - Google OAuth登入實作(下)
下一篇
Day 18:[Routerの呼吸・壹之型] 導航系統 - 理解SPA路由機制
系列文
打造銷售系統30天修練 - 全集中・Vue之呼吸19
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言