Skip to content

Instantly share code, notes, and snippets.

@gokhantaskan
Last active February 1, 2024 12:06
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 { IS_DEV, LS_FB_EXPIRATION_DATE } from "@/constants";
import type { BackendError } from "@/lib/types/common";
import { useBearerToken } from "./useBearerToken";
export function useAxios<T, D = any>(url: string, options?: Omit<AxiosRequestConfig<D>, "url">) {
const { bearerToken, fetchIdTokenResults } = useBearerToken();
const defaults: Partial<AxiosRequestConfig> = {
url,
method: "GET",
baseURL: import.meta.env.VITE_API_PATH,
headers: {
Authorization: `Bearer ${bearerToken}`,
},
};
const params = defu(options, defaults);
const instance = axios.create();
instance.interceptors.request.use(
async config => {
// Add trailing slash to url if it is a POST method.
// First, check if the endpoint has a trailing slash.
// If it doesn't have a trailing slash, add it.
if (config.method?.toLowerCase() === "post") {
if (!config.url?.endsWith("/")) {
config.url += "/";
}
}
// Check if the token has expired
const fbExpirationDate = Number(localStorage.getItem(LS_FB_EXPIRATION_DATE));
if (fbExpirationDate && new Date().getTime() >= fbExpirationDate) {
await fetchIdTokenResults(true);
config.headers.Authorization = `Bearer ${bearerToken}`;
console.log("Token has expired and refreshed");
}
return config;
},
error => {
if (IS_DEV) {
console.log("Axios request error: ", error);
}
return Promise.reject(error);
}
);
instance.interceptors.response.use(
response => {
if (IS_DEV) {
console.log("Axios request response: ", response);
}
return response;
},
async error => {
if (IS_DEV) {
console.log("Axios response error: ", error);
}
if (
(error._data as BackendError).name === "AuthenticationFailed" ||
error.response.status === 403
) {
console.warn("Authentication failed. Retrying...");
await fetchIdTokenResults(true);
// ? Retry the request - Needs testing
instance.request(error.config);
}
return Promise.reject(error);
}
);
return instance.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