Last active
June 1, 2024 08:45
-
-
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 { 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); | |
} |
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