Dealing with session expiration in a React app

I need to have the UI reflect the logged in state at all times. useSession will not trigger if a session expires (see: https://discord.com/channels/1288403910284935179/1353810672458403920/1353820038154551469) I'm trying to figure out how to deal with this, here's my current plan: 1. Store the current session in a React context. Keep it updated for sign in / sign out via useSession. 2. When I get a 401 back from my server, that probably means the session has expired, call getSession and update the context. Anyone have a better idea? (tagging this with Next.js but I think it should apply to all React apps)
6 Replies
konstantin
konstantin4w ago
setup an auth guard with useSession on the frontend, then just revoke the session whenever you want on the backend, or let it expire I have an auth guard and it's a decent setup. For a more agnostic approach you can add an auth middleware and return some standard response (+code) then handle that for all responses to the client
lukas
lukasOP4w ago
Yeah, an auth guard on the frontend is what I'm trying to set up. The problem is that useSession does not fire if the session expires (confirmed by bekacru, see my discussion link above.) Basically - I need the UI to change as soon as the session expires. Seems like my options are to poll with getSession, or to just wait until an operation fails with a 401 error.
lonelyplanet
lonelyplanet4w ago
create a custom useSession hook that has a timeout that set to expiry time of session make sure you cleanup the timeout every render. In your hook you could have a sideeffect like onExpire and make it a funciton call the onExpire in the timeout https://github.com/daveyplate/better-auth-tanstack/blob/main/src/hooks/session/use-session.ts See this hook from better-auth-tanstack it has when it expires it refetches ensure the session is constantly refreshes by betterauth Shows an example of cleaning up the timeout aswell
lukas
lukasOP4w ago
So I have a custom useSession hook now - I tried calling refetch() after expiration to get the native useSession to refresh with the null session, but it didn't work, I had to just return data: null from my custom useSession hook after the getSession call from the timeout returns null. Does that seem right?
lonelyplanet
lonelyplanet4w ago
Care to share your code?
lukas
lukasOP4w ago
const originalUseSession = authClient.useSession;

// Create our extended useSession hook with expiration handling
export function useSession() {
const originalResult = originalUseSession();
const [sessionResult, setSessionResult] = useState(originalResult);

useEffect(() => {
// Update sessionResult whenever originalResult changes
setSessionResult(originalResult);
}, [originalResult]);

useEffect(() => {
const { data } = sessionResult;

// If no session or no expiration, don't set up a timer
if (!data?.session) return;
if (!data.session?.expiresAt) return;

// Calculate time until expiration
const expiresAt = new Date(data.session.expiresAt).getTime();
const refetchIn = expiresAt - Date.now() + 5000; // fetch the session 5 seconds after it expires

const timeout = setTimeout(async () => {
const newSessionResult = await authClient.getSession();

if (!newSessionResult || !newSessionResult.data || newSessionResult.data === null) {
setSessionResult({ ...sessionResult, data: null });
}
}, refetchIn);

return () => clearTimeout(timeout);
}, [sessionResult]);

return sessionResult;
}
const originalUseSession = authClient.useSession;

// Create our extended useSession hook with expiration handling
export function useSession() {
const originalResult = originalUseSession();
const [sessionResult, setSessionResult] = useState(originalResult);

useEffect(() => {
// Update sessionResult whenever originalResult changes
setSessionResult(originalResult);
}, [originalResult]);

useEffect(() => {
const { data } = sessionResult;

// If no session or no expiration, don't set up a timer
if (!data?.session) return;
if (!data.session?.expiresAt) return;

// Calculate time until expiration
const expiresAt = new Date(data.session.expiresAt).getTime();
const refetchIn = expiresAt - Date.now() + 5000; // fetch the session 5 seconds after it expires

const timeout = setTimeout(async () => {
const newSessionResult = await authClient.getSession();

if (!newSessionResult || !newSessionResult.data || newSessionResult.data === null) {
setSessionResult({ ...sessionResult, data: null });
}
}, refetchIn);

return () => clearTimeout(timeout);
}, [sessionResult]);

return sessionResult;
}

Did you find this page helpful?