前言
當你要將 JWT 驗證整合至 React 前端,並搭配 Refresh Token 或整合 OAuth(如 Google 登入)流程,你需要考慮前端的存儲方式、token 的更新機制,以及第三方登入的流程銜接。
以下將分成三個部分講解:
一、React 前端整合 JWT(含 Refresh Token)
流程總覽:
使用者輸入帳密登入,發送 /login
請求。
伺服器簽發兩種 token:
- Access Token(短效,有效期如 15 分鐘)
- Refresh Token(長效,有效期如 7 天)
前端儲存 Access Token(如在記憶體),Refresh Token 建議儲存在 HttpOnly Cookie。
當 Access Token 過期時,自動用 Refresh Token 換取新 Access Token。
使用者主動登出時,Refresh Token 一併清除。
伺服器回傳範例(登入成功):
{ "accessToken": "xxxxx", "expiresIn": 900 }
|
並透過 Set-Cookie
傳送 HttpOnly 的 Refresh Token:
Set-Cookie: refreshToken=xxxxx; HttpOnly; Path=/; Max-Age=604800;
|
React 實作要點
import axios from 'axios';
export async function login(username, password) { const res = await axios.post( '/api/login', { username, password }, { withCredentials: true, } );
localStorage.setItem('accessToken', res.data.accessToken); }
|
import axios from 'axios';
const api = axios.create({ baseURL: '/api', withCredentials: true, });
api.interceptors.request.use((config) => { const token = localStorage.getItem('accessToken'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; });
api.interceptors.response.use( (res) => res, async (err) => { if (err.response.status === 401) { try { const res = await axios.post('/api/refresh', {}, { withCredentials: true }); localStorage.setItem('accessToken', res.data.accessToken);
err.config.headers.Authorization = `Bearer ${res.data.accessToken}`; return axios(err.config); } catch (e) { window.location.href = '/login'; return Promise.reject(e); } } return Promise.reject(err); } );
export default api;
|
登出
export async function logout() { await axios.post('/api/logout', {}, { withCredentials: true }); localStorage.removeItem('accessToken'); }
|
備註
- Refresh Token 儲存在瀏覽器的
HttpOnly Cookie
,無法被 JavaScript 存取,提升安全性。
- Access Token 儲存在記憶體或 localStorage(但 localStorage 易受 XSS 攻擊)。
- 若要完全防止 CSRF,Refresh Token cookie 需搭配 SameSite 設定與 CSRF token。
二、OAuth 2.0 登入(以 Google 為例)
流程總覽
- 前端點擊「使用 Google 登入」。
- 透過 Google OAuth 流程取得授權碼(或 id_token)。
- 前端將該 token 傳送到後端
/auth/google
。
- 後端驗證 Google id_token,並簽發 JWT 給前端。
React 前端整合(Google 登入)
使用 @react-oauth/google
:
npm install @react-oauth/google
|
import { GoogleOAuthProvider, GoogleLogin } from '@react-oauth/google';
function App() { return ( <GoogleOAuthProvider clientId="你的 Google Client ID"> <GoogleLogin onSuccess={(credentialResponse) => { // 將 id_token 傳送給後端 fetch('/api/auth/google', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ token: credentialResponse.credential }), }); }} onError={() => { console.log('登入失敗'); }} /> </GoogleOAuthProvider> ); }
|
後端處理(Node.js + google-auth-library
)
import { OAuth2Client } from 'google-auth-library'; import jwt from 'jsonwebtoken';
const client = new OAuth2Client(GOOGLE_CLIENT_ID);
router.post('/auth/google', async (req, res) => { const { token } = req.body;
const ticket = await client.verifyIdToken({ idToken: token, audience: GOOGLE_CLIENT_ID, });
const payload = ticket.getPayload();
const jwtToken = jwt.sign({ userId: payload.sub, name: payload.name }, process.env.JWT_SECRET, { expiresIn: '15m', });
const refreshToken = jwt.sign({ userId: payload.sub }, process.env.JWT_REFRESH_SECRET, { expiresIn: '7d', });
res .cookie('refreshToken', refreshToken, { httpOnly: true, maxAge: 7 * 24 * 60 * 60 * 1000, }) .json({ accessToken: jwtToken }); });
|
三、小結
功能 |
JWT + Refresh |
OAuth 2.0 |
是否需要帳密登入 |
是 |
否,透過第三方登入 |
Token 儲存 |
Access Token: localStorage / memory Refresh Token: HttpOnly Cookie |
同上 |
適合對象 |
自建會員系統 |
使用 Google / Facebook / LINE 等快速登入 |
安全性 |
良好,需搭配 HTTPS |
高,由 Google 等大廠管理 |
實作難度 |
中等,需處理 Token 刷新邏輯 |
中高,需處理外部驗證流程 |
延伸建議
- 若你想使用前後端共用的 JWT 驗證邏輯,建議抽出 middleware 並集中處理。
- 可搭配
jsonwebtoken
、axios-auth-refresh
等工具。
- 若前後端完全分離,建議使用跨網域 Cookie 搭配
SameSite=None; Secure
。
Comments