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 ( ... );
};