feat: enhance token refresh logic with request queue and improved error handling
This commit is contained in:
parent
b3c6ae2460
commit
845381e84d
@ -11,24 +11,42 @@ export const axiosInstance = axios.create({
|
|||||||
baseURL: process.env.NEXT_PUBLIC_API_URL,
|
baseURL: process.env.NEXT_PUBLIC_API_URL,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let isRefreshing = false;
|
||||||
|
let refreshQueue: Array<(token: string) => void> = [];
|
||||||
|
|
||||||
axiosInstance.interceptors.request.use(async (config) => {
|
axiosInstance.interceptors.request.use(async (config) => {
|
||||||
// If the access token is still valid, use it
|
|
||||||
if (tokenExpirationAt && moment().isBefore(tokenExpirationAt)) {
|
if (tokenExpirationAt && moment().isBefore(tokenExpirationAt)) {
|
||||||
config.headers.Authorization = `Bearer ${cachedAccessToken}`;
|
config.headers.Authorization = `Bearer ${cachedAccessToken}`;
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, get a new access token
|
if (isRefreshing) {
|
||||||
|
// Add the request to the queue
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
refreshQueue.push((token: string) => {
|
||||||
|
config.headers.Authorization = `Bearer ${token}`;
|
||||||
|
resolve(config);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
isRefreshing = true;
|
||||||
|
|
||||||
|
try {
|
||||||
const session = await getSession();
|
const session = await getSession();
|
||||||
if (!session) {
|
if (!session) {
|
||||||
throw new Error("User is not authenticated");
|
throw new Error("User is not authenticated");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache the new access token
|
|
||||||
cachedAccessToken = session.accessToken;
|
cachedAccessToken = session.accessToken;
|
||||||
tokenExpirationAt = moment(session.accessTokenExpires);
|
tokenExpirationAt = moment(session.accessTokenExpires);
|
||||||
|
|
||||||
// Use the new access token
|
// Execute the queue
|
||||||
|
refreshQueue.forEach((cb) => cb(cachedAccessToken!));
|
||||||
|
refreshQueue = [];
|
||||||
config.headers.Authorization = `Bearer ${cachedAccessToken}`;
|
config.headers.Authorization = `Bearer ${cachedAccessToken}`;
|
||||||
return config;
|
return config;
|
||||||
|
} finally {
|
||||||
|
isRefreshing = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
@ -62,7 +62,11 @@ export const authOptions: AuthOptions = {
|
|||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (moment().isBefore(moment(token.accessTokenExpires))) {
|
if (
|
||||||
|
moment().isBefore(
|
||||||
|
moment(token.accessTokenExpires).subtract(5, "s"),
|
||||||
|
)
|
||||||
|
) {
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,14 +89,25 @@ export const authOptions: AuthOptions = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let isRefreshing = false;
|
||||||
|
let refreshPromise: Promise<JWT> | null = null;
|
||||||
|
|
||||||
const refreshAccessToken = async (token: JWT): Promise<JWT> => {
|
const refreshAccessToken = async (token: JWT): Promise<JWT> => {
|
||||||
|
// If another request is already refreshing the token, wait for it to finish
|
||||||
|
if (isRefreshing) {
|
||||||
|
return refreshPromise!;
|
||||||
|
}
|
||||||
|
|
||||||
|
isRefreshing = true;
|
||||||
|
refreshPromise = (async () => {
|
||||||
|
try {
|
||||||
const response = await axios.post<{
|
const response = await axios.post<{
|
||||||
access_token: string;
|
access_token: string;
|
||||||
expires_in: number;
|
expires_in: number;
|
||||||
refresh_token: string;
|
refresh_token: string;
|
||||||
error_description?: string;
|
error_description?: string;
|
||||||
}>(
|
}>(
|
||||||
process.env.OAUTH_TOKEN_URL,
|
process.env.OAUTH_TOKEN_URL!,
|
||||||
{
|
{
|
||||||
grant_type: "refresh_token",
|
grant_type: "refresh_token",
|
||||||
refresh_token: token.refreshToken,
|
refresh_token: token.refreshToken,
|
||||||
@ -106,11 +121,7 @@ const refreshAccessToken = async (token: JWT): Promise<JWT> => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.status != 200) {
|
if (response.status !== 200) throw response.data;
|
||||||
throw new Error(
|
|
||||||
response.data.error_description || "Failed to refresh access token",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...token,
|
...token,
|
||||||
@ -120,6 +131,13 @@ const refreshAccessToken = async (token: JWT): Promise<JWT> => {
|
|||||||
.subtract(5, "s"),
|
.subtract(5, "s"),
|
||||||
refreshToken: response.data.refresh_token,
|
refreshToken: response.data.refresh_token,
|
||||||
};
|
};
|
||||||
|
} finally {
|
||||||
|
isRefreshing = false;
|
||||||
|
refreshPromise = null;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
return refreshPromise;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSession = cache(() => getServerSession());
|
export const getSession = cache(() => getServerSession());
|
||||||
|
Loading…
Reference in New Issue
Block a user