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:

  1. Redux Toolkit and RTK Query: Make sure Redux Toolkit is installed in your project.

  2. 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!