[GH-ISSUE #8060] [BUG] multi-session: listDeviceSessions() does not include session created via OAuth (Google/social) login #10984

Open
opened 2026-04-13 07:22:11 -05:00 by GiteaMirror · 1 comment
Owner

Originally created by @rahultumma-jnext on GitHub (Feb 19, 2026).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/8060

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Configure Better Auth with the multi-session plugin and at least one social provider (e.g. Google).
  2. Sign in via OAuth (e.g. authClient.signIn.social({ provider: 'google', callbackURL: '...' })).
  3. After redirect back to the app, call authClient.getSession() → returns the session (correct).
  4. Call authClient.multiSession.listDeviceSessions() → the session from step 2 is not in the list.
  5. Sign in with email/password. That new session does appear in listDeviceSessions().

Current vs. Expected behavior

Current behavior:

  • After OAuth (Google/Apple) login, getSession() returns the new session.
  • listDeviceSessions() does not include that OAuth session (only sessions with a _multi- cookie appear).
  • Email/password sign-in sessions do appear in listDeviceSessions().

Expected behavior:

  • After OAuth login, the new session should also get a multi-session cookie so it appears in listDeviceSessions(), consistent with credential sign-in.

What version of Better Auth are you using?

1.4.18

System info

{
  "system": {
    "platform": "linux",
    "arch": "x64",
    "version": "#96~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Fri Jan 16 13:19:05 UTC 2",
    "release": "6.8.0-94-generic",
    "cpuCount": 8,
    "cpuModel": "Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz",
    "totalMemory": "31.09 GB",
    "freeMemory": "10.07 GB"
  },
  "node": {
    "version": "v22.22.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "10.9.4"
  },
  "frameworks": [
    { "name": "next", "version": "15.3.6" },
    { "name": "react", "version": "19.1.0" }
  ],
  "databases": [
    { "name": "pg", "version": "^8.16.3" }
  ],
  "betterAuth": {
    "version": "1.4.18",
    "config": {
      "user": { "modelName": "users" },
      "account": { "modelName": "account" },
      "session": {
        "modelName": "session",
        "storeSessionInDatabase": true,
        "expiresIn": 2592000,
        "updateAge": 172800
      },
      "baseURL": "http://localhost:4000",
      "basePath": "/api/auth",
      "secret": "[REDACTED]",
      "socialProviders": { "google": {}, "apple": {} },
      "emailAndPassword": { "enabled": true, "autoSignIn": true },
      "plugins": [
        { "name": "open-api" },
        { "name": "multi-session", "config": { "options": { "maximumSessions": 20 }, "hooks": { "after": [{}, {}] } } },
        { "name": "custom-session", "config": { "options": { "shouldMutateListDeviceSessionsEndpoint": true } } },
        { "name": "email-otp" },
        { "name": "magic-link" },
        { "name": "organization" }
      ],
      "advanced": {
        "cookiePrefix": "serplens",
        "crossSubDomainCookies": { "enabled": true },
        "defaultCookieAttributes": { "secure": true, "sameSite": "none" }
      }
    }
  }
}

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

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth"
import { multiSession } from "better-auth/plugins"
// ... your database adapter (drizzleAdapter, etc.)

export const auth = betterAuth({
  database: drizzleAdapter(db, { provider: 'pg', schema, usePlural: false }),
  baseURL: process.env.BETTER_AUTH_URL,
  basePath: "/api/auth",
  secret: process.env.BETTER_AUTH_SECRET,
  socialProviders: {
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    },
  },
  plugins: [
    multiSession({ maximumSessions: 20 }),
  ],
})

Additional context

Root cause (from tracing the code): The OAuth callback (packages/better-auth/src/api/routes/callback.ts) calls setSessionCookie() then throw c.redirect(toRedirectTo). In packages/better-auth/src/api/to-auth-endpoints.ts the endpoint runner only catches APIError in the .catch(); a thrown redirect is rethrown, so after hooks never run. The multi-session plugin sets the _multi- cookie in an after hook. That hook does not run on OAuth callback, so the OAuth session never gets the cookie. listDeviceSessions() only reads _multi- cookies, so the OAuth session is missing. getSession() works because it uses the main session cookie set before the redirect.

Relevant source paths:

  • callback: packages/better-auth/src/api/routes/callback.ts (setSessionCookie then throw c.redirect)
  • endpoint runner: packages/better-auth/src/api/to-auth-endpoints.ts (.catch() only handles APIError)
  • multi-session hook: packages/better-auth/src/plugins/multi-session/index.ts (after hook sets _multi- cookie)

Suggested fix: In to-auth-endpoints.ts, treat a thrown redirect (e.g. Response with status 3xx) like APIError in the catch: don't rethrow; set context.returned and context.responseHeaders from the redirect, run runAfterHooks(), then return the redirect with merged headers so the multi-session plugin can add its Set-Cookie.

Workaround: Merge current session (from getSession()) into listDeviceSessions() result in the app so the active session always appears in the UI.

Originally created by @rahultumma-jnext on GitHub (Feb 19, 2026). Original GitHub issue: https://github.com/better-auth/better-auth/issues/8060 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Configure Better Auth with the multi-session plugin and at least one social provider (e.g. Google). 2. Sign in via OAuth (e.g. authClient.signIn.social({ provider: 'google', callbackURL: '...' })). 3. After redirect back to the app, call authClient.getSession() → returns the session (correct). 4. Call authClient.multiSession.listDeviceSessions() → the session from step 2 is not in the list. 5. Sign in with email/password. That new session does appear in listDeviceSessions(). ### Current vs. Expected behavior Current behavior: - After OAuth (Google/Apple) login, getSession() returns the new session. - listDeviceSessions() does not include that OAuth session (only sessions with a *_multi-* cookie appear). - Email/password sign-in sessions do appear in listDeviceSessions(). Expected behavior: - After OAuth login, the new session should also get a multi-session cookie so it appears in listDeviceSessions(), consistent with credential sign-in. ### What version of Better Auth are you using? 1.4.18 ### System info ```bash { "system": { "platform": "linux", "arch": "x64", "version": "#96~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Fri Jan 16 13:19:05 UTC 2", "release": "6.8.0-94-generic", "cpuCount": 8, "cpuModel": "Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz", "totalMemory": "31.09 GB", "freeMemory": "10.07 GB" }, "node": { "version": "v22.22.0", "env": "development" }, "packageManager": { "name": "npm", "version": "10.9.4" }, "frameworks": [ { "name": "next", "version": "15.3.6" }, { "name": "react", "version": "19.1.0" } ], "databases": [ { "name": "pg", "version": "^8.16.3" } ], "betterAuth": { "version": "1.4.18", "config": { "user": { "modelName": "users" }, "account": { "modelName": "account" }, "session": { "modelName": "session", "storeSessionInDatabase": true, "expiresIn": 2592000, "updateAge": 172800 }, "baseURL": "http://localhost:4000", "basePath": "/api/auth", "secret": "[REDACTED]", "socialProviders": { "google": {}, "apple": {} }, "emailAndPassword": { "enabled": true, "autoSignIn": true }, "plugins": [ { "name": "open-api" }, { "name": "multi-session", "config": { "options": { "maximumSessions": 20 }, "hooks": { "after": [{}, {}] } } }, { "name": "custom-session", "config": { "options": { "shouldMutateListDeviceSessionsEndpoint": true } } }, { "name": "email-otp" }, { "name": "magic-link" }, { "name": "organization" } ], "advanced": { "cookiePrefix": "serplens", "crossSubDomainCookies": { "enabled": true }, "defaultCookieAttributes": { "secure": true, "sameSite": "none" } } } } } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" import { multiSession } from "better-auth/plugins" // ... your database adapter (drizzleAdapter, etc.) export const auth = betterAuth({ database: drizzleAdapter(db, { provider: 'pg', schema, usePlural: false }), baseURL: process.env.BETTER_AUTH_URL, basePath: "/api/auth", secret: process.env.BETTER_AUTH_SECRET, socialProviders: { google: { clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET, }, }, plugins: [ multiSession({ maximumSessions: 20 }), ], }) ``` ### Additional context Root cause (from tracing the code): The OAuth callback (packages/better-auth/src/api/routes/callback.ts) calls setSessionCookie() then throw c.redirect(toRedirectTo). In packages/better-auth/src/api/to-auth-endpoints.ts the endpoint runner only catches APIError in the .catch(); a thrown redirect is rethrown, so after hooks never run. The multi-session plugin sets the *_multi-<token>* cookie in an after hook. That hook does not run on OAuth callback, so the OAuth session never gets the cookie. listDeviceSessions() only reads *_multi-* cookies, so the OAuth session is missing. getSession() works because it uses the main session cookie set before the redirect. Relevant source paths: - callback: packages/better-auth/src/api/routes/callback.ts (setSessionCookie then throw c.redirect) - endpoint runner: packages/better-auth/src/api/to-auth-endpoints.ts (.catch() only handles APIError) - multi-session hook: packages/better-auth/src/plugins/multi-session/index.ts (after hook sets *_multi-* cookie) Suggested fix: In to-auth-endpoints.ts, treat a thrown redirect (e.g. Response with status 3xx) like APIError in the catch: don't rethrow; set context.returned and context.responseHeaders from the redirect, run runAfterHooks(), then return the redirect with merged headers so the multi-session plugin can add its Set-Cookie. Workaround: Merge current session (from getSession()) into listDeviceSessions() result in the app so the active session always appears in the UI.
GiteaMirror added the coreoauthbug labels 2026-04-13 07:22:11 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Feb 19, 2026):

Thanks for the detailed bug report! I investigated the code and found something interesting.

Your root cause analysis may be incorrect. Looking at to-auth-endpoints.ts, after hooks do execute when redirects are thrown because redirects are APIError instances that get caught and converted to response objects before executeAfterHooks() runs. The catch block handles APIError specifically and only rethrows non-APIError exceptions.

However, there are several real multi-session OAuth issues that might be causing your problem:

  1. Server-side OAuth Set-Cookie header loss - If you're initiating OAuth from the server side (not client-side), Set-Cookie headers can be lost during immediate redirects. The browser never receives them.

  2. Off-by-one counting error - There's a known bug where >= should be > in the session limit check, which can prevent cookies from being set.

Suggested debugging steps:

  1. Check browser DevTools Network tab during OAuth callback to verify if the _multi-* Set-Cookie header is present in the response
  2. If it's missing from the response entirely, the issue is likely in how the OAuth flow is initiated or how redirects are handled by your framework
  3. If it's present but not stored, it could be a cookie attribute issue (domain/secure/sameSite mismatch)

If you're initiating OAuth server-side, try using client-side authClient.signIn.social() instead, which ensures Set-Cookie headers are properly processed by the browser.

Your workaround of merging getSession() into listDeviceSessions() is reasonable for now.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Chat with Dosu Open in Cursor Join Discord Share on X

<!-- gh-comment-id:3925152963 --> @dosubot[bot] commented on GitHub (Feb 19, 2026): <!-- Answer --> Thanks for the detailed bug report! I investigated the code and found something interesting. **Your root cause analysis may be incorrect.** Looking at [to-auth-endpoints.ts](https://github.com/better-auth/better-auth/blob/07d80082fce33871f543f5d5191cfbf0b22adb21/packages/better-auth/src/api/to-auth-endpoints.ts#L119-L148), after hooks *do* execute when redirects are thrown because redirects are `APIError` instances that get caught and converted to response objects before `executeAfterHooks()` runs. The catch block handles `APIError` specifically and only rethrows non-APIError exceptions. However, there are several **real multi-session OAuth issues** that might be causing your problem: 1. **Server-side OAuth Set-Cookie header loss** - If you're initiating OAuth from the server side (not client-side), [Set-Cookie headers can be lost during immediate redirects](https://github.com/better-auth/better-auth/issues/7131#issuecomment-3710590104). The browser never receives them. 2. **Off-by-one counting error** - There's a [known bug where `>=` should be `>`](https://github.com/better-auth/better-auth/issues/4490#issuecomment-3263232395) in the session limit check, which can prevent cookies from being set. **Suggested debugging steps:** 1. Check browser DevTools Network tab during OAuth callback to verify if the `_multi-*` Set-Cookie header is present in the response 2. If it's missing from the response entirely, the issue is likely in how the OAuth flow is initiated or how redirects are handled by your framework 3. If it's present but not stored, it could be a cookie attribute issue (domain/secure/sameSite mismatch) **If you're initiating OAuth server-side**, try using [client-side `authClient.signIn.social()`](https://github.com/better-auth/better-auth/issues/6544) instead, which ensures Set-Cookie headers are properly processed by the browser. Your workaround of merging `getSession()` into `listDeviceSessions()` is reasonable for now. <!-- 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/f82b9135-f97d-4642-9908-e1981d6e9b3b?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/f82b9135-f97d-4642-9908-e1981d6e9b3b?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/f82b9135-f97d-4642-9908-e1981d6e9b3b?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/f82b9135-f97d-4642-9908-e1981d6e9b3b?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/f82b9135-f97d-4642-9908-e1981d6e9b3b?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/f82b9135-f97d-4642-9908-e1981d6e9b3b?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/f82b9135-f97d-4642-9908-e1981d6e9b3b?feedback_type=other)</sup> [![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=Thanks%20for%20the%20detailed%20bug%20report%21%20I%20investigated%20the%20code%20and%20found%20something%20interesting.%0A%0A%2A%2AYour%20root%20cause%20analysis%20may%20be%20incorrect.%2A%2A%20Looking%20at%20%5Bto-auth-endpoints.ts%5D%28https%3A//github.com/better-auth/better-auth/blob/07d80082fce33871f543f5d5191cfbf0b22adb21/packages/better-auth/src/api/to-auth-endpoints.ts%23L119-L148%29%2C%20after%20hooks%20%2Ado%2A%20execute%20when%20redirects%20are%20thrown%20because%20redirects%20are%20%60APIError%60%20instances%20that%20get%20caught%20and%20converted%20to%20response%20objects%20before%20%60executeAfterHooks%28%29%60%20runs.%20The%20catch%20block%20handles%20%60APIError%60%20specifically%20and%20only%20rethrows%20non-APIError%20exceptions.%0A%0AHowever%2C%20there%20are%20several%20%2A%2Areal%20multi-session%20OAuth%20issues%2A%2A%20that%20might%20be%20causing%20your%20problem%3A%0A%0A1.%20%2A%2AServer-side%20OAuth%20Set-Cookie%20header%20loss%2A%2A%20-%20If%20you%27re%20initiating%20OAuth%20from%20the%20server%20side%20%28not%20client-side%29%2C%20%5BSet-Cookie%20headers%20can%20be%20lost%20during%20immediate%20redirects%5D%28https%3A//github.com/better-auth/better-auth/issues/7131%23issuecomment-3710590104%29.%20The%20browser%20never%20receives%20them.%0A%0A2.%20%2A%2AOff-by-one%20counting%20error%2A%2A%20-%20There%27s%20a%20%5Bknown%20bug%20where%20%60%3E%3D%60%20should%20be%20%60%3E%60%5D%28https%3A//github.com/better-auth/better-auth/issues/4490%23issuecomment-3263232395%29%20in%20the%20session%20limit%20check%2C%20which%20can%20prevent%20cookies%20from%20being%20set.%0A%0A%2A%2ASuggested%20debugging%20steps%3A%2A%2A%0A1.%20Check%20browser%20DevTools%20Network%20tab%20during%20OAuth%20callback%20to%20verify%20if%20the%20%60_multi-%2A%60%20Set-Cookie%20header%20is%20present%20in%20the%20response%0A2.%20If%20it%27s%20missing%20from%20the%20response%20entirely%2C%20the%20issue%20is%20likely%20in%20how%20the%20OAuth%20flow%20is%20initiated%20or%20how%20redirects%20are%20handled%20by%20your%20framework%0A3.%20If%20it%27s%20present%20but%20not%20stored%2C%20it%20could%20be%20a%20cookie%20attribute%20issue%20%28domain/secure/sameSite%20mismatch%29%0A%0A%2A%2AIf%20you%27re%20initiating%20OAuth%20server-side%2A%2A%2C%20try%20using%20%5Bclient-side%20%60authClient.signIn.social%28%29%60%5D%28https%3A//github.com/better-auth/better-auth/issues/6544%29%20instead%2C%20which%20ensures%20Set-Cookie%20headers%20are%20properly%20processed%20by%20the%20browser.%0A%0AYour%20workaround%20of%20merging%20%60getSession%28%29%60%20into%20%60listDeviceSessions%28%29%60%20is%20reasonable%20for%20now.)&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/8060)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#10984