Skip to content

Instantly share code, notes, and snippets.

@moogii
Created August 8, 2021 16:50
Show Gist options
  • Star 30 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save moogii/f4b3c35b22ca1b20fdcbc0fa770069ca to your computer and use it in GitHub Desktop.
Save moogii/f4b3c35b22ca1b20fdcbc0fa770069ca to your computer and use it in GitHub Desktop.
Axios based Next js client and server side token refresher
import axios, { AxiosError } from "axios";
import { GetServerSidePropsContext } from "next";
import Router from "next/router";
const isServer = () => {
return typeof window === "undefined";
}
let accessToken = "";
let context = <GetServerSidePropsContext>{};
const baseURL = process.env.NEXT_PUBLIC_BACKEND_URL!;
export const setAccessToken = (_accessToken: string) => {
accessToken = _accessToken
}
export const getAccessToken = () => (accessToken)
export const setContext = (_context: GetServerSidePropsContext) => {
context = _context;
}
export const api = axios.create({
baseURL,
headers: {
'Content-Type': 'application/json',
},
withCredentials: true, // to send cookie
})
api.interceptors.request.use((config) => {
if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`
}
if (isServer() && context?.req?.cookies) {
config.headers.Cookie = `gid=${context.req.cookies.gid};`
}
return config;
});
api.interceptors.response.use(
response => {
return response;
},
(error: AxiosError) => {
// check conditions to refresh token
if (error.response?.status === 401 && !error.response?.config?.url?.includes("auth/refresh")
&& !error.response?.config?.url?.includes("signin")) {
return refreshToken(error);
}
return Promise.reject(error);
}
)
let fetchingToken = false;
let subscribers: ((token: string) => any)[] = [];
const onAccessTokenFetched = (token: string) => {
subscribers.forEach((callback) => callback(token));
subscribers = [];
}
const addSubscriber = (callback: (token: string) => any) => {
subscribers.push(callback)
}
const refreshToken = async (oError: AxiosError) => {
try {
const { response } = oError;
// create new Promise to retry original request
const retryOriginalRequest = new Promise((resolve) => {
addSubscriber((token: string) => {
response!.config.headers['Authorization'] = `Bearer ${token}`
resolve(axios(response!.config))
})
})
// check whether refreshing token or not
if (!fetchingToken) {
fetchingToken = true;
// refresh token
const { data } = await api.post('/api/v1/auth/refresh')
// check if this is server or not. We don't wanna save response token on server.
if (!isServer) {
setAccessToken(data.accessToken);
}
// when new token arrives, retry old requests
onAccessTokenFetched(data.accessToken)
}
return retryOriginalRequest
} catch (error) {
// on error go to login page
if (!isServer() && !Router.asPath.includes('/login')) {
Router.push('/login');
}
if (isServer()) {
context.res.setHeader("location", "/login");
context.res.statusCode = 302;
context.res.end();
}
return Promise.reject(oError);
} finally {
fetchingToken = false;
}
}
import { GetServerSideProps } from 'next';
import React from 'react';
import { api, setContext } from '../utils/api';
const Post: React.FC<any> = ({ posts }) => {
return (
<ul>
{JSON.stringify(posts)}
</ul>
);
}
export const getServerSideProps: GetServerSideProps = async (context) => {
setContext(context)
let posts = [];
try {
const { data } = await api.get("posts");
posts = data;
} catch (error) {
throw (error)
}
return {
props: {
posts,
}
}
}
export default Post;
@RioChndr
Copy link

Thank you very much, I hard to find axios config for next js.

@arvi9
Copy link

arvi9 commented Jan 26, 2023

Thank you 🫡

@YoonHan
Copy link

YoonHan commented Mar 21, 2023

Thanks for sharing this :)

btw, I have just found one little typo in the code above.
At line 89 in api.ts file, I think isServer needs to be called as a function -> if (!isServer())

@synuns
Copy link

synuns commented Jun 21, 2023

Thank you! This code inspired me :)
It's so good idea!

@rickyviz
Copy link

rickyviz commented Jan 30, 2024

it not working in app directory

how i set context ?

app directory not support this method getServerSideProps

export const getServerSideProps: GetServerSideProps = async (context) => {
  setContext(context)

  let posts = [];
  try {
    const { data } = await api.get("posts");
    posts = data;
  } catch (error) {
    throw (error)
  }

  return {
    props: {
      posts,
    }
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment