SWR Nextjs : Auto Rotate Refresh Token

Rio ChandraRio Chandra
2 min read

What stack that I use?

    "swr": "^2.2.5",
    "next": "14.2.3",

Problem

SPA (Single Page Application) are hard to identify user's session using JWT. In my case, I use JWT as token session that used for every interaction API.

Solution

We can use onErrorRetry property in swr documentation. Here is my code to automatically refresh token when API return 401 and token_expired code.

"use client";
import { SWRConfig } from "swr";
import { FetcherErrorException, fetcherSwrGet } from "./fetcher";

export function SwrConfig({ children }: { children: React.ReactNode }) {
  return (
    <SWRConfig
      value={{
        errorRetryCount: 3,
        async onErrorRetry(err: FetcherErrorException, key, config, revalidate, { retryCount }) {
          if (retryCount >= 3) return;
          if (err.status === 404) return;
          // My server return spesific code to identify expired token
          // Verify here
          if (err.response?.code === "token_expired") {
            try {
                // add your function to refresh token
                await refreshtoken();
            } catch (e: any) {
              console.error(e)
              if (e.status === 401) {
                // if error 401, do force logout and redirect to login
                logout()
                window.location.href = "/auth/login"
                return
              }
            }
            // revalidate the request, ask swr to re-fetch the API
            revalidate({ retryCount: retryCount + 1 })
          }
        }
      }}
    >
      {children}
    </SWRConfig>
  );
}

You must return status in error class and response json also, here is my fetcher API looks like

export const fetchApi = async (...params: Parameters<typeof fetch>) => {
  const token = useUserAuth.getState().token;
  params[0] = new URL(params[0] as string, process.env.NEXT_PUBLIC_APP_SERVER);
  const header = new Headers();
  header.set('Content-Type', 'application/json');
  params[1] = Object.assign(params[1] || {}, {
    headers: header
  })
  if (token?.accessToken) {
    header.set('Authorization', `Bearer ${token.accessToken}`)
  }

  const resFetch = await fetch(...params);
  const resFetchJson = await resFetch.json();
  // ---------  Focus in this part ---------
  if (!resFetch.ok) {
    const message = resFetchJson?.message || resFetch.statusText;
    throw new FetcherErrorException(message, {
      response: resFetchJson,
      status: resFetch.status,
    })
  }
  return resFetchJson;
}

export class FetcherErrorException extends Error {
  status: number;
  response: any

  constructor(message: string, options: {
    status: number
    response: any
  }) {
    super(message)
    this.status = options.status
    this.response = options.response
  }
}
0
Subscribe to my newsletter

Read articles from Rio Chandra directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Rio Chandra
Rio Chandra

I am a Software Developer, solves complex problems while creating some of the most advanced applications in the industry. Provides mentorship and supervise developers. Has experience with Reactjs, VueJs, NestJs and other Framework.