rememberMe = flase is not signing out the user when browser close #1393

Closed
opened 2026-03-13 08:37:02 -05:00 by GiteaMirror · 17 comments
Owner

Originally created by @DimitarY on GitHub (Jun 20, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Try to log in using authClinet.signIn.email using

callbackURL: callbackUrl || "/",
rememberMe: false

Current vs. Expected behavior

When rememberMe is set to false user is not signed out when the browser is closed.

What version of Better Auth are you using?

1.2.8

Provide environment information

- OS: Rocky Linux 9.6
- Browser: Firefox 128.11.0esr

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

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth"
export const auth = betterAuth({
  emailAndPassword: {
    enabled: true,
    autoSignIn: false,
  },
});

Additional context

rememberMe was updated in https://github.com/better-auth/better-auth/pull/476, and the default value is false even though in the doc it says it is true. I tried to implement this in my codebase, but found out that it is already false by default, and it is not working.

Originally created by @DimitarY on GitHub (Jun 20, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce Try to log in using `authClinet.signIn.email` using ``` callbackURL: callbackUrl || "/", rememberMe: false ``` ### Current vs. Expected behavior When `rememberMe` is set to `false` user is not signed out when the browser is closed. ### What version of Better Auth are you using? 1.2.8 ### Provide environment information ```bash - OS: Rocky Linux 9.6 - Browser: Firefox 128.11.0esr ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" export const auth = betterAuth({ emailAndPassword: { enabled: true, autoSignIn: false, }, }); ``` ### Additional context `rememberMe` was updated in https://github.com/better-auth/better-auth/pull/476, and the default value is `false` even though in the doc it says it is `true`. I tried to implement this in my codebase, but found out that it is already `false` by default, and it is not working.
GiteaMirror added the bug label 2026-03-13 08:37:02 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Jun 21, 2025):

The code for Better Auth is designed so that when rememberMe is set to false, the session cookie should be a session-only cookie (no maxAge), which means it should expire when the browser is closed. This matches standard browser behavior for session cookies and is implemented by omitting the maxAge attribute when setting the cookie. There’s no platform-specific logic for Firefox or Linux, so this should work across browsers as long as they follow standard cookie handling rules reference.

If the session persists after closing the browser, possible reasons include: browser settings that restore sessions/tabs, proxy or deployment layers modifying cookie headers, or custom cookie attributes in your Better Auth config that override defaults.

To debug, check the session cookie in your browser’s dev tools after login with rememberMe: false. Make sure it does not have an Expires or Max-Age attribute. If it does, something is overriding the intended behavior. Also, check for any advanced cookie configuration in your Better Auth setup that might affect this.

If the cookie looks correct but the session still persists, try with a fresh browser profile or another browser to rule out browser-specific session restore features. If you’re behind a proxy or using a custom domain setup, ensure those layers aren’t rewriting cookie headers.

If you can, share the actual Set-Cookie header for the session cookie after login with rememberMe: false—that will help pinpoint if the issue is with cookie attributes or something else.

Written by @dosu, approved by Bekacru

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Join Discord Share on X

@dosubot[bot] commented on GitHub (Jun 21, 2025): <!-- Answer --> The code for Better Auth is designed so that when `rememberMe` is set to `false`, the session cookie should be a session-only cookie (no `maxAge`), which means it should expire when the browser is closed. This matches standard browser behavior for session cookies and is implemented by omitting the `maxAge` attribute when setting the cookie. There’s no platform-specific logic for Firefox or Linux, so this should work across browsers as long as they follow standard cookie handling rules [reference](https://github.com/better-auth/better-auth/blob/9801d1be53d9da04686b94c6286c53ec97496740/packages/better-auth/src/cookies/index.ts). If the session persists after closing the browser, possible reasons include: browser settings that restore sessions/tabs, proxy or deployment layers modifying cookie headers, or custom cookie attributes in your Better Auth config that override defaults. To debug, check the session cookie in your browser’s dev tools after login with `rememberMe: false`. Make sure it does not have an `Expires` or `Max-Age` attribute. If it does, something is overriding the intended behavior. Also, check for any advanced cookie configuration in your Better Auth setup that might affect this. If the cookie looks correct but the session still persists, try with a fresh browser profile or another browser to rule out browser-specific session restore features. If you’re behind a proxy or using a custom domain setup, ensure those layers aren’t rewriting cookie headers. If you can, share the actual `Set-Cookie` header for the session cookie after login with `rememberMe: false`—that will help pinpoint if the issue is with cookie attributes or something else. _Written by @dosu, approved by Bekacru_ <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/84cad877-b7d5-44f2-b51c-ec3660d9f8cc?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/84cad877-b7d5-44f2-b51c-ec3660d9f8cc?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/84cad877-b7d5-44f2-b51c-ec3660d9f8cc?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/84cad877-b7d5-44f2-b51c-ec3660d9f8cc?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/84cad877-b7d5-44f2-b51c-ec3660d9f8cc?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/84cad877-b7d5-44f2-b51c-ec3660d9f8cc?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/84cad877-b7d5-44f2-b51c-ec3660d9f8cc?feedback_type=other)</sup>&nbsp;&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/3112)
Author
Owner

@ping-maxwell commented on GitHub (Jul 10, 2025):

Hello, @DimitarY does the comment from Dosubot help answer your question?

@ping-maxwell commented on GitHub (Jul 10, 2025): Hello, @DimitarY does the comment from Dosubot help answer your question?
Author
Owner

@DimitarY commented on GitHub (Jul 10, 2025):

I am still having random cases where user is still sign in when browser is closed. I am not sure why it is happening.

In my config I don't see anything that can cause this.

@DimitarY commented on GitHub (Jul 10, 2025): I am still having random cases where user is still sign in when browser is closed. I am not sure why it is happening. In my [config](https://github.com/DimitarY/next-starter/blob/main/src/lib/auth.ts) I don't see anything that can cause this.
Author
Owner

@ping-maxwell commented on GitHub (Jul 10, 2025):

@DimitarY I'll see if I can test on my end. By the way, what browser are you on?

@ping-maxwell commented on GitHub (Jul 10, 2025): @DimitarY I'll see if I can test on my end. By the way, what browser are you on?
Author
Owner

@DimitarY commented on GitHub (Jul 10, 2025):

@DimitarY I'll see if I can test on my end. By the way, what browser are you on?

I try on Firefox, Chrome, Firefox Mobile, Safari, and Ack Mobile

@DimitarY commented on GitHub (Jul 10, 2025): > @DimitarY I'll see if I can test on my end. By the way, what browser are you on? > I try on Firefox, Chrome, Firefox Mobile, Safari, and Ack Mobile
Author
Owner

@ping-maxwell commented on GitHub (Jul 10, 2025):

Okay I just tested on my end and it works, the session was null upon re-opening chrome.
Let me skim through your config.

@ping-maxwell commented on GitHub (Jul 10, 2025): Okay I just tested on my end and it works, the session was null upon re-opening chrome. Let me skim through your config.
Author
Owner

@DimitarY commented on GitHub (Jul 10, 2025):

You can use my project and test it directly

https://github.com/DimitarY/next-starter

@DimitarY commented on GitHub (Jul 10, 2025): You can use my project and test it directly https://github.com/DimitarY/next-starter
Author
Owner

@ping-maxwell commented on GitHub (Jul 10, 2025):

Hey can you make a bare bone simple version to demo or at least for me to test?
There is quite some work to get setup, and then I see your sign-in form doesn't allow providing passwords?

Anyway I think it would be much easier if you can just make a demo project to test.

@ping-maxwell commented on GitHub (Jul 10, 2025): Hey can you make a bare bone simple version to demo or at least for me to test? There is quite some work to get setup, and then I see your sign-in form doesn't allow providing passwords? Anyway I think it would be much easier if you can just make a demo project to test.
Author
Owner

@DimitarY commented on GitHub (Jul 20, 2025):

Hey can you make a bare bone simple version to demo or at least for me to test? There is quite some work to get setup, and then I see your sign-in form doesn't allow providing passwords?

Anyway I think it would be much easier if you can just make a demo project to test.

I created a new branch with a clean sign-in with credentials and a remember me checkbox

https://github.com/DimitarY/next-starter/tree/remember-me

@DimitarY commented on GitHub (Jul 20, 2025): > Hey can you make a bare bone simple version to demo or at least for me to test? There is quite some work to get setup, and then I see your sign-in form doesn't allow providing passwords? > > Anyway I think it would be much easier if you can just make a demo project to test. I created a new branch with a clean sign-in with credentials and a remember me checkbox https://github.com/DimitarY/next-starter/tree/remember-me
Author
Owner

@qodesmith commented on GitHub (Jul 24, 2025):

I've been experiencing this as well (on localhost, so not sure if that affects things). I sign in via email with rememberMe: false and I see 3 Set-Cookie entries in the headers. One of them has a Max-Age property:

Cooke 1:

better-auth.session_token=12345; Path=/; HttpOnly; SameSite=Lax

Cookie 2:

better-auth.dont_remember=true.12345; Path=/; HttpOnly; SameSite=Lax

Cookie 3:

better-auth.session_data=long12345; Max-Age=300; Path=/; HttpOnly; SameSite=Lax

Should that last cookie have Max-Age set?

@qodesmith commented on GitHub (Jul 24, 2025): I've been experiencing this as well (on localhost, so not sure if that affects things). I sign in via email with `rememberMe: false` and I see 3 `Set-Cookie` entries in the headers. One of them has a `Max-Age` property: Cooke 1: ``` better-auth.session_token=12345; Path=/; HttpOnly; SameSite=Lax ``` Cookie 2: ``` better-auth.dont_remember=true.12345; Path=/; HttpOnly; SameSite=Lax ``` Cookie 3: ``` better-auth.session_data=long12345; Max-Age=300; Path=/; HttpOnly; SameSite=Lax ``` Should that last cookie have `Max-Age` set?
Author
Owner

@Bekacru commented on GitHub (Aug 2, 2025):

@qodesmith No. By default, when maxAge isn’t provided, the browser sets the cookie to expire at the end of the session. To check whether this is working, just inspect your cookies, the session cookie should have a session as expiry, and there should also be a dont_remember cookie set

@Bekacru commented on GitHub (Aug 2, 2025): @qodesmith No. By default, when `maxAge` isn’t provided, the browser sets the cookie to expire at the end of the session. To check whether this is working, just inspect your cookies, the session cookie should have a `session` as expiry, and there should also be a `dont_remember` cookie set
Author
Owner

@johncmunson commented on GitHub (Sep 16, 2025):

I'm having the same exact problem. I'll see if I can post a minimal repo soon demonstrating the issue.

@johncmunson commented on GitHub (Sep 16, 2025): I'm having the same exact problem. I'll see if I can post a minimal repo soon demonstrating the issue.
Author
Owner

@DimitarY commented on GitHub (Sep 16, 2025):

What I found is that when browser is set to "continue where you left off" the session is not terminated because browsers thread it like the session never ended and it restores it.
I am still working on a solution for that. Even with version 1.3.10 where they fix the issues with session cache.

@DimitarY commented on GitHub (Sep 16, 2025): What I found is that when browser is set to "continue where you left off" the session is not terminated because browsers thread it like the session never ended and it restores it. I am still working on a solution for that. Even with version 1.3.10 where they fix the issues with session cache.
Author
Owner

@johncmunson commented on GitHub (Sep 16, 2025):

@DimitarY I just came to the same conclusion :)

In addition, I'm finding that even if you change that setting to something like "Open the new tab page", Chrome might still restore your previous session if you are signed into a Chrome profile that has sync enabled.

When I created a new Chrome profile with no sync settings enabled, the "Remember me" functionality started working exactly as I would expect.

I'm fine with this and don't intend to go out of my way in my application code to try and overcome browser quirks in order to force behavior that we would all expect. However, as I was curious to what a solution to this problem might look like, I had Copilot spit out some code for me. This code has not been tested, but it makes sense and it seems like with some refinement this strategy could potentially work.

If you try this out, I'd be curious to know how it goes for you!

Edit: I continued vibe coding my way through the original code that Copilot spit out (it definitely wasn't working initially) and arrived at something that works. Code has been updated below. I've barely reviewed the code, other than at a high conceptual level, but I manually tested my auth flows with "Remember me" checked and also unchecked. It seems to work, despite whatever browser settings the user might have set.

/**
 * REMEMBER ME FUNCTIONALITY
 * 
 * Better Auth's "Remember me" functionality controls session cookie persistence, but browser
 * behavior may not always match user expectations. Here's what you need to know:
 * 
 * EXPECTED BEHAVIOR:
 * - rememberMe: true  → User stays logged in across browser restarts
 * - rememberMe: false → User gets logged out when browser is closed
 * 
 * ACTUAL BROWSER BEHAVIOR:
 * When rememberMe=false, Better Auth sets session cookies (no Max-Age), which should expire
 * when the browser closes. However, modern browsers often restore session cookies due to:
 * - "Continue where you left off" settings
 * - Browser crash recovery
 * - Incomplete browser shutdowns (closing tabs vs. quitting the app)
 * - Mobile browser suspension rather than termination
 * - Being logged into a browser profile and having sync features enabled
 * 
 * This means users who uncheck "Remember me" might still appear logged in after returning,
 * which can be confusing and may not meet security expectations.
 * 
 * POTENTIAL SOLUTION:
 * 
 * EPHEMERAL SESSION TRACKING WITH DUAL STORAGE APPROACH:
 * The core principle is to use browser storage APIs that have different persistence
 * characteristics to track user intent and browser state:
 * 
 * - sessionStorage: Cleared when browser truly closes (ephemeral sessions)
 * - localStorage: Persists across browser restarts (persistent sessions)
 * - Combination logic: Track which storage type should be authoritative
 * 
 * Conceptual flow:
 * 1. On login, set markers in appropriate storage based on rememberMe choice
 * 2. On page load, check storage markers against active session state
 * 3. Force sign-out when storage indicates session should have expired
 * 4. Use client-side validators that run early in app initialization
 * 
 * This approach leverages the fact that sessionStorage has more reliable
 * browser-close behavior than session cookies, while localStorage provides
 * the persistence needed for "remember me" functionality.
 * 
 * IMPLEMENTATION CONSIDERATIONS:
 * - HttpOnly cookies cannot be read by JavaScript (security feature)
 * - Client-side session validation should use auth library APIs, not direct cookie access
 * - Storage markers need to be cleared on explicit sign-out to prevent conflicts
 * - Consider edge cases like manual storage clearing or multiple tabs
 */
await authClient.signIn.email(signInFields, {
  ...hooks,
  onSuccess: () => {
+    // Set appropriate session marker based on rememberMe choice
+    if (signInFields.rememberMe) {
+      setPersistentSession()
+    } else {
+      setEphemeralSession()
+    }
  },
})
export const SignOutButton = () => {
  const router = useRouter()

  return (
    <button
      onClick={async () => {
+        // Clear ephemeral session marker before signing out
+        clearEphemeralSession()

        await authClient.signOut({
          fetchOptions: {
            onSuccess: () => {
              router.push("/sign-in")
            },
          },
        })
      }}
    >
      Sign Out
    </button>
  )
}
export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
+        <EphemeralSessionValidator />
        {children}
      </body>
    </html>
  )
}
"use client"

import { useEffect } from "react"
import { useRouter } from "next/navigation"
import { authClient } from "@/lib/auth-client"
import {
  shouldForceSignOutAsync,
  clearEphemeralSession,
} from "@/lib/ephemeral-session"

/**
 * EphemeralSessionValidator
 *
 * This component should be included in your root layout to automatically
 * sign out users when they return after closing their browser (if they
 * logged in with rememberMe=false).
 *
 * It runs on every page load to check if the session should be considered
 * expired based on ephemeral session rules.
 */
export function EphemeralSessionValidator() {
  const router = useRouter()

  useEffect(() => {
    // Only run on client-side
    if (typeof window === "undefined") return

    // Async function to check if we should force sign-out
    const checkEphemeralSession = async () => {
      try {
        if (await shouldForceSignOutAsync()) {
          console.log("Forcing sign-out due to expired ephemeral session")

          // Clear the ephemeral session marker
          clearEphemeralSession()

          // Sign out the user (this will clear server-side session and cookies)
          authClient.signOut({
            fetchOptions: {
              onSuccess: () => {
                // Only redirect if we're not already on a public auth page
                const currentPath = window.location.pathname
                const isAuthPage = [
                  "/sign-in",
                  "/sign-up",
                  "/forgot-password",
                  "/verify-email",
                ].includes(currentPath)

                if (!isAuthPage) {
                  router.push("/sign-in")
                }
              },
              onError: (error) => {
                console.error("Error during forced sign-out:", error)
                // Even if sign-out fails, redirect to sign-in
                const currentPath = window.location.pathname
                const isAuthPage = [
                  "/sign-in",
                  "/sign-up",
                  "/forgot-password",
                  "/verify-email",
                ].includes(currentPath)

                if (!isAuthPage) {
                  router.push("/sign-in")
                }
              },
            },
          })
        }
      } catch (error) {
        console.error("Error checking ephemeral session:", error)
      }
    }

    checkEphemeralSession()
  }, [router])

  // This component doesn't render anything
  return null
}
/**
 * Ephemeral Session Management
 *
 * This module provides utilities to ensure users are automatically signed out
 * when they close their browser, even if session cookies persist due to browser
 * behavior like "Continue where you left off" or incomplete browser shutdowns.
 *
 * How it works:
 * 1. On login with rememberMe=false, we store a "boot marker" in sessionStorage
 * 2. On subsequent page loads, we check if this marker exists
 * 3. If missing (browser was fully closed), we force sign-out even if cookies exist
 * 4. sessionStorage is automatically cleared when browser is fully closed
 */

const BOOT_MARKER_KEY = "better-auth-boot-marker"
const PERSISTENT_SESSION_KEY = "better-auth-persistent-session"

/**
 * Generate a unique boot marker for this browser session
 */
function generateBootMarker(): string {
  return `boot-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
}

/**
 * Set a boot marker in sessionStorage to track this browser session
 * Call this after successful login when rememberMe=false
 */
export function setEphemeralSession(): void {
  if (typeof window !== "undefined") {
    const bootMarker = generateBootMarker()
    sessionStorage.setItem(BOOT_MARKER_KEY, bootMarker)
    // Remove any persistent session marker
    localStorage.removeItem(PERSISTENT_SESSION_KEY)
    console.debug("Ephemeral session marker set:", bootMarker)
  }
}

/**
 * Set a persistent session marker in localStorage
 * Call this after successful login when rememberMe=true
 */
export function setPersistentSession(): void {
  if (typeof window !== "undefined") {
    localStorage.setItem(PERSISTENT_SESSION_KEY, "true")
    // Remove any ephemeral session marker
    sessionStorage.removeItem(BOOT_MARKER_KEY)
    console.debug("Persistent session marker set")
  }
}

/**
 * Check if the user has a persistent session (rememberMe=true)
 */
export function hasPersistentSession(): boolean {
  if (typeof window === "undefined") return false
  return localStorage.getItem(PERSISTENT_SESSION_KEY) === "true"
}

/**
 * Check if the current session should be considered valid
 * Returns false if this appears to be a new browser session
 * (i.e., user closed browser and reopened)
 */
export function isEphemeralSessionValid(): boolean {
  if (typeof window === "undefined") {
    // Server-side: assume valid, let client-side handle the check
    return true
  }

  const bootMarker = sessionStorage.getItem(BOOT_MARKER_KEY)
  const isValid = bootMarker !== null

  if (!isValid) {
    console.debug("Ephemeral session invalid: no boot marker found")
  }

  return isValid
} /**
 * Clear the ephemeral session marker
 * Call this on explicit sign-out
 */
export function clearEphemeralSession(): void {
  if (typeof window !== "undefined") {
    sessionStorage.removeItem(BOOT_MARKER_KEY)
    localStorage.removeItem(PERSISTENT_SESSION_KEY)
    console.debug("Session markers cleared")
  }
}

/**
 * Check if a session exists by using Better Auth's session detection
 * This uses Better Auth's built-in mechanisms rather than trying to read HttpOnly cookies
 */
export async function hasActiveSession(): Promise<boolean> {
  if (typeof window === "undefined") return false

  try {
    // Use Better Auth's getSession to check if there's an active session
    // This will work regardless of cookie httpOnly status
    const { authClient } = await import("@/lib/auth-client")
    const { data: session } = await authClient.getSession()
    return !!session
  } catch (error) {
    console.debug("Error checking session status:", error)
    return false
  }
}

/**
 * Check if there's session data in the session_data cookie cache
 * Better Auth sets this cookie for caching when cookieCache is enabled
 * This cookie is typically not HttpOnly since it's for client-side access
 */
export function hasSessionDataCookie(): boolean {
  if (typeof window === "undefined") return false

  // Check for the Better Auth session data cache cookie
  // This cookie is used when cookieCache is enabled and should be readable
  return document.cookie.includes("better-auth.session_data=")
}

/**
 * Debug function to show all cookies
 */
export function getAllCookies(): string[] {
  if (typeof window === "undefined") return []

  return document.cookie
    .split(";")
    .map((cookie) => cookie.trim())
    .filter((cookie) => cookie.length > 0)
}

/**
 * Check if any Better Auth related cookies exist
 */
export function hasBetterAuthCookies(): boolean {
  if (typeof window === "undefined") return false

  const cookies = document.cookie
  return cookies.includes("better-auth.") || cookies.includes("better_auth")
}

/**
 * Determine if the current session should be considered expired
 * based on ephemeral session rules
 */
export function shouldForceSignOut(): boolean {
  // Never force sign-out for persistent sessions (rememberMe=true)
  if (hasPersistentSession()) {
    return false
  }

  // For ephemeral sessions, force sign-out if the ephemeral marker is missing
  // This indicates the browser was closed and sessionStorage was cleared
  return !isEphemeralSessionValid()
}

/**
 * Async version that checks for active session more reliably
 * Use this when you can handle async operations
 */
export async function shouldForceSignOutAsync(): Promise<boolean> {
  // Never force sign-out for persistent sessions (rememberMe=true)
  if (hasPersistentSession()) {
    return false
  }

  // Only check ephemeral validity if there's actually an active session
  // This prevents false positives when user is already signed out
  const hasSession = await hasActiveSession()
  if (!hasSession) {
    return false
  }

  // For ephemeral sessions with active session, force sign-out if marker is missing
  return !isEphemeralSessionValid()
}
@johncmunson commented on GitHub (Sep 16, 2025): @DimitarY I just came to the same conclusion :) In addition, I'm finding that even if you change that setting to something like "Open the new tab page", Chrome might still restore your previous session if you are signed into a Chrome profile that has sync enabled. When I created a new Chrome profile with no sync settings enabled, the "Remember me" functionality started working exactly as I would expect. I'm fine with this and don't intend to go out of my way in my application code to try and overcome browser quirks in order to force behavior that we would all expect. However, as I was curious to what a solution to this problem might look like, I had Copilot spit out some code for me. _This code has not been tested_, but it makes sense and it seems like with some refinement this strategy could potentially work. If you try this out, I'd be curious to know how it goes for you! _Edit: I continued vibe coding my way through the original code that Copilot spit out (it definitely wasn't working initially) and arrived at something that works. Code has been updated below. I've barely reviewed the code, other than at a high conceptual level, but I manually tested my auth flows with "Remember me" checked and also unchecked. It seems to work, despite whatever browser settings the user might have set._ ```ts /** * REMEMBER ME FUNCTIONALITY * * Better Auth's "Remember me" functionality controls session cookie persistence, but browser * behavior may not always match user expectations. Here's what you need to know: * * EXPECTED BEHAVIOR: * - rememberMe: true → User stays logged in across browser restarts * - rememberMe: false → User gets logged out when browser is closed * * ACTUAL BROWSER BEHAVIOR: * When rememberMe=false, Better Auth sets session cookies (no Max-Age), which should expire * when the browser closes. However, modern browsers often restore session cookies due to: * - "Continue where you left off" settings * - Browser crash recovery * - Incomplete browser shutdowns (closing tabs vs. quitting the app) * - Mobile browser suspension rather than termination * - Being logged into a browser profile and having sync features enabled * * This means users who uncheck "Remember me" might still appear logged in after returning, * which can be confusing and may not meet security expectations. * * POTENTIAL SOLUTION: * * EPHEMERAL SESSION TRACKING WITH DUAL STORAGE APPROACH: * The core principle is to use browser storage APIs that have different persistence * characteristics to track user intent and browser state: * * - sessionStorage: Cleared when browser truly closes (ephemeral sessions) * - localStorage: Persists across browser restarts (persistent sessions) * - Combination logic: Track which storage type should be authoritative * * Conceptual flow: * 1. On login, set markers in appropriate storage based on rememberMe choice * 2. On page load, check storage markers against active session state * 3. Force sign-out when storage indicates session should have expired * 4. Use client-side validators that run early in app initialization * * This approach leverages the fact that sessionStorage has more reliable * browser-close behavior than session cookies, while localStorage provides * the persistence needed for "remember me" functionality. * * IMPLEMENTATION CONSIDERATIONS: * - HttpOnly cookies cannot be read by JavaScript (security feature) * - Client-side session validation should use auth library APIs, not direct cookie access * - Storage markers need to be cleared on explicit sign-out to prevent conflicts * - Consider edge cases like manual storage clearing or multiple tabs */ ``` ```diff await authClient.signIn.email(signInFields, { ...hooks, onSuccess: () => { + // Set appropriate session marker based on rememberMe choice + if (signInFields.rememberMe) { + setPersistentSession() + } else { + setEphemeralSession() + } }, }) ``` ```diff export const SignOutButton = () => { const router = useRouter() return ( <button onClick={async () => { + // Clear ephemeral session marker before signing out + clearEphemeralSession() await authClient.signOut({ fetchOptions: { onSuccess: () => { router.push("/sign-in") }, }, }) }} > Sign Out </button> ) } ``` ```diff export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode }>) { return ( <html lang="en"> <body className={`${geistSans.variable} ${geistMono.variable} antialiased`} > + <EphemeralSessionValidator /> {children} </body> </html> ) } ``` ```ts "use client" import { useEffect } from "react" import { useRouter } from "next/navigation" import { authClient } from "@/lib/auth-client" import { shouldForceSignOutAsync, clearEphemeralSession, } from "@/lib/ephemeral-session" /** * EphemeralSessionValidator * * This component should be included in your root layout to automatically * sign out users when they return after closing their browser (if they * logged in with rememberMe=false). * * It runs on every page load to check if the session should be considered * expired based on ephemeral session rules. */ export function EphemeralSessionValidator() { const router = useRouter() useEffect(() => { // Only run on client-side if (typeof window === "undefined") return // Async function to check if we should force sign-out const checkEphemeralSession = async () => { try { if (await shouldForceSignOutAsync()) { console.log("Forcing sign-out due to expired ephemeral session") // Clear the ephemeral session marker clearEphemeralSession() // Sign out the user (this will clear server-side session and cookies) authClient.signOut({ fetchOptions: { onSuccess: () => { // Only redirect if we're not already on a public auth page const currentPath = window.location.pathname const isAuthPage = [ "/sign-in", "/sign-up", "/forgot-password", "/verify-email", ].includes(currentPath) if (!isAuthPage) { router.push("/sign-in") } }, onError: (error) => { console.error("Error during forced sign-out:", error) // Even if sign-out fails, redirect to sign-in const currentPath = window.location.pathname const isAuthPage = [ "/sign-in", "/sign-up", "/forgot-password", "/verify-email", ].includes(currentPath) if (!isAuthPage) { router.push("/sign-in") } }, }, }) } } catch (error) { console.error("Error checking ephemeral session:", error) } } checkEphemeralSession() }, [router]) // This component doesn't render anything return null } ``` ```ts /** * Ephemeral Session Management * * This module provides utilities to ensure users are automatically signed out * when they close their browser, even if session cookies persist due to browser * behavior like "Continue where you left off" or incomplete browser shutdowns. * * How it works: * 1. On login with rememberMe=false, we store a "boot marker" in sessionStorage * 2. On subsequent page loads, we check if this marker exists * 3. If missing (browser was fully closed), we force sign-out even if cookies exist * 4. sessionStorage is automatically cleared when browser is fully closed */ const BOOT_MARKER_KEY = "better-auth-boot-marker" const PERSISTENT_SESSION_KEY = "better-auth-persistent-session" /** * Generate a unique boot marker for this browser session */ function generateBootMarker(): string { return `boot-${Date.now()}-${Math.random().toString(36).substr(2, 9)}` } /** * Set a boot marker in sessionStorage to track this browser session * Call this after successful login when rememberMe=false */ export function setEphemeralSession(): void { if (typeof window !== "undefined") { const bootMarker = generateBootMarker() sessionStorage.setItem(BOOT_MARKER_KEY, bootMarker) // Remove any persistent session marker localStorage.removeItem(PERSISTENT_SESSION_KEY) console.debug("Ephemeral session marker set:", bootMarker) } } /** * Set a persistent session marker in localStorage * Call this after successful login when rememberMe=true */ export function setPersistentSession(): void { if (typeof window !== "undefined") { localStorage.setItem(PERSISTENT_SESSION_KEY, "true") // Remove any ephemeral session marker sessionStorage.removeItem(BOOT_MARKER_KEY) console.debug("Persistent session marker set") } } /** * Check if the user has a persistent session (rememberMe=true) */ export function hasPersistentSession(): boolean { if (typeof window === "undefined") return false return localStorage.getItem(PERSISTENT_SESSION_KEY) === "true" } /** * Check if the current session should be considered valid * Returns false if this appears to be a new browser session * (i.e., user closed browser and reopened) */ export function isEphemeralSessionValid(): boolean { if (typeof window === "undefined") { // Server-side: assume valid, let client-side handle the check return true } const bootMarker = sessionStorage.getItem(BOOT_MARKER_KEY) const isValid = bootMarker !== null if (!isValid) { console.debug("Ephemeral session invalid: no boot marker found") } return isValid } /** * Clear the ephemeral session marker * Call this on explicit sign-out */ export function clearEphemeralSession(): void { if (typeof window !== "undefined") { sessionStorage.removeItem(BOOT_MARKER_KEY) localStorage.removeItem(PERSISTENT_SESSION_KEY) console.debug("Session markers cleared") } } /** * Check if a session exists by using Better Auth's session detection * This uses Better Auth's built-in mechanisms rather than trying to read HttpOnly cookies */ export async function hasActiveSession(): Promise<boolean> { if (typeof window === "undefined") return false try { // Use Better Auth's getSession to check if there's an active session // This will work regardless of cookie httpOnly status const { authClient } = await import("@/lib/auth-client") const { data: session } = await authClient.getSession() return !!session } catch (error) { console.debug("Error checking session status:", error) return false } } /** * Check if there's session data in the session_data cookie cache * Better Auth sets this cookie for caching when cookieCache is enabled * This cookie is typically not HttpOnly since it's for client-side access */ export function hasSessionDataCookie(): boolean { if (typeof window === "undefined") return false // Check for the Better Auth session data cache cookie // This cookie is used when cookieCache is enabled and should be readable return document.cookie.includes("better-auth.session_data=") } /** * Debug function to show all cookies */ export function getAllCookies(): string[] { if (typeof window === "undefined") return [] return document.cookie .split(";") .map((cookie) => cookie.trim()) .filter((cookie) => cookie.length > 0) } /** * Check if any Better Auth related cookies exist */ export function hasBetterAuthCookies(): boolean { if (typeof window === "undefined") return false const cookies = document.cookie return cookies.includes("better-auth.") || cookies.includes("better_auth") } /** * Determine if the current session should be considered expired * based on ephemeral session rules */ export function shouldForceSignOut(): boolean { // Never force sign-out for persistent sessions (rememberMe=true) if (hasPersistentSession()) { return false } // For ephemeral sessions, force sign-out if the ephemeral marker is missing // This indicates the browser was closed and sessionStorage was cleared return !isEphemeralSessionValid() } /** * Async version that checks for active session more reliably * Use this when you can handle async operations */ export async function shouldForceSignOutAsync(): Promise<boolean> { // Never force sign-out for persistent sessions (rememberMe=true) if (hasPersistentSession()) { return false } // Only check ephemeral validity if there's actually an active session // This prevents false positives when user is already signed out const hasSession = await hasActiveSession() if (!hasSession) { return false } // For ephemeral sessions with active session, force sign-out if marker is missing return !isEphemeralSessionValid() } ```
Author
Owner

@johncmunson commented on GitHub (Sep 16, 2025):

@ping-maxwell It's unclear to me whether or not the better-auth team would have much ability to improve the "Remember me" functionality at the library level. Even if it's possible, normalizing browser quirks like this may be outside the scope of what the team is interested in maintaining. Or you guys might have the viewpoint that overriding browser settings is the responsibility of application code, not the auth library.

Regardless though, I feel that at a minimum the docs could be expanded to provide more clarity and insight into what app developers should expect to encounter when adding "Remember me" functionality to their auth flow. Achieving a "Remember me" behavior that matched my initial expectations of how it should work (and, how I think most people would expect it to work) turned out to be far more complex than I anticipated, which was a big surprise.

@johncmunson commented on GitHub (Sep 16, 2025): @ping-maxwell It's unclear to me whether or not the better-auth team would have much ability to improve the "Remember me" functionality at the library level. Even if it's possible, normalizing browser quirks like this may be outside the scope of what the team is interested in maintaining. Or you guys might have the viewpoint that overriding browser settings is the responsibility of application code, not the auth library. Regardless though, I feel that at a minimum the docs could be expanded to provide more clarity and insight into what app developers should expect to encounter when adding "Remember me" functionality to their auth flow. Achieving a "Remember me" behavior that matched my initial expectations of how it should work (and, how I think most people would expect it to work) turned out to be far more complex than I anticipated, which was a big surprise.
Author
Owner

@ping-maxwell commented on GitHub (Sep 17, 2025):

Hmm, my opinion is that our current solution is fine. I'll ask the team and get back to you guys if I get a response.

@ping-maxwell commented on GitHub (Sep 17, 2025): Hmm, my opinion is that our current solution is fine. I'll ask the team and get back to you guys if I get a response.
Author
Owner

@dosubot[bot] commented on GitHub (Dec 18, 2025):

Hi, @DimitarY. I'm Dosu (https://dosu.dev), and I'm helping the better-auth team manage their backlog and am marking this issue as stale.

Issue Summary:

  • You reported that setting rememberMe to false does not sign out users on browser close as expected.
  • The discrepancy between code and docs on the default value of rememberMe was noted.
  • It was clarified that session cookies should expire on browser close, but browser settings like "continue where you left off" can cause sessions to persist.
  • Other users confirmed similar behavior and shared client-side workarounds using sessionStorage/localStorage.
  • Maintainers acknowledged browser limitations and plan to improve documentation to clarify expected behavior.

Next Steps:

  • Please confirm if this issue is still relevant with the latest version of better-auth by commenting here.
  • If no further activity occurs, I will automatically close this issue in 7 days.

Thanks for your understanding and contribution!

@dosubot[bot] commented on GitHub (Dec 18, 2025): Hi, @DimitarY. I'm Dosu (https://dosu.dev), and I'm helping the better-auth team manage their backlog and am marking this issue as stale. **Issue Summary:** - You reported that setting `rememberMe` to false does not sign out users on browser close as expected. - The discrepancy between code and docs on the default value of `rememberMe` was noted. - It was clarified that session cookies should expire on browser close, but browser settings like "continue where you left off" can cause sessions to persist. - Other users confirmed similar behavior and shared client-side workarounds using sessionStorage/localStorage. - Maintainers acknowledged browser limitations and plan to improve documentation to clarify expected behavior. **Next Steps:** - Please confirm if this issue is still relevant with the latest version of better-auth by commenting here. - If no further activity occurs, I will automatically close this issue in 7 days. Thanks for your understanding and contribution!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#1393