在上篇中,我們已經完成了所有理論學習和前置作業,並成功取得了 Google OAuth 的 Client ID。
現在,是時候將這些準備工作付諸實踐了。本篇將深入 LoginView.vue
的 <script setup>
區塊,逐行解析我們是如何利用 Google Identity Services 函式庫來觸發登入流程,並取得授權碼的。
讓我們先看看 LoginView.vue
中的主要程式碼結構:
//常數配置
const STORE_OPTIONS = [
{ value: '', text: '請選擇您的門市...', disabled: true },
{ value: 'test1', text: '測試門市1' },
{ value: 'test2', text: '測試門市2' },
{ value: 'test3', text: '測試門市3' },
{ value: 'test4', text: '測試門市4' },
{ value: 'test5', text: '測試門市5' },
{ value: 'test6', text: '測試門市6' },
];
const ERROR_MESSAGES = {
STORE_REQUIRED: '請選擇您的門市',
CONFIG_ERROR: '系統配置錯誤,請聯繫系統管理員',
GOOGLE_LOAD_FAILED: 'Google 服務加載失敗,請稍後再試',
LOGIN_FAILED: '登入失敗,請重試',
LOGIN_ERROR: '登入過程中發生錯誤,請重試',
BACKEND_ERROR: '後端驗證失敗,請重試',
NETWORK_ERROR: '網路錯誤,請重試',
};
// 環境變數
const clientId = import.meta.env.VITE_GOOGLE_CLIENT_ID;
googleLogin
函式深度解析現在讓我們來看核心函式 googleLogin
:
async function googleLogin() {
// 1. 前置檢查
if (!validateStore()) {
return;
}
if (!isClientIdConfigured()) {
storeError.value = ERROR_MESSAGES.CONFIG_ERROR;
return;
}
if (!isGoogleServiceAvailable()) {
storeError.value = ERROR_MESSAGES.GOOGLE_LOAD_FAILED;
return;
}
isLoading.value = true;
clearError();
try {
// 2. 初始化 Google Client
const oauth2 = window.google.accounts.oauth2.initCodeClient({
client_id: clientId,
scope: 'openid email profile',
ux_mode: 'popup',
redirect_uri: window.location.origin,
callback: handleOAuthCallback,
});
// 3. 觸發登入視窗
oauth2.requestCode();
} catch (error) {
handleLoginError(error);
}
}
// 輔助函式
function validateStore() {
if (!storeSelect.value) {
storeError.value = ERROR_MESSAGES.STORE_REQUIRED;
return false;
}
storeError.value = '';
return true;
}
function clearError() {
storeError.value = '';
}
function isGoogleServiceAvailable() {
return window.google?.accounts?.oauth2;
}
function isClientIdConfigured() {
return !!clientId;
}
async function handleOAuthCallback(response) {
isLoading.value = false;
if (response.error) {
storeError.value = ERROR_MESSAGES.LOGIN_FAILED;
return;
}
// 必須成功與後端驗證才能登入
try {
const backendResponse = await fetch('/api/auth/google', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
code: response.code,
store: storeSelect.value,
}),
});
if (backendResponse.ok) {
const { token, user } = await backendResponse.json();
// 後端驗證成功,保存 JWT token 和用戶資訊
localStorage.setItem('authToken', token);
if (user) {
localStorage.setItem('userInfo', JSON.stringify(user));
}
// 登入成功,導向到指定頁面
router.push('/dashboard');
} else {
const errorData = await backendResponse.json();
storeError.value = errorData.message || ERROR_MESSAGES.BACKEND_ERROR;
}
} catch (error) {
console.error('Backend authentication error:', error);
storeError.value = ERROR_MESSAGES.NETWORK_ERROR;
}
}
function handleLoginError(error) {
isLoading.value = false;
storeError.value = ERROR_MESSAGES.LOGIN_ERROR;
console.error('Google login error:', error);
}
在執行主要邏輯前,我們進行了三項重要的檢查:
validateStore()
:確保使用者已經選擇了門市。isClientIdConfigured()
:檢查 .env
檔案中的 Client ID 是否成功讀取。如果沒有,可能是環境變數設定有誤。isGoogleServiceAvailable()
:檢查從 index.html
引入的 Google GSI 函式庫是否已成功載入並掛載到 window
物件上。initCodeClient
)這是整個流程的核心。window.google.accounts.oauth2.initCodeClient
會建立並初始化一個用於「授權碼流程」的客戶端。我們傳入一個設定物件:
client_id
: 我們在上篇中取得並設定在環境變數中的用戶端 ID。scope
: 我們向 Google 請求的權限範圍。
openid
: 用於 OpenID Connect 流程,是驗證使用者身份的標準。email
: 請求存取使用者的電子郵件地址。profile
: 請求存取使用者的基本公開資料(姓名、頭像等)。ux_mode
: 使用者體驗模式。我們選擇 'popup'
,這會彈出一個新的登入視窗,體驗較佳。另一個選項是 'redirect'
,會將整個頁面重新導向到 Google。redirect_uri
: 重新導向 URI。雖然在 popup
模式下它不是那麼顯著,但仍是安全驗證的必要一環,必須與 Cloud Console 中的設定相符。我們使用 window.location.origin
來動態獲取當前的來源網址。callback
: 回呼函式。這是最關鍵的部分,當使用者在彈出視窗中完成登入與授權後,Google 會觸發這個函式。requestCode
)initCodeClient
只是做好了準備工作,oauth2.requestCode()
才是真正執行「向使用者請求授權碼」這個動作的命令。執行後,就會跳出我們熟悉的 Google 登入彈窗。
當使用者完成操作後,我們在 handleOAuthCallback
函式中處理回呼邏輯:
isLoading.value = false
: 無論成功或失敗,非同步操作已結束,我們首先要做的就是關閉 Loading 狀態。response.error
: Google 會透過 response
物件告訴我們結果。如果 response.error
存在,表示使用者拒絕授權或發生了其他錯誤。response.code
: 如果一切順利,response
物件中就會包含我們夢寐以求的「授權碼 (Authorization Code)」!網域限制:我們在 Google Cloud Console 中設定了網域限制,只允許 @XXX.com
結尾的 email 登入。這樣 Google 會在驗證階段就過濾掉不符合條件的用戶,確保只有授權的員工才能登入系統。
我們取得的 response.code
是「授權碼 (Authorization Code)」,這是 OAuth 流程的第一步。為了確保安全性,我們立即將授權碼送到後端進行驗證和交換。
在 handleOAuthCallback
函式中,我們實作了完整的安全流程:
@XXX.com
的用戶可以登入這樣的實作方式結合了 Google 的網域限制和 OAuth 2.0 的安全標準,既確保了安全性,又提供了良好的用戶體驗。
恭喜!我們已經成功地將 Google OAuth 登入整合到了我們的 Vue 應用中。透過上下兩篇的修煉,我們不僅學會了如何在 Google Cloud Console 進行繁瑣的設定,也深度理解了如何在 Vue 中呼叫 Google GSI 函式庫,並實作了完整的「授權碼」安全處理流程。
「Authの呼吸・壹之型」讓我們掌握了前端在 SSO 流程中的關鍵職責,並確保了整個登入流程的安全性。這已經為我們的 POS 系統打開了通往安全、便捷驗證的大門。
明日,Day 17:[Authの呼吸・參之型] 登入流程 - Token管理與安全性。心を燃やせ 🔥!