cover

SWR Nextjs : Auto Rotate Refresh Token

2 Minutes

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
  }
}
comments powered by Disqus