Sử dụng Axios interceptor để refresh token cho Next-Auth
Khá nhiều developers thích sủ dụng Next-Auth để authenticate cho dự án NextJs, và cũng không ít developers thích sự dụng Axios (một mình hoặc đi kèm thư viện như React-Query). Một ngày đẹp trời bạn nhận ra Next-Auth lưu data ở cookie vậy làm sao để hoạt động với thằng Axios nhỉ? Bài viết này mình sẽ hướng dẫn các bạn sử dụng cobo Next-Auth Axios (Tanstack React Query) để refresh token khi access token hết hạn.
Khi sử dụng Axios đi kèm với Next-Auth chúng ta cần xử lý được 2 phần: Đưa access token vào Header với request interceptor, refresh được token với response interceptor khi access token hết hạn. Next-Auth không hỗ trợ việc update session từ phía client mà ngoài componen nên đẹp nhất chúng ta sẽ tạo ra axios auth dưới dạng 1 hook.
Thiết lập ApiAuth Hook
Khởi tạo axios instance
Tạo file tại lib/api.ts
// lib/api.ts
import { API_URL } from "@/config/const";
import axios from "axios";
export const ApiAuth = axios.create({
baseURL: API_URL,
});
Giải thích: File này không có gì đáng nói các bạn nhớ linh hoạt API_URL nhé, đây là enpoint của backend
Api Auth Hook
Tạo file tại hooks/auth/useApiAuth.ts
// hooks/auth/useApiAuth.ts
import { useSession } from "next-auth/react";
import { useEffect } from "react";
import { useRefreshToken } from "./useRefreshToken";
import { ApiAuth } from "@/lib/Api";
const useApiAuth = () => {
const { data: session } = useSession();
const refreshToken = useRefreshToken();
useEffect(() => {
const requestIntercept = ApiAuth.interceptors.request.use(
(config) => {
if (!config.headers["Authorization"]) {
config.headers[
"Authorization"
] = `Bearer ${session?.tokens?.accessToken}`;
}
return config;
},
(error) => Promise.reject(error)
);
const responseIntercept = ApiAuth.interceptors.response.use(
(response) => response,
async (error) => {
const prevRequest = error?.config;
if (error?.response?.status === 401 && !prevRequest?.sent) {
prevRequest.sent = true;
await refreshToken();
prevRequest.headers[
"Authorization"
] = `Bearer ${session?.tokens.accessToken}`;
return ApiAuth(prevRequest);
}
return Promise.reject(error);
}
);
return () => {
ApiAuth.interceptors.request.eject(requestIntercept);
ApiAuth.interceptors.response.eject(responseIntercept);
};
}, [session, refreshToken]);
return ApiAuth;
};
export default useApiAuth;
Giải thích: Hook này thiết lập request interceptor cho axios với access token lấy từ Next-Auth session. Đối với response interceptor nếu nhận về response status 401 thì tiến hành refresh token và gọi lại request
Refresh Token Hook
Tạo file tại hooks/auth/useRefreshToken.ts
// hooks/auth/useRefreshToken.ts
import { Api } from "@/lib/Api";
import { signIn, useSession } from "next-auth/react";
export const useRefreshToken = () => {
const { data: session } = useSession();
const refreshToken = async () => {
// Gọi tới backend để lấy access token mới và trả về
const res = await Api.post("/auth/refresh-token", {
refreshToken: session?.tokens.refreshToken,
});
if (session) session.tokens.accessToken = res.data.tokens.accessToken;
else signIn();
};
return refreshToken;
};
Giải thích: useRefreshToken lấy thông tin refresh token trong Next-Auth session, gọi lên backend để lấy access token mới, cập nhật vào Next-Auth session, và trả về giá trị access token
Sử dụng
Thuần Axios
Lúc này useApiAuth() đã trở thành 1 hook các bạn có thể gọi trong component như bình thường. Ví dụ:
// component posts
import useAxiosAuth from "@lib/hooks/useAxiosAuth";
import { useSession } from "next-auth/react";
import { useState } from "react";
const Posts = () => {
const [posts, setPosts] = useState();
const fetchPost = async () => {
const res = await axiosAuth.get("/posts");
setPosts(res.data);
};
useEffect(() => {
fetchPost()
}, []}
return (
...
);
};
export default HomePage;
Với React Query
Tạo 1 hook kết hợp Api Auth và React Query
// hooks/post/useGetPost.ts
import { useMutation } from "@tanstack/react-query";
import useApiAuth from "@hooks/api/useApiAuth";
export const useGetPosts = () => {
const apiAuth = useApiAuth();
return useQuery({
queryKey: ["getPosts"],
queryFn: () => Api.get("/posts").then((res) => res.data),
});
};
Sử dụng trong component
// component posts
import useGetPosts from "@hooks/post/useGetPost";
const Posts = () => {
const { isLoading, data } = useGetPosts();
return (
...
);
};
All rights reserved