javascript - กระบวนการยืนยันตัวตน JWT - ส่วนของฝั่งหน้า
ฉันกำลังสร้างกระบวนการอนุญาตผู้ใช้ด้วยโทเค็น JWT
ลำดับการไหลดูเป็นอย่างไร?
- ผู้ใช้เข้าสู่ระบบ - ได้รับโทเค็นการเข้าถึงและโทเค็นการรีเฟรชจากเซิร์ฟเวอร์ เป็นการตอบสนอง
- โทเค็นการเข้าถึงมาในรูปแบบ JSON body และถูกบันทึกใน local storage โทเค็นรีเฟรชมาในคุกกี้แบบ httpOnly
- ผู้ใช้สามารถใช้เมธอด
getAllUsersจนกว่าจะหมดอายุของ access token - เมื่อใดก็ตามที่เมธอด
getAllUsersคืนค่า401 unauthorized(เมื่อ access token หมดอายุ) จะมีการส่งคำขอไปยัง endpoint สำหรับ refresh token -getRefreshTokenซึ่งจะคืนค่า access token ใหม่ที่ถูกบันทึกไว้ใน local storage - โทเค็นรีเฟรชหมดอายุและผู้ใช้กำลังถูกออกจากระบบ
กระบวนการทั้งหมดใน Postman ใช้งานได้ แต่ฉันมีปัญหาที่ฝั่งหน้าเว็บไซต์
ฟังก์ชัน getAllUsers ทำงานได้จนกว่า access token จะหมดอายุ
นั่นคือเหตุผลที่ฉันสร้างฟังก์ชันระดับโลกในไฟล์ยูทิลิตี้ที่ตรวจสอบว่าการตอบสนองเป็น 401 หรือไม่ และถ้าใช่ มันจะส่งคำขอเพื่อรับ access token ใหม่และเรียกฟังก์ชันที่ส่งกลับข้อผิดพลาดนั้น อย่างไรก็ตาม มันไม่ทำงาน
ฉันคิดว่าปัญหาอยู่ในฟังก์ชัน getAllUsers ซึ่งจะเข้าสู่บล็อก catch ทันที (เมื่อไม่สามารถดึงรายชื่อผู้ใช้ได้เนื่องจาก 401) และไม่เรียกใช้ฟังก์ชัน global จากไฟล์ util นั้น การล็อกในคอนโซลจากทั้งสองฟังก์ชัน (getDataFromResponse, getRefreshToken) ไม่ทำงาน ดังนั้นมันจึงยังไม่ไปถึงตรงนั้น
มีไอเดียอะไรไหม??
ไฟล์ยูทิลิตี้ API
import { AxiosResponse } from "axios";
import { apiService } from "./api.service";
type ApiServiceMethods = keyof typeof apiService;
export const getDataFromResponse = async (
response: AxiosResponse,
funName: ApiServiceMethods,
...args: any
): Promise<any> => {
if (response.status === 401) {
console.log("error");
await apiService.getRefreshToken();
return await apiService[funName](args);
}
return response.data;
};
บริการ API:
import { getDataFromResponse } from "./api.utils";
import axios from "./axios";
type LoginArgs = {
password: string;
username: string;
};
const apiServiceDef = () => {
const login = async (args: LoginArgs) => {
try {
const response = await axios.post("/login", {
username: args.username,
password: args.password,
});
const { data } = response;
const { token } = data;
localStorage.setItem("accessToken", token);
return response;
} catch (e) {
throw new Error("Custom");
}
};
/* problem here */
const getAllUsers = async () => {
const Token = localStorage.getItem("accessToken");
try {
const response = await axios.get("/users", {
headers: {
Token,
},
});
return await getDataFromResponse(response, "getAllUsers");
} catch (e) {
console.log(e);
}
};
/* problem here */
const getRefreshToken = async () => {
try {
console.log("fetch new access token");
const response = await axios.get("/refreshToken");
if (response.status === 401) {
localStorage.removeItem("accessToken");
throw new Error("TokenExpiredError");
}
const { data } = response;
const { token } = data
localStorage.setItem("accessToken", token);
return response;
} catch (e) {
console.log(e);
}
};
return { login, getRefreshToken, getAllUsers };
};
export const apiService = apiServiceDef();
คำตอบ #1
ฉันมักจะใช้ตัวห่อรอบฟังก์ชันแบบอะซิงค์หรือแค่ใช้ตัวสกัดของ axios (https://stackoverflow.com/a/47216863/11787903) แน่ใจว่า err.response.status เป็นคุณสมบัติที่ถูกต้อง ไม่แน่ใจเรื่องนั้น แต่แนวทางนี้ควรใช้ได้สำหรับคุณ
const asyncWrapper = async (handler) => {
try {
return handler()
} catch (err) {
if (err.response.status === 401) {
// refresh token then again call handler
await refreshToken()
return handler()
}
}
}
const getAllUsers = asyncWrapper(() => {
const Token = localStorage.getItem("accessToken");
return axios.get("/users", {
headers: {
Token,
},
});
});
