Skip to content

Instantly share code, notes, and snippets.

@gokhantaskan
Last active June 1, 2024 08:45
Show Gist options
  • Save gokhantaskan/b3738bce682de02dfd73ab039528be20 to your computer and use it in GitHub Desktop.
Save gokhantaskan/b3738bce682de02dfd73ab039528be20 to your computer and use it in GitHub Desktop.
Axios + Firebase interceptor for authentication
import _axios, { type AxiosRequestConfig } from "axios";
import defu from "defu";
import { FB_EXPIRATION_DATE, IS_DEV } from "@/constants/globals";
import { useBearerToken } from "./useBearerToken";
export function useAxios<T, D = any>(url: string, options?: Omit<AxiosRequestConfig<D>, "url">) {
const { bearerToken, getIdTokenResult } = useBearerToken();
const defaults: Partial<AxiosRequestConfig> = {
url,
method: "GET",
baseURL: import.meta.env.VITE_API_PATH,
headers: {
...(bearerToken ? { Authorization: `Bearer ${bearerToken}` } : null),
},
};
const params = defu(options, defaults);
const axios = _axios.create();
axios.interceptors.request.use(
async config => {
// Add trailing slash to url if it doesn't have and is not a GET method.
if (config.method && ["post", "patch", "put"].includes(config.method.toLowerCase())) {
if (!config.url?.endsWith("/")) {
config.url += "/";
}
}
// Check if the token has expired
const fbExpirationDate = Number(localStorage.getItem(FB_EXPIRATION_DATE));
const now = new Date().getTime();
if (fbExpirationDate && now >= fbExpirationDate) {
const { token: newBearerToken } = await getIdTokenResult(true);
config.headers.Authorization = `Bearer ${newBearerToken}`;
}
return config;
},
error => {
if (IS_DEV) {
console.log("Axios request error: ", error);
}
return Promise.reject(error);
}
);
axios.interceptors.response.use(
response => {
if (IS_DEV) {
console.log("Axios request response: ", response);
}
return response;
},
async error => {
if (IS_DEV) {
console.log("Axios error response: ", error);
}
// ! You must implement an additional indicator for a token refresh,
// ! or it will result in an infinite loop with all 403 errors
if (error.response.status === 403) {
try {
const { token: newBearerToken } = await getIdTokenResult(true);
error.config.headers.Authorization = `Bearer ${newBearerToken}`;
return axios.request(error.config);
} catch (retryError) {
if (IS_DEV) {
console.log("Retry request error: ", retryError);
}
return Promise.reject(retryError);
}
}
return Promise.reject(error);
}
);
return axios.request<T>(params);
}
import { type IdTokenResult } from "firebase/auth";
import { useCurrentUser } from "vuefire";
import { LS_FB_EXPIRATION_DATE } from "@/constants";
let bearerToken: string | null = null;
export function useBearerToken() {
function fetchIdTokenResults(forceRefresh?: boolean) {
return new Promise<IdTokenResult>((resolve, reject) => {
const user = useCurrentUser();
try {
user.value?.getIdTokenResult(forceRefresh).then(r => {
bearerToken = r.token;
// Set the token expiration date in the local storage to use it later in the fetch interceptor
window?.localStorage.setItem(
LS_FB_EXPIRATION_DATE,
new Date(r.expirationTime).getTime().toString()
);
resolve(r);
});
} catch (e) {
// Not sure if this is needed
// bearerToken.value = null;
reject(e);
}
});
}
return {
bearerToken,
fetchIdTokenResults,
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment