Handling Token Expiration in RTK Query: A Beginner's Guide
Handling Token Expiration with RTK Query: A Seamless Solution
In the world of modern web development, handling token expiration is a common yet crucial task. Tokens, especially JSON Web Tokens (JWTs), are widely used to secure API endpoints. However, these tokens have a limited lifespan and need periodic refreshing. In this blog post, we'll explore how to manage token expiration using Redux Toolkit Query (RTK Query) in a React application. By the end of this guide, you'll have a seamless solution for handling token refresh, ensuring your app remains secure and user-friendly.
Prerequisites
Before we dive into the code, ensure you have the following set up:
Redux Toolkit and RTK Query: Make sure Redux Toolkit is installed in your project.
API with Refresh Token Endpoint: Your backend should have an endpoint to refresh tokens.
Step 1: Setting Up the Base Query
We start by creating a base query using fetchBaseQuery
provided by RTK Query. This base query will be the foundation for all our API requests and include the necessary authorization headers.
import {
BaseQueryFn,
createApi,
fetchBaseQuery,
} from "@reduxjs/toolkit/query/react";
import { RootState } from "../store";
import { logout, setUser } from "../features/auth/authSlice";
const baseQuery = fetchBaseQuery({
baseUrl: "http://localhost:5000/api/v1",
credentials: "include",
prepareHeaders: (headers, { getState }) => {
const token = (getState() as RootState).auth.token;
if (token) {
headers.set("authorization", `Bearer ${token}`);
}
return headers;
},
});
In the prepareHeaders
function, we add the authorization header if a token exists in the Redux state, making our API requests secure.
Step 2: Handling Token Expiration
Next, we create a baseQueryWithRefreshToken
function. This function wraps around our base query to handle token expiration and refresh the token if necessary.
const baseQueryWithRefreshToken: BaseQueryFn = async (
args,
api,
extraOptions
) => {
let result = await baseQuery(args, api, extraOptions);
console.log(result);
if (result?.error?.status === 401) {
console.log("Token expired, attempting to refresh...");
const res = await fetch("http://localhost:5000/api/v1/auth/refresh-token", {
method: "POST",
credentials: "include",
});
const data = await res.json();
console.log(data?.data?.accessToken);
if (data?.data?.accessToken) {
const user = (api.getState() as RootState).auth.user;
api.dispatch(
setUser({
user,
token: data.data.accessToken,
})
);
result = await baseQuery(args, api, extraOptions);
} else {
api.dispatch(logout());
}
}
return result;
};
Here's how baseQueryWithRefreshToken
works:
We attempt to make the original API request.
If the request fails with a 401 status (Unauthorized), we make a request to the refresh token endpoint.
If a new access token is received, we update the Redux state and retry the original request.
If the refresh token request fails, we log out the user.
Step 3: Creating the API Slice
Finally, we create an API slice using RTK Query. This slice will use our baseQueryWithRefreshToken
to handle all API requests.
export const baseApi = createApi({
reducerPath: "baseApi",
baseQuery: baseQueryWithRefreshToken,
endpoints: () => ({}),
});
With this setup, any endpoint you define within baseApi
will automatically handle token expiration and refresh the token if necessary.
Conclusion
Handling token expiration and refreshing tokens can be complex, but with RTK Query, we can simplify the process significantly. By creating a custom base query that manages token refreshing, we ensure our application remains secure and user sessions stay active without additional user intervention.
This approach not only enhances the security of your application but also improves the user experience by handling token expiration gracefully. Customize this implementation to fit your specific needs and backend setup, and you'll have a robust solution for managing token expiration in your React app.
Happy coding!