Last active
February 1, 2024 12:06
-
-
Save gokhantaskan/b3738bce682de02dfd73ab039528be20 to your computer and use it in GitHub Desktop.
Axios + Firebase interceptor for authentication
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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