iT邦幫忙

2026 iThome 鐵人賽

DAY 16
0
Vibe Coding

夢幻甜品師闖工程世界:Vibe Coding vs 專業開發的 0→1 冒險攻略系列 第 16

Day16|拿到鑰匙就算會開門了嗎?從 Google Login 看懂 Access Token 與 ID Token

  • 分享至 

  • xImage
  •  

昨天聊到 TDD,測試讓我開始不只在意「功能有沒有跑起來」,也更在意:它到底是不是照我想要的方式在運作。

今天就來講一個我對這件事最有感的功能——

Google 登入。

Google 登入是我在 BuJo 裡第一個做的全端功能,而它前前後後居然換了三種做法!

一開始我以為這件事很單純:

使用者按下 Google 登入,Google 幫忙確認他是誰,BuJo 把資料拿回來,登入完成。

後來它一路從 Access Token、One Tap,最後又改成 Backend OAuth Redirect,我也以為只是:

同一個功能,換了三種作法。

但真的把每一次改動拆開研究,我才發現根本不是這麼回事。

有的問題在問:

Google 怎麼證明你是誰?

瀏覽器在登入裡扮演什麼角色?

Google 已經認出你之後,BuJo 又怎麼繼續記得你?

看起來只是一顆「使用 Google 登入」的按鈕,背後其實疊了好幾層不同的概念。

所以接下來三天,我想從這個自己真的改過三次的功能開始,一層一層把登入拆開來看。

第一天,就先從我最早搞不清楚的一把「鑰匙」開始。


第一版:我只知道 BuJo 跟 Google 之間需要一把鑰匙

第一次研究 Google Login 時,我對整條流程的理解其實很模糊。

大概只知道:

BuJo 跟 Google 之間,需要有一個像鑰匙一樣的東西做認證,才能去 Google 那裡把使用者資料拿回來。

當時第一版流程大概是這樣:

BuJo 自己的 Google 登入按鈕
↓
googleTokenLogin()
↓
Google OAuth 流程
↓
Frontend 拿到 Google Access Token
↓
把 Access Token 傳給 BuJo Backend
↓
Backend 拿同一顆 Access Token 去問 Google UserInfo
↓
Google 回傳使用者資料
↓
BuJo 找到或建立 User
↓
BuJo 發自己的 JWT
↓
Frontend 存進 localStorage

這一版是真的可以登入。

Backend 會拿 Frontend 傳來的 Google Access Token,用 Bearer Token 的方式呼叫 Google UserInfo,再根據回來的 sub、email 等資料建立或找到 BuJo 使用者,最後才由 BuJo 自己簽一顆 JWT。

第一次整條流程真的跑起來時,我只覺得很神奇。

明明只是按一顆按鈕,中間卻穿過 Google、Frontend、Backend、Database,最後又回到自己的網站。

但那時候如果真的問我:

Access Token 到底是什麼?

我其實還是答不太出來。

腦袋裡一直有一個問題:

「所以它是一把鑰匙嗎?」

Google 給 Frontend 的鑰匙,和 Frontend 再送去 Backend 的鑰匙,是同一把嗎?

第一版裡,答案其實是:對,就是同一顆 Access Token。

Google 發給 Frontend,Frontend 再把它交給 BuJo Backend,Backend 最後拿它去存取 Google 的 UserInfo API。

所以「拿著一把鑰匙一路往下開門」這個想像,並不是完全錯。

真正少掉的是另一個問題:

這把鑰匙,原本到底是拿來開哪一扇門的?


功能明明沒壞,我為什麼又回頭查?

我真正開始重新研究登入流程,不是因為 Google Login 壞掉,而是因為當時 BuJo 會把登入後拿到的 Token 存進 localStorage

我一直在想:

「localStorage 是不是一個 JavaScript 可以直接讀得到的地方?那 Token 放在這裡真的安全嗎?」

原本只是想查 Token 到底應該放在哪裡,結果一路往回追,問題越長越多:

  • 這顆 Token 到底是誰發的?
  • Google Token 跟 BuJo Token 是同一顆嗎?
  • Google 登入到底應該拿 Access Token 還是 ID Token?
  • OAuth 到底是在做登入,還是在做授權?

然後我第一次真的搞清楚:

Access Token 跟 ID Token 根本不是同一種用途。

我的第一個反應其實是:

「蛤?那我之前用錯了?」

更奇怪的是:

「那為什麼之前還可以登入?」

這也是我第一次很明顯地感覺到:

程式可以正常運作,和我選的是不是最適合的做法,是兩件完全不同的事。


Authentication 跟 Authorization,從第一步就不是同一件事

要理解 Access Token 和 ID Token,得先把兩個常常黏在一起出現的字拆開。

Authentication:身分驗證,你是誰?

Authentication,就是身分驗證

它處理的是:

「你真的是你說的那個人嗎?」

例如 Google 告訴 BuJo:

這個 Google 帳號對應的是某個特定使用者。

這是在確認身分。

Authorization:授權,你可以做什麼?

Authorization,就是授權

它問的是:

「你被允許做什麼?」

例如 BuJo 想取得:

  • Google Calendar
  • Google Drive
  • 某些 Google API 資源

這些就是權限問題。

所以可以先很粗略地記:

Authentication
= 身分驗證
= 你是誰?

Authorization
= 授權
= 你可以做什麼?

而這個差異,剛好也是我一開始會把 Access Token 和 ID Token 混在一起的原因。


OAuth 2.0 跟 OIDC 又是什麼?

這裡又是一組我以前會直接統稱成「Google OAuth 登入」的東西。

OAuth 2.0:先處理授權

OAuth 2.0 本質上是一套授權框架(Authorization Framework),用來規範:

使用者怎麼授權某個服務,去存取另一個服務裡的資源。

它最核心的問題不是:

這個人是誰?

而是:

這個 App 被允許存取哪些資源?

OpenID Connect:在 OAuth 2.0 上補上身分

OpenID Connect,簡稱 OIDC。

它建立在 OAuth 2.0 之上,再加入身分驗證的規範,用來回答:

「登入的這個人到底是誰?」

而在 OIDC 裡,最核心的身分憑證之一,就是後面會看到的 ID Token

所以如果把兩個拆開:

OAuth 2.0
→ 授權(Authorization)

OIDC
→ 身分驗證(Authentication)

明明平常都只說一句「Google Login」,底下其實同時踩著授權和身分驗證兩層。


原來 Token 也不是只有一種

我以前對 Token 的理解很接近:

Token = 鑰匙。

後來才發現,這個比喻不是完全錯,只是鑰匙也分很多種。

在 OAuth / OIDC 流程裡,常常會看到:

Authorization Code
Access Token
ID Token
Refresh Token

看起來全部都是一串讓人眼花撩亂的東西,但用途不一樣。

Authorization Code:授權碼

它比較像一張短暫的兌換券

它不是拿來直接存取資料,而是用來到 Token Endpoint 交換 Token。

Access Token:存取權杖

它比較像:

API 的通行證。

例如:

拿著 Access Token
↓
去 Google API
↓
取得這顆 Token 被允許存取的資料

ID Token:身分權杖

它比較像:

Google 簽名的身分證明。

裡面會帶著足以讓 Backend 驗證身分的資訊,例如:

這個使用者是誰
這顆 Token 是誰發的
是發給哪一個 App
什麼時候過期

它的重點不是 「你可以存取什麼」 ,而是 「這個使用者是誰」

Refresh Token:更新權杖

當 Access Token 到期時,可以在適合的流程裡拿它去取得新的 Access Token,而不用每次重新要求使用者授權。


那第一版為什麼明明用 Access Token,還是可以登入?

這正是我當時最困惑的地方。

因為第一版不是直接把 Access Token 當成「身分證」。

它實際上走的是這條路:

Access Token
↓
Backend 拿去呼叫 Google UserInfo
↓
Google UserInfo 回傳使用者資料
↓
BuJo 用這些資料辨識使用者

所以它確實可以正常完成登入。

只是如果我的目的本來就是:

確認現在登入的這個人是誰

那 OIDC 裡的 ID Token 會是更直接的身分憑證。

因為它本來就是拿來表達使用者身分的,Backend 可以直接驗證這顆 Token 是誰發的、是不是發給 BuJo,以及有沒有過期,不需要再先拿 Access Token 去查一次 UserInfo。

這也是後來 BuJo 第二版改成:

googleOneTap()
↓
Google ID Token
↓
BuJo Backend
↓
verifyIdToken()

而不是再走原本 Access Token → UserInfo 的路線。


ID Token 到底驗什麼?

ID Token 裡有一組資訊欄位,通常稱為 Claims(聲明資訊)

不用一次把全部背起來,先看幾個最常碰到的:

sub
aud
iss
exp

sub:這個使用者是誰

sub使用者的唯一識別碼(Subject Identifier)

可以把它想成身分提供者給這個使用者的一個固定識別。

iss:這顆 Token 是誰發的

iss 代表發行者(Issuer)

Backend 可以藉此確認這顆 Token 是不是由預期的身分提供者發出。

exp:這顆 Token 還有效嗎

exp 代表到期時間(Expiration Time)

過期的身分憑證不能一直被接受。

aud:這顆 Token 是發給誰的

aud 代表這顆 Token 預期要給誰使用(Audience)

Backend 驗證 ID Token 時,不只要知道:

這顆 Token 真的是 Google 發的

還要確認:

這顆 Token 真的是發給 BuJo 這個 Client 的

也就是說,「是真的」還不夠。

還要確認它跟我的 App 有正確的關係。

這一層是我第一版完全沒有想到的。


Google 驗證完身分後,為什麼 BuJo 還要再發 JWT?

回頭看第一版流程,其實 Google 驗證完使用者之後,BuJo 還會再發一顆自己的 JWT。

這時候就很容易混亂:

Google 的 ID Token 跟 BuJo 的 JWT,到底是不是同一種東西?

不是。

它們都是 JWT 格式的 Token,但發行者和用途完全不同:

Google ID Token
→ Google 發
→ 證明 Google 身分

BuJo JWT
→ BuJo 發
→ 讓 BuJo 後續辨識自己的使用者

所以 JWT 比較像是一種 Token 可以採用的資料格式,不是一種登入方式。

這一層先記住就好,Day18 再接著看:
BuJo 發完自己的 JWT 之後,到底把它放去哪裡?


我後來才發現,查證不能只沿著一條路一直走

這次還有一個對我很有影響的地方。

當時我其實不是完全沒有查資料。

我有很認真地問 AI:

這樣做對嗎?

這樣符合業界規範嗎?

這個資訊正確嗎?

問題是,我的查證方式比較像:

找到一條路
↓
確認這條路能不能走
↓
可以
↓
繼續往前

現在回頭看,我少問的是:

這裡總共有哪幾條路?
↓
每一條的用途和限制是什麼?
↓
安全性差在哪?
↓
各自適合什麼情境?
↓
我再選

也就是從:

線性查證

變成:

發散式比較。

第一版用 Access Token 不是「功能一定不會成功」。

真正讓我改變想法的是:

「這條路走得通」跟「這是不是最符合目前需求的做法」,其實是兩件不同的事。

中間還少了一步:

把其他可行方案攤開來比較,再理解每種做法的用途、限制和取捨。

所以現在如果再跟 AI 討論同類型的技術問題,我不會只說:

「請教我一個符合業界規範的方法。」

我會再多問一層:

「還有哪些常見做法?請把用途、限制、安全性和適用情境攤開,再讓我判斷。」

對我來說,這才是第一次真正做完整功能之後,慢慢長出來的工程思考。


改成 ID Token,事情就結束了嗎?

改成 ID Token 的時候,我真的有一種:

「好,這次應該就是正確的了!不會再有問題了吧?」

結果下一個 Bug 更奇怪。

按下 Google Login。

什麼都沒發生。

更麻煩的是——

它連一個明顯的 Error 都沒有。

明天就來看,當身分驗證改成 ID Token 之後,問題卻又跑到 Browser 身上時,這個 Google Login 又發生了什麼。


上一篇
Day15|Boss 還沒登場,先把通關條件寫好?從 TDD 看懂 Red-Green-Refactor
下一篇
Day17|明明路都走對了,為什麼還是卡關?從瀏覽器 Bug 看懂 Google 登入怎麼改走另一條路
系列文
夢幻甜品師闖工程世界:Vibe Coding vs 專業開發的 0→1 冒險攻略19
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言