I am trying to implement token update logic for my SvelteKIT application which uses an external api. I ran into a problem with handling concurrent requests. I added a semaphore to prevent multiple token refreshes, but now I have a different problem. I am waiting for the update to complete, but the event passed to the handle function does not contain new cookies. How can I solve this problem?
import { api_url } from '$lib/utils';
import { redirect, type Handle, type HandleFetch, type RequestEvent } from '@sveltejs/kit';
import * as scp from 'set-cookie-parser';
const delay = (ms) => new Promise((res) => setTimeout(res, ms));
const isAllowedHost = (host: string) => {
return host === 'localhost' || host === 'nginx';
};
function setCookies(
res: Response,
event: RequestEvent<Partial<Record<string, string>>, string | null>
) {
const setCookie = res.headers.getSetCookie();
if (setCookie && isAllowedHost(event.url.hostname)) {
const parsed = scp.parse(res);
parsed.forEach((cookie) => {
event.cookies.set(cookie.name, cookie.value, {
...cookie
});
});
if (res.status == 200) {
if (event.url.pathname == '/api/auth/signin' || event.url.pathname == '/api/auth/refresh') {
event.locals.isAuthenticated = true;
}
}
}
}
let needsRefresh = false;
function checkTokenRequest(): Promise<void> {
return new Promise(function (resolve) {
(function waitForTokenRequest() {
if (!needsRefresh) return resolve();
setTimeout(waitForTokenRequest, 30);
})();
});
}
export const handle: Handle = async ({ event, resolve }) => {
if (needsRefresh) {
await checkTokenRequest();
}
const { cookies } = event;
const accessToken = cookies.get('access_token');
const refreshToken = cookies.get('refresh_token');
if (refreshToken) {
event.locals.isAuthenticated = true;
if (!accessToken) {
needsRefresh = true;
}
} else if (!accessToken && !refreshToken) {
event.locals.isAuthenticated = false;
}
if (!event.url.pathname.includes('/auth')) {
if (!event.locals.isAuthenticated) {
throw redirect(303, '/auth/signin');
}
}
if (needsRefresh && !event.url.pathname.includes('/auth')) {
const refresh_res = await fetch(`${api_url}/auth/refresh`, {
method: 'POST',
headers: {
cookie: `refresh_token=${refreshToken?.toString()}`
}
});
if (!refresh_res.ok) {
cookies.delete('access_token', { path: '/' });
cookies.delete('refresh_token', { path: '/' });
} else {
setCookies(refresh_res, event);
}
await delay(5000);
needsRefresh = false;
}
return await resolve(event);
};
export const handleFetch: HandleFetch = async ({ request, event, fetch: nodeFetch }) => {
const { cookies } = event;
const requestURL = new URL(request.url);
if (isAllowedHost(requestURL.host)) {
request.headers.set('cookie', `access_token=${cookies.get('access_token')?.toString()}`);
}
const res = await nodeFetch(request);
if (event.url.pathname == '/auth/signin') {
setCookies(res, event);
}
return res;
};
I have tried to create global variables with access and refresh token, but I don’t think it’s a good solution.
PoSayDone is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.