From 98fcaeba3fbe16e6fd33f385075b22a4ff5d6ead Mon Sep 17 00:00:00 2001 From: Flook Date: Sun, 16 Nov 2025 07:41:57 +0700 Subject: [PATCH] =?UTF-8?q?=E0=B8=9F=E0=B8=B1=E0=B8=87=E0=B8=81=E0=B9=8C?= =?UTF-8?q?=E0=B8=8A=E0=B8=B1=E0=B8=99=20=E0=B8=88=E0=B8=94=E0=B8=88?= =?UTF-8?q?=E0=B8=B3=E0=B8=89=E0=B8=B1=E0=B8=99=20=E0=B8=94=E0=B9=89?= =?UTF-8?q?=E0=B8=A7=E0=B8=A2=20JWT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/LoginForm.jsx | 2 +- web/src/services/authApi.js | 32 +++++++++++++++++++++++++++++++- web/src/services/axiosClient.js | 32 +++++++++++++++++++++++++++++++- 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/web/src/components/LoginForm.jsx b/web/src/components/LoginForm.jsx index f8fa111..6119ad2 100644 --- a/web/src/components/LoginForm.jsx +++ b/web/src/components/LoginForm.jsx @@ -102,7 +102,7 @@ export default function LoginForm() { {/* Checkbox และ Link ลืมรหัสผ่าน */}
diff --git a/web/src/services/authApi.js b/web/src/services/authApi.js index 7699627..eae8985 100644 --- a/web/src/services/authApi.js +++ b/web/src/services/authApi.js @@ -15,8 +15,18 @@ const API_BASE_URL = import.meta.env.VITE_API_BASE_URL; * แปลง Object ให้เป็นฟอร์มข้อมูล (x-www-form-urlencoded) */ const toFormUrlEncoded = (data) => { + // วนซ้ำ key และ encode return Object.keys(data) - .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(data[key])) + .map(key => { + let value = data[key]; + // รับค่า remember_me (boolean) มาแปลงเป็น String 'true' + if (value === true) { + value = 'true'; + } else if (value === false) { + value = 'false'; // หรือจะส่งเฉพาะเมื่อเป็น true ก็ได้ + } + return encodeURIComponent(key) + '=' + encodeURIComponent(value); + }) .join('&'); }; @@ -121,6 +131,8 @@ export const useLoginMutation = () => { // 1. จัดการ Token และ User Data localStorage.setItem('token', data.access_token); + // บันทึก Refresh Token แยกต่างหาก + localStorage.setItem('refresh_token', data.refresh_token); localStorage.setItem('user', JSON.stringify(data.user)); localStorage.setItem('role', data.role); // บันทึก Role ที่ถูกต้อง @@ -250,4 +262,22 @@ export const useConfirmPasswordReset = () => { throw new Error(msg); }, }); +}; + +/** + * ฟังก์ชันสำหรับเรียก API เพื่อต่ออายุ Access Token ด้วย Refresh Token + */ +export const refreshAccessToken = async () => { + const refresh = localStorage.getItem('refresh_token'); // เก็บ Refresh Token แยกต่างหาก + + if (!refresh) { + throw new Error("Refresh token not found."); + } + + const response = await axios.post(`${API_BASE_URL}/api/v1/auth/jwt/refresh/`, { + refresh: refresh, + }); + + // คืนค่า Access Token ใหม่ + return response.data.access; }; \ No newline at end of file diff --git a/web/src/services/axiosClient.js b/web/src/services/axiosClient.js index 6612219..907552d 100644 --- a/web/src/services/axiosClient.js +++ b/web/src/services/axiosClient.js @@ -1,6 +1,7 @@ import axios from 'axios'; import { getStore } from '../app/store'; import { logout } from '../features/auth/authSlice'; +import { refreshAccessToken } from './authApi'; // REGEX ตรวจจับตัวเลขที่ใหญ่เกิน 15 หลัก (ซึ่งเกินขีดจำกัดความปลอดภัยของ JS) const bigIntRegex = /"id":(\d{15,})/g; @@ -55,8 +56,37 @@ axiosClient.interceptors.request.use( axiosClient.interceptors.response.use( (response) => response, - (error) => { + async (error) => { const status = error.response ? error.response.status : null; + const originalRequest = error.config; + + // ถ้าเป็น 401 และ Request นั้นยังไม่ถูกลอง Refresh + if (status === 401 && !originalRequest._retry) { + + originalRequest._retry = true; // ตั้ง Flag ว่ากำลังจะลอง Refresh + + const storeInstance = getStore(); + + try { + // 1. เรียกฟังก์ชัน Refresh Token + const newAccessToken = await refreshAccessToken(); + + // 2. บันทึก Access Token ใหม่ + localStorage.setItem('token', newAccessToken); + + // 3. อัปเดต Header ของ Request เดิม + originalRequest.headers.Authorization = `Bearer ${newAccessToken}`; + + // 4. ลองเรียก Request เดิมซ้ำอีกครั้ง + return axiosClient(originalRequest); + + } catch (refreshError) { + // หาก Refresh Token ล้มเหลว (หมดอายุ 30 วันแล้ว) + console.error("JWT Refresh Failed. Logging out.", refreshError); + storeInstance.dispatch(logout()); // บังคับ Logout + return Promise.reject(error); + } + } // ถ้าได้รับ 401 หรือ 403 (Token หมดอายุ/ไม่มีสิทธิ์) if (status === 401 || status === 403) {