Session 與 Cookie 登入機制
使用者登入成功後,系統必須回答一個問題:下一個 HTTP 請求來的時候,伺服器怎麼知道這是同一個已登入使用者?Session 與 Cookie 登入機制就是最經典的答案。
一句話回答:Session 登入是 server-side authentication state:使用者登入成功後,伺服器建立一筆 session 資料,產生高熵 session id,透過 Set-Cookie 發給瀏覽器;之後瀏覽器自動帶 cookie,伺服器用 session id 查 session store 取得 userId、狀態、過期時間與風險資訊。Cookie 只是承載 session id 的傳輸機制,安全上要設定 HttpOnly、Secure、SameSite、合理 Path/Domain,並處理 CSRF、session fixation、登出撤銷與多裝置管理。
一、 必考觀念
1. Session 和 Cookie 不是同一件事
| 名詞 | 是什麼 | 放在哪裡 |
|---|---|---|
| Session | 伺服器端的登入狀態 | Redis / DB / memory store |
| Session ID | 指向 session 的隨機識別碼 | Cookie 裡或少數情況放 header |
| Cookie | 瀏覽器保存並自動帶上的小段資料 | Browser |
簡化理解:
Cookie 裡放 session id。
Session store 裡放真正的登入狀態。所以不要把敏感 user profile、role、permission 全塞進 cookie。Cookie 只要能讓 server 找回 session 就好。
2. HTTP 是無狀態,Session 補上狀態
HTTP 每個 request 本身是獨立的:
GET /me
GET /orders
POST /logout如果沒有 cookie 或 token,server 不知道這幾個 request 是否來自同一個使用者。
Session-based login 的流程是:
登入成功
-> server 建立 session
-> browser 收到 session cookie
-> 後續 request 自動帶 cookie
-> server 查 session
-> 認出使用者3. Session 是有狀態的設計
Session-based authentication 是 stateful:
server 必須保存 session 狀態。好處:
- 登出容易,刪 session 即可。
- 可立即撤銷單一裝置。
- 可保存裝置、IP、MFA、風險狀態。
- 可集中管理 session 生命週期。
代價:
- 需要 session store。
- 多台 server 要共享 session。
- Redis / DB 掛掉會影響登入狀態。
- 跨域與 cookie 設定要更謹慎。
延伸讀:HTTP 專題:Session 與 Token 的抉擇。
二、 登入流程
1. Sequence Diagram
2. Session 資料放什麼?
Session store 可以放:
type Session = {
id: string
userId: string
createdAt: string
expiresAt: string
lastSeenAt: string
lastAuthenticatedAt: string
mfaVerifiedAt?: string
ip?: string
userAgent?: string
revokedAt?: string
}避免放:
- 原始密碼。
- passwordHash。
- 大量 user profile。
- 敏感 token 明文。
- 不必要的個資。
Session 應該足以識別使用者與登入狀態,但不要變成另一個大資料庫。
3. Session ID 要高熵、不可預測
不好的 session id:
user_123
timestamp_123456
base64(userId)好的 session id:
function createSessionId() {
return crypto.randomBytes(32).toString('base64url')
}session id 是 bearer credential。誰拿到它,通常就能冒用該 session。
OWASP Session Management Cheat Sheet 強調 session identifier 應具備足夠熵,並且不應在其中暴露可推測或敏感資訊。
三、 Set-Cookie 設計
1. 基本 Set-Cookie
登入成功後:
Set-Cookie: __Host-sid=abc123; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=3600常見屬性:
| 屬性 | 作用 |
|---|---|
| HttpOnly | JavaScript 不能透過 document.cookie 讀取 |
| Secure | 只在 HTTPS 傳送 |
| SameSite | 控制 cross-site request 是否帶 cookie |
| Path | 限制 cookie 傳送路徑 |
| Domain | 限制 cookie 可用網域 |
| Max-Age / Expires | 控制 cookie 存活時間 |
MDN Set-Cookie 文件也說明了這些屬性的行為與瀏覽器限制。
2. HttpOnly
HttpOnly 讓前端 JavaScript 不能讀取 session cookie。
這能降低 XSS 後直接偷 cookie 的風險:
document.cookie但要注意:
- HttpOnly 不能防止 XSS 發出已帶 cookie 的請求。
- HttpOnly 不能取代 XSS 防護。
- 前端若需要知道登入狀態,應透過
/meAPI,而不是讀 cookie。
3. Secure
Secure 表示 cookie 只會透過 HTTPS 傳送。
正式環境 session cookie 應該設:
Secure如果沒有 Secure,使用者在不安全連線中可能洩漏 session cookie。
4. SameSite
SameSite 影響跨站請求是否帶 cookie。
| 值 | 說明 | 常見用途 |
|---|---|---|
| Strict | 只有 same-site request 會帶 | 安全較強,但可能影響外部連入 |
| Lax | 大多數 same-site 與部分 top-level navigation 會帶 | 常見預設選擇 |
| None | cross-site 也會帶,必須 Secure | 第三方嵌入或跨站 SSO 場景 |
對一般網站:
SameSite=Lax通常是安全與體驗的平衡。
對非常敏感的管理後台,可評估:
SameSite=Strict如果前後端不同 site、或需要第三方 iframe / SSO callback,就要更仔細設計。
5. __Host- cookie prefix
可以使用 cookie prefix 增加安全限制:
Set-Cookie: __Host-sid=...; Path=/; Secure; HttpOnly; SameSite=Lax__Host- cookie 需要:
Secure。Path=/。- 不能設定
Domain。
這有助於避免某些子網域覆蓋 cookie 的風險。OWASP Session Management Cheat Sheet 也建議使用安全的 cookie 屬性與 cookie prefix 強化 session cookie。
四、 Session Store
1. 不要用 memory store 上正式環境
單機開發可以用 memory store,但正式環境不適合:
- server 重啟 session 全失效。
- 多台 server 不共享。
- 記憶體容易膨脹。
- 無法集中撤銷。
正式環境常用:
- Redis。
- Database。
- Distributed cache。
2. Redis Session Store
概念:
sid -> session json
TTL = session expiresAt例如:
await redis.set(
`session:${sessionId}`,
JSON.stringify(session),
{
EX: 60 * 60,
},
)每次請求:
const session = await redis.get(`session:${sessionId}`)3. 多台 server
所有 server 共用 session store,使用者打到哪台都能查到 session。
Sticky session 可以用,但通常不如共享 session store 彈性好。
五、 Session 生命週期
1. Absolute timeout 與 idle timeout
常見兩種過期:
| 類型 | 說明 |
|---|---|
| Absolute timeout | 不管有沒有操作,最多活多久 |
| Idle timeout | 多久沒有活動就過期 |
例如:
一般會員:
idle timeout = 30 分鐘
absolute timeout = 7 天
管理員:
idle timeout = 15 分鐘
absolute timeout = 8 小時2. Sliding session
Sliding session 會在使用者活動時延長過期時間:
if (sessionShouldRefresh(session)) {
await sessionStore.extend(session.id, 30 * 60)
}好處:
- 活躍使用者不會突然登出。
風險:
- 如果 session 被盜,攻擊者也可能持續延長。
所以高風險系統要搭配:
- absolute timeout。
- 裝置風險檢查。
- 高風險操作 step-up。
3. lastAuthenticatedAt
登入狀態不等於近期驗證。
可以在 session 裡保存:
lastAuthenticatedAt: '2026-05-24T10:00:00Z'高風險操作要求:
lastAuthenticatedAt within 10 minutes例如:
- 修改密碼。
- 修改 email。
- 關閉 MFA。
- 下載敏感資料。
六、 CSRF 與 Cookie
1. 為什麼 Cookie 需要考慮 CSRF?
瀏覽器會自動帶 cookie。
如果使用者已登入 bank.example,攻擊者網站可能誘導瀏覽器送出:
POST https://bank.example/transfer
Cookie: sid=...這就是 CSRF 的核心問題:攻擊者不能讀 response,但可能讓使用者瀏覽器帶著 cookie 發出請求。
2. SameSite 可以降低風險,但不是唯一防線
SameSite=Lax 或 Strict 可以降低很多 CSRF 風險。
但 OWASP CSRF Prevention Cheat Sheet 也提醒,SameSite 是防禦深度,不應取代完整 CSRF 防護。某些同站子網域、瀏覽器差異或特殊流程仍需小心。
3. CSRF Token
對敏感操作可使用 CSRF token:
server 產生 csrf token
-> 前端在表單或 header 帶上 token
-> server 驗證 token 是否匹配 session例如:
POST /api/profile
Cookie: __Host-sid=...
X-CSRF-Token: csrf-tokenCSRF token 不應該只存在 cookie 裡然後完全不驗證。重點是攻擊者網站不能取得或構造正確 token。
七、 Session Fixation
1. 什麼是 Session Fixation?
Session fixation 是攻擊者讓受害者使用攻擊者已知的 session id。
例如:
攻擊者取得一個 session id
-> 誘導使用者用這個 session id 登入
-> 登入後 session id 不變
-> 攻擊者使用同一 session id 冒用2. 登入成功後要 rotate session id
防法:
登入前的 anonymous session
-> 登入成功
-> 產生新的 session id
-> 舊 session id 失效不要把未登入 session 直接升級成已登入 session 而不換 id。
await sessionStore.destroy(oldSessionId)
const newSession = await sessionStore.createAuthenticatedSession(user.id)OWASP Session Management Cheat Sheet 也建議在權限等級變化後更新 session id。
3. 權限升級也要考慮 rotate
例如:
- 完成 MFA。
- 切換成 admin mode。
- 完成 step-up authentication。
這些都可以視風險旋轉 session id 或更新 session 狀態。
八、 登出與撤銷
1. 登出要做兩件事
登出不是只清前端狀態。
要:
server 刪除或 revoke session
browser 清除 cookie回應:
Set-Cookie: __Host-sid=; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=02. 單裝置登出與全部裝置登出
單裝置登出:
revoke current session全部裝置登出:
revoke all sessions for user常見觸發:
- 使用者手動全部登出。
- 修改密碼。
- 忘記密碼重設成功。
- 帳號被停用。
- 可疑登入。
- MFA 被關閉。
延伸讀:忘記密碼流程設計。
3. Session revoke 欄位
可以 hard delete,也可以 soft revoke:
type Session = {
id: string
userId: string
expiresAt: string
revokedAt?: string
revokedReason?: 'logout' | 'password_reset' | 'admin_revoke' | 'risk'
}Soft revoke 有助於稽核與調查。
九、 多裝置管理
1. 使用者應該能看到裝置列表
可以顯示:
- 瀏覽器。
- 作業系統。
- 大略位置。
- 最近活動時間。
- 建立時間。
- 是否目前裝置。
不要顯示過度精細或敏感資訊。
2. 裝置列表其實是 session 列表
type UserSessionView = {
id: string
deviceName: string
browser: string
os: string
approximateLocation: string
createdAt: string
lastSeenAt: string
current: boolean
}使用者可以:
登出某台裝置
登出全部其他裝置3. 新裝置通知
新 session 建立時,如果是新裝置或高風險來源,可以通知:
你的帳號剛從新的裝置登入。
如果不是你本人,請立即重設密碼並登出其他裝置。這和帳號保護、異常登入偵測會互相配合。
十、 追問題庫
Q1:Session 和 Cookie 差在哪?
Session 是伺服器端保存的登入狀態;Cookie 是瀏覽器保存並自動帶回 server 的資料。常見做法是在 Cookie 裡放 session id,server 用 session id 查 session store。
Q2:Session-based login 為什麼容易登出?
因為登入狀態存在 server。登出時刪除或 revoke session,後續即使 browser 還帶舊 cookie,server 也查不到有效 session。
Q3:Cookie 設 HttpOnly 就安全了嗎?
不是。HttpOnly 只能避免 JavaScript 直接讀取 cookie,但 XSS 仍可能讓瀏覽器帶著 cookie 發 request。還是要做 XSS 防護、CSRF 防護、權限檢查和風險控制。
Q4:SameSite 能完全防 CSRF 嗎?
不能完全依賴。SameSite 可以降低很多 CSRF 風險,但敏感操作仍建議搭配 CSRF token、Origin / Referer 檢查等防護。
Q5:為什麼登入成功要換 session id?
避免 session fixation。登入前的 anonymous session id 若被攻擊者控制,登入成功後不換 id,攻擊者可能用同一 session id 冒用使用者。
Q6:Session 存 Redis 還是 DB?
Redis 適合高頻讀寫與 TTL,常見於 session store。DB 適合需要長期稽核、查詢與一致性要求的場景。也可以 Redis 存 active session,DB 存 audit 或歷史 session。
十一、 實作題
題目:設計一個 Session Cookie 登入
需求:
- 使用者帳密登入。
- 成功後建立 session。
- Cookie 設 HttpOnly、Secure、SameSite。
- 後續
/me可以取得使用者。 - 登出後 session 失效。
登入:
async function login(input: LoginInput, reply: Reply) {
const user = await authService.verifyPassword(input)
const session = await sessionService.create({
userId: user.id,
userAgent: input.userAgent,
ip: input.ip,
})
reply.setCookie('__Host-sid', session.id, {
httpOnly: true,
secure: true,
sameSite: 'lax',
path: '/',
maxAge: 60 * 60,
})
return {
user: toLoginProfile(user),
}
}驗證 middleware:
async function requireSession(request: Request) {
const sessionId = request.cookies['__Host-sid']
if (!sessionId) {
throw new UnauthorizedError()
}
const session = await sessionStore.get(sessionId)
if (!session || session.revokedAt || session.expiresAt < new Date()) {
throw new UnauthorizedError()
}
return session
}登出:
async function logout(request: Request, reply: Reply) {
const sessionId = request.cookies['__Host-sid']
if (sessionId) {
await sessionStore.revoke(sessionId, 'logout')
}
reply.setCookie('__Host-sid', '', {
httpOnly: true,
secure: true,
sameSite: 'lax',
path: '/',
maxAge: 0,
})
return {
status: 'ok',
}
}題目延伸:修改密碼後撤銷 session
async function afterPasswordChanged(userId: string, currentSessionId: string) {
await sessionStore.revokeAllExcept({
userId,
exceptSessionId: currentSessionId,
reason: 'password_reset',
})
}高風險系統也可以連目前 session 一起撤銷,要求重新登入。
十二、 資深視角
1. Session 是可控性強的設計
Session-based login 很適合:
- Web app。
- 後台系統。
- 需要即時登出。
- 需要多裝置管理。
- 需要風險狀態。
- 需要伺服器端撤銷。
它的可控性通常比純 stateless JWT 更好。
2. Cookie 安全不是單一 flag
成熟設計會一起看:
- HttpOnly。
- Secure。
- SameSite。
- Path / Domain。
- Cookie prefix。
- CSRF token。
- XSS 防護。
- HTTPS / HSTS。
- Session rotation。
- Session revocation。
只說「我把 token 放 HttpOnly Cookie」還不夠。
3. Session 設計要配合產品風險
不同帳號類型可以不同策略:
| 帳號 | Session 策略 |
|---|---|
| 一般會員 | 較長 remember me,可管理裝置 |
| 管理員 | 短 idle timeout,強制 MFA |
| 金流操作 | step-up authentication |
| 醫療資料 | 嚴格 timeout + audit |
| 政府服務 | 高信任身份與重新驗證 |
不要用同一套 session 規則套所有風險場景。
總結
| 問題 | 回答重點 |
|---|---|
| Session | server-side login state |
| Cookie | browser 自動帶回 server 的 session id 容器 |
| Session store | Redis / DB / distributed cache |
| Cookie flags | HttpOnly、Secure、SameSite、Path、Domain、Max-Age |
| CSRF | Cookie 自動帶上,所以要 SameSite + CSRF 防護 |
| Session fixation | 登入成功或權限提升後 rotate session id |
| 登出 | revoke server session + 清 browser cookie |
| 多裝置 | 每個裝置一個 session,可查看與撤銷 |
| 資深取捨 | 可控性強,但需要 session store 與跨域 cookie 設計 |
一句完整的面試回答可以是:
Session 與 Cookie 登入是有狀態的身份驗證方式。使用者帳密登入成功後,server 建立一筆 session,裡面保存 userId、expiresAt、lastSeenAt、mfaVerifiedAt 等登入狀態,並產生高熵 session id。Server 透過 Set-Cookie 把 session id 寫到瀏覽器,cookie 要設 HttpOnly、Secure、SameSite、合理 Path/Domain,最好使用
__Host-prefix。後續請求瀏覽器會自動帶 cookie,server 用 session id 去 Redis 或 DB 查 session。登出時要 revoke server session 並清 cookie;修改密碼、帳號停用或可疑登入時也要撤銷相關 session。因為 cookie 會自動帶上,所以要考慮 CSRF,SameSite 只是防禦之一,敏感操作仍可搭配 CSRF token。登入成功後要 rotate session id,避免 session fixation。Session 的優點是可即時撤銷與集中管理,代價是需要 server-side session store。