[GH-ISSUE #1862] ctx.setCookie is not called in auth.api.getSession in v1.2.4 #8951

Closed
opened 2026-04-13 04:11:53 -05:00 by GiteaMirror · 10 comments
Owner

Originally created by @tedlex on GitHub (Mar 18, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/1862

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 sessionMW = async (
  req: Request,
  res: Response,
  next: Function
) => {
  const ctx = {
    headers: fromNodeHeaders(req.headers),
    setCookie: (name: string, data: string, options: any) => {
      console.log(`Setting cookie: ${name}`);
      res.cookie(name, data, {
        ...options,
        maxAge: config.sessionCache_maxAge * 1000, // 5 minutes
      });
    },
  };
  
  const session = await auth.api.getSession(ctx);

  if (!session) {
    return res.status(401).json({ error: "Unauthorized: Invalid session" });
  }

  req.session = session;
  next();
};

Current vs. Expected behavior

ctx.setCookie is expected to be called when cookieCache is enabled and no cache found in request header. The goal is to refresh the cache cookie when it is expired.

You can reproduce it by deleteing the session_data cookie in browser.

It works as expected in v1.1.21

But it doesn't work in v1.2.4. No log information is printed and cache cookie is not set. It seems that ctx.setCookie is not called.

What version of Better Auth are you using?

1.2.4

Provide environment information

- OS: MACOS 15.3.2 
- browser: chrome

Which area(s) are affected? (Select all that apply)

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth"
export const auth = betterAuth({
  database: drizzleAdapter(db, {
    provider: "pg",
    schema: schema,
  }),
  emailAndPassword: {
    enabled: true,
    requireEmailVerification: true,
  },
  session: {
    cookieCache: {
      enabled: true,
      maxAge: config.sessionCache_maxAge, // Cache duration in seconds
    },
  },

Additional context

No response

Originally created by @tedlex on GitHub (Mar 18, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/1862 ### Is this suited for github? - [x] 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. ```typescript export const sessionMW = async ( req: Request, res: Response, next: Function ) => { const ctx = { headers: fromNodeHeaders(req.headers), setCookie: (name: string, data: string, options: any) => { console.log(`Setting cookie: ${name}`); res.cookie(name, data, { ...options, maxAge: config.sessionCache_maxAge * 1000, // 5 minutes }); }, }; const session = await auth.api.getSession(ctx); if (!session) { return res.status(401).json({ error: "Unauthorized: Invalid session" }); } req.session = session; next(); }; ``` ### Current vs. Expected behavior `ctx.setCookie` is expected to be called when cookieCache is enabled and no cache found in request header. The goal is to refresh the cache cookie when it is expired. You can reproduce it by deleteing the session_data cookie in browser. It works as expected in v1.1.21 But it doesn't work in v1.2.4. No log information is printed and cache cookie is not set. It seems that ctx.setCookie is not called. ### What version of Better Auth are you using? 1.2.4 ### Provide environment information ```bash - OS: MACOS 15.3.2 - browser: chrome ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "pg", schema: schema, }), emailAndPassword: { enabled: true, requireEmailVerification: true, }, session: { cookieCache: { enabled: true, maxAge: config.sessionCache_maxAge, // Cache duration in seconds }, }, ``` ### Additional context _No response_
GiteaMirror added the lockedbug labels 2026-04-13 04:11:53 -05:00
Author
Owner

@Muhamed-Ragab commented on GitHub (Mar 19, 2025):

This an example of my authentication middleware you can check it, this is working well with me, if your cookies are not working well you can try to build a plugin or use hooks inside better-auth

import { auth } from "@/core/better-auth/auth";
import { fromNodeHeaders } from "better-auth/node";
import type { RequestHandler } from "express";
import { ReasonPhrases, StatusCodes } from "HTTP-status-codes";

export const authenticate middleware: RequestHandler = async (
  req,
  res,
  next,
) => {
  const session = await auth.api.getSession({
    headers: fromNodeHeaders(req.headers),
  });

  if (!session) {
    res.status(StatusCodes.UNAUTHORIZED).json({
      message: "You are not authenticated",
      code: ReasonPhrases.UNAUTHORIZED,
    });
    return;
  }

  req.session = session.session;
  req.user = session.user;

  next();
};
<!-- gh-comment-id:2735189874 --> @Muhamed-Ragab commented on GitHub (Mar 19, 2025): This an example of my authentication middleware you can check it, this is working well with me, if your cookies are not working well you can try to build a plugin or use hooks inside **better-auth** ```ts import { auth } from "@/core/better-auth/auth"; import { fromNodeHeaders } from "better-auth/node"; import type { RequestHandler } from "express"; import { ReasonPhrases, StatusCodes } from "HTTP-status-codes"; export const authenticate middleware: RequestHandler = async ( req, res, next, ) => { const session = await auth.api.getSession({ headers: fromNodeHeaders(req.headers), }); if (!session) { res.status(StatusCodes.UNAUTHORIZED).json({ message: "You are not authenticated", code: ReasonPhrases.UNAUTHORIZED, }); return; } req.session = session.session; req.user = session.user; next(); }; ```
Author
Owner

@tedlex commented on GitHub (Mar 19, 2025):

@Muhamed-Ragab Thanks for the example. But how do you refresh your session cache cookie in this middleware after it expired? auth.api.getSession can't do this because it only returns session. There is no way to pass res.cookie to it.

<!-- gh-comment-id:2735551940 --> @tedlex commented on GitHub (Mar 19, 2025): @Muhamed-Ragab Thanks for the example. But how do you refresh your session cache cookie in this middleware after it expired? `auth.api.getSession` can't do this because it only returns session. There is no way to pass `res.cookie` to it.
Author
Owner

@Muhamed-Ragab commented on GitHub (Mar 19, 2025):

@Muhamed-Ragab Thanks for the example. But how do you refresh your session cache cookie in this middleware after it expired? auth.api.getSession can't do this because it only returns session. There is no way to pass res.cookie to it.

You can look at this: https://www.better-auth.com/docs/concepts/session-management#session-expiration

<!-- gh-comment-id:2735563036 --> @Muhamed-Ragab commented on GitHub (Mar 19, 2025): > [@Muhamed-Ragab](https://github.com/Muhamed-Ragab) Thanks for the example. But how do you refresh your session cache cookie in this middleware after it expired? `auth.api.getSession` can't do this because it only returns session. There is no way to pass `res.cookie` to it. You can look at this: https://www.better-auth.com/docs/concepts/session-management#session-expiration
Author
Owner

@tedlex commented on GitHub (Mar 19, 2025):

@Muhamed-Ragab It doesn't have information about how to refresh cache. If you call getSession at client side, it will refresh the cache cookie. But if you call getSession at server side, like in the middleware, it only returns the session value, but will not set cookie even though the cache has expired. In v1.1.21 you can define setCookie in ctx and pass it into getSession, but this doesn't work any more in v1.2.4

<!-- gh-comment-id:2735641366 --> @tedlex commented on GitHub (Mar 19, 2025): @Muhamed-Ragab It doesn't have information about how to refresh cache. If you call `getSession` at client side, it will refresh the cache cookie. But if you call `getSession` at server side, like in the middleware, it only returns the session value, but will not set cookie even though the cache has expired. In v1.1.21 you can define `setCookie` in ctx and pass it into `getSession`, but this doesn't work any more in v1.2.4
Author
Owner

@ErikPetersenDev commented on GitHub (Mar 19, 2025):

I'm running into the same problem with TanStack Start. Discussed in Discord here: https://discord.com/channels/1288403910284935179/1341250530982363158

As in your case, it was working in 1.1.21 but doesn't work after 1.2.0 due to the way the provided context is merged. The BA code now appears to make a special exception for "headers" but everything else is overwritten. So ctx.setCookie is technically being called inside setCookieCache (when called from getSession), but it's the default one that doesn't work server-side from our frameworks (Express, TanStack Start).

<!-- gh-comment-id:2738035509 --> @ErikPetersenDev commented on GitHub (Mar 19, 2025): I'm running into the same problem with TanStack Start. Discussed in Discord here: https://discord.com/channels/1288403910284935179/1341250530982363158 As in your case, it was working in 1.1.21 but doesn't work after 1.2.0 due to the way the provided context is merged. The BA code now appears to make a special exception for "headers" but everything else is overwritten. So `ctx.setCookie` is technically being called inside `setCookieCache` (when called from `getSession`), but it's the default one that doesn't work server-side from our frameworks (Express, TanStack Start).
Author
Owner

@tedlex commented on GitHub (Mar 21, 2025):

@ErikPetersenDev Thanks. I am using v1.1.21 now. I think periodically call getSession at client side could make it work in v1.2 to refresh the cache. But I hope there will be a standard way to do this at server side.

<!-- gh-comment-id:2742190692 --> @tedlex commented on GitHub (Mar 21, 2025): @ErikPetersenDev Thanks. I am using v1.1.21 now. I think periodically call getSession at client side could make it work in v1.2 to refresh the cache. But I hope there will be a standard way to do this at server side.
Author
Owner

@ErikPetersenDev commented on GitHub (Mar 21, 2025):

periodically call getSession at client side could make it work in v1.2

@tedlex Yeah, this is what I'm doing for now. I already use React Query in the client, so my other backend fetches were previously (pre-1.2) more than enough to keep the session refreshed. The only difference now is that I have to have a separate fetch to getSession to keep that refreshed.

The other option, which I did at one point in the Discord thread, is to replicate setCookieCache in your own code, and set the cookie if it isn't present and the session is valid. That works, but it's also fragile since if anything changes in the Better Auth library your cookie could become incompatible (e.g., the cookie cache signature changed in 1.1.20 which would have broken any copies).

<!-- gh-comment-id:2742941742 --> @ErikPetersenDev commented on GitHub (Mar 21, 2025): > periodically call getSession at client side could make it work in v1.2 @tedlex Yeah, this is what I'm doing for now. I already use React Query in the client, so my other backend fetches were previously (pre-1.2) more than enough to keep the session refreshed. The only difference now is that I have to have a separate fetch to getSession to keep that refreshed. The other option, which I did at one point in the Discord thread, is to replicate `setCookieCache` in your own code, and set the cookie if it isn't present and the session is valid. That works, but it's also fragile since if anything changes in the Better Auth library your cookie could become incompatible (e.g., the cookie cache signature changed in 1.1.20 which would have broken any copies).
Author
Owner

@dennisjnnh commented on GitHub (Apr 3, 2025):

i have noticed another issue with the set-cookie header when using useSession() and the customSession plugin, but i'm not sure if this is related here. #2106

<!-- gh-comment-id:2776456141 --> @dennisjnnh commented on GitHub (Apr 3, 2025): i have noticed another issue with the set-cookie header when using useSession() and the customSession plugin, but i'm not sure if this is related here. #2106
Author
Owner

@Kinfe123 commented on GitHub (May 3, 2025):

is this still an issue @tedlex

<!-- gh-comment-id:2848727707 --> @Kinfe123 commented on GitHub (May 3, 2025): is this still an issue @tedlex
Author
Owner

@agondigital-in commented on GitHub (Oct 5, 2025):

its may work for you

// src/middlewares/authenticate.middleware.ts
import type { NextFunction, Request, Response } from "express";
import { auth } from "@/lib/auth";

// Middleware to check if user is authenticated
export const authenticate = async (
req: Request,
res: Response,
next: NextFunction,
) => {
try {
const headers = new Headers();
if (req.headers.cookie) {
headers.set("cookie", req.headers.cookie);
}
const session = await auth.api.getSession({
headers,
});

	if (!session || !session.user) {
		return res.status(401).json({
			status: "error",
			message: "Unauthorized - Please login",
		});
	}

	// Attach user and session to request
	req.user = session.user;
	req.session = session.session;

	next();
} catch (error) {
	return res.status(401).json({
		status: "error",
		message: "Authentication failed",
	});
}

};

// Optional: Middleware to attach user if available (doesn't block if not authenticated)
export const optionalAuth = async (
req: Request,
res: Response,
next: NextFunction,
) => {
try {
const headers = new Headers();
if (req.headers.cookie) {
headers.set("cookie", req.headers.cookie);
}
const session = await auth.api.getSession({
headers,
});

	req.user = session?.user || null;
	req.session = session?.session || null;

	next();
} catch (error) {
	req.user = null;
	req.session = null;
	next();
}

};

<!-- gh-comment-id:3369148990 --> @agondigital-in commented on GitHub (Oct 5, 2025): its may work for you // src/middlewares/authenticate.middleware.ts import type { NextFunction, Request, Response } from "express"; import { auth } from "@/lib/auth"; // Middleware to check if user is authenticated export const authenticate = async ( req: Request, res: Response, next: NextFunction, ) => { try { const headers = new Headers(); if (req.headers.cookie) { headers.set("cookie", req.headers.cookie); } const session = await auth.api.getSession({ headers, }); if (!session || !session.user) { return res.status(401).json({ status: "error", message: "Unauthorized - Please login", }); } // Attach user and session to request req.user = session.user; req.session = session.session; next(); } catch (error) { return res.status(401).json({ status: "error", message: "Authentication failed", }); } }; // Optional: Middleware to attach user if available (doesn't block if not authenticated) export const optionalAuth = async ( req: Request, res: Response, next: NextFunction, ) => { try { const headers = new Headers(); if (req.headers.cookie) { headers.set("cookie", req.headers.cookie); } const session = await auth.api.getSession({ headers, }); req.user = session?.user || null; req.session = session?.session || null; next(); } catch (error) { req.user = null; req.session = null; next(); } };
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#8951