Cookie cache does not refresh with server-side getSession call

I have a Tanstack Start app. My only uses of "authClient" right now are sign-in, sign-up, and sign-out. After that, I check the session within server functions and redirect to sign-in if no session is present.
I am also using the cookie cache ("better-auth.session_data") to ensure I'm not hitting the database for auth on every request. However, I noticed that after "session_data" expired the first time, it was never refreshed since I'm never calling the "get-session" endpoint again from the client.
So far this all make sense, but I'm trying to figure out the best way to keep the cookie cache populated. A couple things I've looked at: - I can use React Query to refetch with authClient.getSession() periodically... that'll ensure the cookie cache stays relatively updated (depending on how I refetch there could be some periods with no cookie cache.. I can set the refetch based on the cookie expiration too). - Check somewhere if the cookie is present and fetch if not. - Change my setup to use client-side auth more (in addition to server checks)... the refresh might be "free" in this case since it's checking frequently from the client anyway -- this would be quite a bit more network requests for auth I think. Any other preferred options or something I'm missing? I'm not certain what the best practice is for client-side session checks if things are generally working fine without them now (I'm aware of some potential UX benefits).
21 Replies
ChowderCrab
ChowderCrabOP2mo ago
EDIT: this isn't the latest. See below. I came up with something that works okay, although I'm still interested in what others might think or if there's a simpler solution. What I did: 1. In my server function that checks auth I'm already checking headers on essentially every request. I added a check to see if the cookie cache is present ("better-auth.session_data"). If it's not (i.e., it's expired), then I set a cookie (not HTTP Only) called "cache-expired=true". 2. In my logged-in layout, I have one query set up with React Query to call authClient.getSession. This is set to a longer time like 15 minutes, and will redirect to log-in if the session is ever null (expired). Before returning it sets the cookie "cache-expired=false" to reset things. 3. I have a separate React Query that runs every 10 seconds and all it does is check document.cookie to see if "cache-expired=true". If found, it'll invalidate the "session" query, causing it to refetch immediately and set the cookie cache. This setup will not refetch the session as soon as the cookie cache has expired. However, as soon as the user does pretty much anything in the app we'll set the "cache-expired" cookie and then the client will refresh the cookie cache within 10 seconds. This seems like a decent setup that minimizes unnecessary get-session calls from the client. If the user isn't actively using the app then the session will only be fetched client-side once every 15 minutes (I might even make this longer), which lets me send them to sign-in if the session expires. However, if they are actively using the app and the cookie cache expires, we'll get it reset within 10 seconds of the user's last action. I could save some of the extra work if I can get a new value for the cookie cache directly from BetterAuth and set it in the server function. That way rather than setting a "cache-expired" cookie and manually refetching from the client, I'd just refresh the cookie cache on the server as we do with the primary session token. Maybe there's already a way to do this? UPDATE: Improved further. I looked at the Better Auth code and implemented setCookieCache on my server. That removes the extra cookie I was making and the 10 second check on the client. Now, any time I find the cookie cache missing on the server I recreate it and set the cookie in the response. That means the only time I ever hit the database for the session is the first request after the cookie cache expires, which is exactly how I'd like it work.
deved9036
deved90362mo ago
Was looking into this quite a bit! Do you have a code snippet with your latest implementation? I tried to implement it in nextjs solely relying on ‘use cache’ but couldn’t really get it working
ChowderCrab
ChowderCrabOP2mo ago
Hey, I'm not quite ready to share yet since there are some security implications I'm trying to sort out. I'll come back if I work through those! I was actually able to vastly simplify my solution when I determined that I can pass my "setCookie" function (from TanStack Start) into the getSession call server-side. The Better Auth code will now use that to update cookies for me. So now all I do is:
import { getWebRequest, setCookie } from "@tanstack/start/server";
<<< later in code >>>
const request = getWebRequest();
if (!request) {
throw new Error("No request found");
}

const ctx = { headers: request.headers, setCookie };
const session = await auth.api.getSession(ctx);
import { getWebRequest, setCookie } from "@tanstack/start/server";
<<< later in code >>>
const request = getWebRequest();
if (!request) {
throw new Error("No request found");
}

const ctx = { headers: request.headers, setCookie };
const session = await auth.api.getSession(ctx);
For Next.js setting cookies is a handled differently so I'm not sure if you'll find a way to do this easily. Even if you wrapped the Next.js cookie setting code in a setCookie function that matches the expected signature, I think there may be a problem since cookies are (I think recently) async in Next.js.
mark
mark3w ago
I use the same way to refresh cache in v1.1.21. But I find it not working any more in v1.2.4. The ctx.setCookie is not called in v1.2.4
ChowderCrab
ChowderCrabOP3w ago
Good catch. I just confirmed this on my end. If I downgrade to 1.1.21 then the cookie gets reset no problem from my auth middleware using setCookie, but if I go back to 1.2.4 it doesn't work. Since that's all I changed it confirms something changed with BA and that it isn't a TS Start change causing this. I'll take a quick look to see what might have broken this. After a quick look, I'm not seeing anything in getSession or setSessionCookie after 1.1.21 that would have changed how this works. I might need to look at reimplementing setCookieCache on my own if I can't figure it out.
I think it would be nice to have a standalone server-side "get session" function... maybe with a return value that includes headers to set/delete so that the developer can use whatever is correct for their own framework to make the cookie changes. okay, so starting with 1.2.0 Better Auth will no longer use the setCookie that is passed in via the context. so doing this to pass a custom setCookie:
const ctx = { headers: request.headers, setCookie };
const session = await auth.api.getSession(ctx);
const ctx = { headers: request.headers, setCookie };
const session = await auth.api.getSession(ctx);
does nothing because when the Better Auth code calls ctx.setCookie to update the cookie cache it's using the original setCookie (that doesn't work), not the new one we're passing in. I confirmed this by modifying the code in node_modules to log the function before it's called and can see that it's not what we need. So something changed in 1.2 that doesn't merge in our custom setCookie (from TanStack Start) that was working before. @bekacru Just an FYI in case you're interested. We're trying to use getSession on the server and also be able to update cookies (e.g., refresh the cookie cache). But the default (just passing headers as shown in docs) doesn't work, and the workaround we had working before 1.2 doesn't work after 1.2 (passing setCookie to the getSession call). I think it'd be nice to have a server-only way to get session and find out what cookie changes need to be made.... maybe return cookies that need to be set/deleted so that it was framework agnostic and the user could handle those however they needed (or wrap in an adapter).
bekacru
bekacru3w ago
What framework are you using?
ChowderCrab
ChowderCrabOP3w ago
TanStack Start.
bekacru
bekacru3w ago
does tanstack start has a cookie helper like nextjs?
ChowderCrab
ChowderCrabOP3w ago
They use h3 under the hood, so you import setCookie from @tanstack/react-start/server , which I think is the same as this: https://h3.unjs.io/utils/advanced#setcookieevent-name-value-serializeoptions Prior to 1.2 I could pass that setCookie in the context to getSession and my cookies would get set, but with BA 1.2 the setCookie function I include is ignored. Although as mentioned I think it was only working by chance in the first place. And even if it works that wouldn't help with other cookie-related actions (deleteSessionCookie) that getSession tries to handle.
ChowderCrab
ChowderCrabOP3w ago
I think when BA upgraded to v1.2 and Better Call v1, the way headers were merged was changed and might be different from other things in the context. for example, I see this change: https://github.com/better-auth/better-auth/commit/46dfc078c03a409bbf3284640588ee0ca8989330#diff-fe25b5f529f2941cd1299abab03e2be7adb5d9e882cee20dff2aca64c747b1fc Which has a comment:
/**
* Headers should be merged differently
* so the hook doesn't override the whole
* header
*/
/**
* Headers should be merged differently
* so the hook doesn't override the whole
* header
*/
So without digging into the before/after in more detail it might have been coded so that headers are merged in a different way (and therefore still work with BA 1.2), but other things like a passed in setCookie function are overwritten by the hook. But again, I'm not sure if overriding the function in context is the way it should work or if it would be better to have a separate server-side function for getSession that returns information about cookie-related updates (deletions/sets/etc) which can then be implemented as-needed by the server-side framework in question.
bekacru
bekacru3w ago
we can make a plugin instead like we have for nextjs I thought I saw a PR for it but coudln't find it.
ChowderCrab
ChowderCrabOP3w ago
oh nice, yeah that might be the best way to handle this, especially if it's similar to what's done/planned for Next I looked too and couldn't find any PRs (open or closed) that sounded like this. Do you know generally how it was supposed to work? Like is/was the plugin a special way to "getSession" for Next.js on the server that also handles cookies? Or is it something more specific to cookies only? This whole thing isn't super urgent for me right this second, but it'd definitely be nice if there was an "official" way to do this (either out of the box or plugin).
ChowderCrab
ChowderCrabOP3w ago
I see an Issue was opened yesterday for someone running into the same problem with Express: https://github.com/better-auth/better-auth/issues/1862
GitHub
ctx.setCookie is not called in auth.api.getSession in v1.2.4 · ...
Is this suited for github? Yes, this is suited for github To Reproduce The example is a middleware in expressJS. It calls auth.api.getSession(ctx). Method setCookie is defined in ctx. export const ...
bekacru
bekacru3w ago
I'll look into it myself and will open a PR soon!
Slopeur
Slopeur2w ago
Is there a solution for this yet? I'm currently looking for a way to update the better-auth.session_data cookie if it is expired and stumbled accross this thread. I am using better-auth@1.2.5 and @tanstack/react-start@1.114.30
bekacru
bekacru2w ago
Whenever you fetch session it automatically refresh the session cookies based on session update age and expiration date
Slopeur
Slopeur7d ago
On the server? I am not seeing the better-auth.session_data cookie ever get refreshed. Once it expires, the DB is continusouly hit. I am trying to avoid having to call useSession or getSession on the client and was hoping the cookie could be updated when calling auth.api.getSession({ headers })
Benfa
Benfa7d ago
did you manage to create a PR yet?
gib
gib7d ago
This is the exact issue I’m running into with the oidc-plugin
Benfa
Benfa7d ago
i'm currently using this solution: https://discord.com/channels/1288403910284935179/1355102889269264394 and it's working just fine for me locally
gib
gib7d ago
whats interesting is, this issue isnt only for tanstack. its in the OIDC plugin as well which ive tested for both next and tanstack after it was working in 1.1.9. but i was coming up with a similar solution

Did you find this page helpful?