iT邦幫忙

2026 iThome 鐵人賽

DAY 13
0
JavaScript

從看不懂到做出來,用 PawPal 走過前端新手村系列 第 13

Day 13|面試被問到 JWT,我才發現自己沒有真的懂

  • 分享至 

  • xImage
  •  

今天的故事

在 PawPal 開發寵物功能時,我有參與 JWT 驗證相關功能的實作。

當時我知道 JWT 和登入有關,也知道 API 需要帶上 Token。

只要登入成功、Token 有保存,受保護的 API 能正常呼叫,我就會覺得這個功能已經完成了。

但那時候的我,其實還沒有真正理解整個流程。

我不知道 Token 是從哪裡產生的,也說不清楚:

  • 前端怎麼把 Token 傳給後端?
  • 後端在哪裡取得 Token?
  • Token 裡為什麼會有會員 ID?
  • 驗證成功後,會員 ID 又怎麼和寵物資料連在一起?
  • JWT 驗證通過,是不是就代表所有操作都有權限?

直到後來面試時,我被問到一個問題:

JWT 有沒有期限?

我當下知道 Token 好像可以設定期限,卻沒辦法把 PawPal 的實作完整說清楚。

那次經驗讓我發現:

功能曾經做過,不代表我真的理解它。

所以我重新回到 PawPal 的程式碼,把 JWT 從登入、傳送、驗證,到取得會員身分的流程重新走了一次。


面試當下,我到底卡在哪裡?

面試官問我「JWT 有沒有期限?」時,我第一個反應是:應該有,而且好像可以在產生 Token 時設定。

但如果繼續問下去,我當時其實回答不出來:

  • PawPal 的 Token 設定多久?
  • 期限是在哪裡設定的?
  • Token 過期後,後端怎麼知道?
  • 沒有設定期限,登入功能還能不能使用?
  • 驗證失敗時,API 會回傳什麼?

我曾經參與過相關功能,也看過 Token 出現在登入流程裡,但我的理解主要停留在「照著現有流程使用」。

當問題從「功能怎麼操作」變成「為什麼這樣設計」時,我才發現自己沒有真正把程式碼串起來理解。

這也是我後來重新打開 PawPal 程式碼的原因。


功能做出來了,我卻還沒有真正理解 JWT

一開始接觸 JWT 時,我只知道它像登入後取得的一張通行證。

登入成功後,後端會回傳 Token;前端把它保存起來,之後呼叫需要登入的 API 時再附上。

但當時我的理解大概停在:

登入成功
↓
取得 Token
↓
帶著 Token 呼叫 API

至於 Token 裡面有什麼、後端怎麼確認真假,以及驗證完成後怎麼知道目前是誰,我沒有真正拆開來看。

這也是為什麼面試官問到期限時,我雖然做過相關功能,卻沒有辦法有條理地回答。

重新查看程式碼後,我才發現 JWT 並不是只有「產生一串字串」。

它連接了登入、會員身分與 API 保護,也成為後續進行權限判斷時的重要依據。


登入成功後,為什麼還需要 Token?

使用者輸入帳號密碼並登入成功,只能證明那一次登入請求通過了。

接下來,使用者還會:

  • 查看自己的寵物
  • 新增寵物
  • 修改寵物資料
  • 刪除寵物
  • 查看會員資料
  • 修改個人資訊

HTTP 請求本身不會自動記得上一個請求是誰。

所以使用者每次呼叫受保護的 API 時,都需要帶上一個可以代表身分的資訊。

在 PawPal 裡,這個資訊就是 JWT。

可以先把它想成:

登入成功後取得的身分通行證

前端之後每次呼叫受保護的 API,都把這張通行證一起送給後端。

後端驗證通過後,才知道這次請求來自哪一位會員。


PawPal 怎麼產生 Token?

PawPal 在登入成功後,會使用 jwt.sign() 產生 Token。

下面是簡化後的概念:

const token = jwt.sign(
  {
    sub: user.id,
    email: user.email,
  },
  process.env.JWT_SECRET,
  {
    expiresIn: '7d',
  },
)

這裡有三個重要部分。

第一個是 Payload:

{
  sub: user.id,
  email: user.email,
}

sub 可以理解為這張 Token 代表的主體。

在 PawPal 裡,它保存的是會員 ID。

第二個是 Secret:

process.env.JWT_SECRET

它由後端保存,用來簽發及驗證 Token。

實際內容不能寫進前端、文章或 GitHub。

第三個是有效期限:

expiresIn: '7d'

PawPal 的 Token 設定為七天後到期。

如果 JWT 沒有設定期限,登入功能仍可正常運作,只是 Token 不會因為時間到了而自動失效。


前端怎麼把 Token 傳給後端?

取得 Token 後,前端在呼叫受保護的 API 時,需要把它放進 Request Header。

常見格式是:

Authorization: Bearer <token>

下面是簡化概念:

const headers = {
  Authorization: `Bearer ${token}`,
}

接著呼叫 API:

await axios.get('/api/v1/pets', {
  headers,
})

Bearer 可以先理解為一種 Authorization Header 的格式。

它告訴後端:

這次請求帶了一個代表使用者身分的 Token。

但前端有附上 Token,不代表後端就會直接相信它。

後端仍然必須驗證這張 Token 是否真的由系統簽發,以及是否仍在有效期限內。


Middleware 就像 API 前面的檢查站

PawPal 使用 Express Middleware 處理 JWT 驗證。

Middleware 可以先想成 API 前面的檢查站。

請求進入真正的功能以前,會先經過驗證:

前端送出 Request
↓
JWT Middleware
↓
驗證 Token
↓
驗證成功才進入 Controller

下面是簡化後的概念:

const payload = jwt.verify(token, process.env.JWT_SECRET)

req.userId = Number(payload.sub)

next()

jwt.verify() 會確認:

  • Token 的簽章是否能通過目前後端使用的 Secret 驗證
  • Token 是否仍在有效期限內

驗證失敗或 Token 已過期時,後端會回傳 401。

驗證成功後,Middleware 會再取得 payload.sub,確認會員 ID 後放進:

req.userId

最後呼叫:

next()

讓請求繼續進入下一個 Middleware 或 Controller。


PawPal 如何從 Token 取得會員 ID?

Token 產生時,PawPal 已經把會員 ID 放進 sub

{
  sub: user.id,
}

Middleware 驗證成功後,就能讀取:

payload.sub

再將它轉成會員 ID:

req.userId = Number(payload.sub)

後面的 Controller 或 Service 就不需要再請前端傳一次會員 ID。

整個流程可以整理成:

登入成功
↓
Token 的 sub 保存會員 ID
↓
前端傳送 Bearer Token
↓
Middleware 驗證 Token
↓
讀取 payload.sub
↓
寫入 req.userId
↓
後端知道目前會員是誰

這時我才真正理解,JWT 不只是判斷「有沒有登入」。

它還讓後端能從已驗證的 Token 中取得目前會員身分。


知道我是誰之後,還有下一個問題

JWT 驗證成功後,後端已經知道目前登入的會員是誰。

但這時還有另一個問題:

知道你是誰,不代表你可以操作所有資料。

JWT 主要處理的是 Authentication,也就是身分驗證。

至於修改或刪除資料時,後端如何確認這筆資料真的屬於目前會員,則屬於 Authorization,也就是權限判斷。

這部分會留到 Day 14 繼續整理。


從做出來,到真正理解

重新整理 JWT 流程後,我終於比較能回答面試官當時問的問題。

JWT 可以設定期限。

在 PawPal 裡,Token 的期限是七天:

expiresIn: '7d'

在 PawPal 目前的流程中,期限到了之後,後端使用 jwt.verify() 驗證時就會失敗並回傳 401;要繼續使用受保護功能,就需要重新取得有效的 Token。

我也開始能把完整流程說清楚:

登入成功
↓
後端簽發 JWT
↓
前端保存並傳送 Token
↓
Middleware 驗證 Token
↓
從 payload.sub 取得會員 ID
↓
保存到 req.userId
↓
後端知道目前會員是誰

這次最大的收穫,不是多記住一個 jwt.verify()

而是我開始知道,面對曾經做過的功能,不能只停在「它可以動」。

我需要能說明:

  • 為什麼需要它
  • 資料怎麼流動
  • 每一層負責什麼
  • 程式碼為什麼這樣寫

如果現在再被問一次,我會怎麼回答?

如果現在面試官再問我「JWT 有沒有期限?」,我會回答:

JWT 可以設定有效期限,但不是每一個 JWT 都一定會自動有期限。期限通常在後端使用 jwt.sign() 產生 Token 時設定。PawPal 使用 expiresIn: '7d',所以 Token 七天後會失效。後端使用 jwt.verify() 驗證時,如果 Token 已經過期,就會驗證失敗並回傳 401。

我也會補充,沒有設定期限的 Token 仍可正常產生與使用,只是不會因為時間經過而自動失效。

以前我只能回答「JWT 跟登入有關」。

現在我至少可以從產生、傳送、驗證到過期,把整個流程說清楚。

這也是這次重新整理程式碼後,對我最實際的幫助。


今天的學習整理

這次重新理解 JWT,我整理出六個重點:

  1. 登入成功後,後端仍需要 Token 識別後續請求的會員身分。
  2. PawPal 將會員 ID 放在 JWT 的 sub,並設定七天期限。
  3. 前端透過 Authorization Header 傳送 Bearer Token。
  4. Middleware 使用 jwt.verify() 驗證,並把會員 ID 放進 req.userId
  5. JWT 使用簽章確認內容是否可信,但 Payload 並不是加密空間,不應放入密碼或秘密資料。
  6. JWT 處理的是 Authentication;知道會員是誰之後,仍要另外進行 Authorization 權限判斷。

以前的我會說:

JWT 就是登入後拿到的 Token。

現在我會說:

JWT 讓後端驗證這次請求的身分,並從 Token 裡取得目前會員的 ID,讓後續 API 知道正在處理誰的請求。

這兩句話的差別,就是我從「做過功能」走到「開始真正理解功能」的過程。


下一篇預告

JWT 驗證成功後,後端已經知道目前登入的人是誰。

但登入成功,不代表可以修改或刪除所有人的資料。

Day 14,我會繼續整理身分驗證與權限控管的差別,以及 PawPal 如何限制會員只能操作屬於自己的資料。

下一篇:

Day 14|權限控管:不是每個人都能做每件事。


上一篇
Day 12|為什麼登入後還要知道「我是誰」?
下一篇
Day 14|權限控管:不是每個人都能做每件事。
系列文
從看不懂到做出來,用 PawPal 走過前端新手村14
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言