cannot continue oauth2 flow after signing in #2905

Open
opened 2026-03-13 10:26:49 -05:00 by GiteaMirror · 8 comments
Owner

Originally created by @till on GitHub (Feb 21, 2026).

Originally assigned to: @himself65 on GitHub.

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

So, I've been trying to use 1.5 (latest) — I know, I know — because I need the compat for OAuth 2.0 clients (my current integrations do not support PKCE).

This is what I have in my project:

├── @better-auth/cli@1.5.0-beta.13
├── @better-auth/oauth-provider@1.5.0-beta.16
├── better-auth@1.5.0-beta.16

I've tried various things; my latest attempt is using the client side SDK:

                const { data, error } = await authClient.signIn.email({
                  email: document.getElementById("login-email").value,
                  password: document.getElementById("login-password").value,
                  callbackURL: "${redirectUrl || "/profile"}",
                });

This results in a JavaScript popup ("UNAUTHORIZED"), I see an XHR request fail with 401. The oauth_query is automatically added to the request (line breaks added by me):

response_type=code
&client_id=...
&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Foauth2-test-redirect
&scope=openid+profile
&state=...
&prompt=login
&exp=1771627488&sig=f0Ngq9R%2Fq28mHYBC08z3ZOGzK3HTO8nHUhHlSneoMY4%3D

My server-side attempt included adding the oauth_query to the call — I think there is a bug in better-call which I temporarily patched to even get that far. The result is the same, I get unauthorized — but when I reload I have a session/I am logged in:

  {"level":"error","time":1771624768436,                                                                                                                                                                                                         
    "err":{                                                                                                                                                                                                                                    
      "type":"APIError",                                                                                                                                                                                                                         
      "message":"Unauthorized",                                                                                                                                                                                                                  
      "stack":"APIError\n    at new APIError (file:///projectnode_modules/@better-auth/core/dist/error/index.mjs:15:3)\n    at APIError.from
  (file:///project/node_modules/@better-auth/core/dist/error/index.mjs:21:10)\n    at
  file:///project/node_modules/better-auth/dist/api/routes/session.mjs:294:40\n    at process.processTicksAndRejections (node:internal/process/task_queues:103:5)\n    at async Object.internalHandler
  [as handler] (file:///project/node_modules/better-call/dist/middleware.mjs:17:21)\n    at async createInternalContext
  (file:///project/node_modules/better-call/dist/context.mjs:85:20)\n    at async internalHandler
  (file:///project/node_modules/better-call/dist/middleware.mjs:11:27)\n    at async createInternalContext
  (file:///project/node_modules/better-call/dist/context.mjs:85:20)\n    at async tryCatch (file:///project/node_modules/better-call/dist/utils.mjs:61:10)",
      "status":"UNAUTHORIZED",
      "body":{"message":"Unauthorized","code":"UNAUTHORIZED"},
      "statusCode":401
    }
  }

I think authorizeEndpoint called by the after-hook runs through sessionMiddleware, which creates a new internal context (createInternalContext) that doesn't inherit ctx.context.session set by the after-hook.


The better-call issue:

"message":"Cannot redefine property: Symbol(better-call:api-error-headers)"

I patched better-call/dist/middleware.mjs:

  Object.defineProperty(e, kAPIErrorHeaderSymbol, {
    enumerable: false,
-  configurable: false,
+ configurable: true, 
    get() {
      return internalContext.responseHeaders;
    }
  });

Current vs. Expected behavior

The current state is that the flow basically stops at the login. I don't get redirected to the redirect URL I supplied when I start the request in the client.

What version of Better Auth are you using?

1.5.0-beta16

System info

I can't run this command, it fails:


need to install the following packages:
auth@1.5.0-beta.17
Ok to proceed? (y) y

npm error code EUNSUPPORTEDPROTOCOL
npm error Unsupported URL Type "workspace:": workspace:*

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

Other

Auth config (if applicable)

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

Additional context

No response

Originally created by @till on GitHub (Feb 21, 2026). Originally assigned to: @himself65 on GitHub. ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce So, I've been trying to use 1.5 (latest) — I know, I know — because I need the compat for OAuth 2.0 clients (my current integrations do not support PKCE). This is what I have in my project: ``` ├── @better-auth/cli@1.5.0-beta.13 ├── @better-auth/oauth-provider@1.5.0-beta.16 ├── better-auth@1.5.0-beta.16 ``` I've tried various things; my latest attempt is using the client side SDK: ```js const { data, error } = await authClient.signIn.email({ email: document.getElementById("login-email").value, password: document.getElementById("login-password").value, callbackURL: "${redirectUrl || "/profile"}", }); ``` This results in a JavaScript popup ("UNAUTHORIZED"), I see an XHR request fail with `401`. The `oauth_query` is automatically added to the request (line breaks added by me): ``` response_type=code &client_id=... &redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Foauth2-test-redirect &scope=openid+profile &state=... &prompt=login &exp=1771627488&sig=f0Ngq9R%2Fq28mHYBC08z3ZOGzK3HTO8nHUhHlSneoMY4%3D ``` My server-side attempt included adding the `oauth_query` to the call — I think there is a bug in `better-call` which I temporarily patched to even get that far. The result is the same, I get unauthorized — but when I reload I have a session/I am logged in: ```json {"level":"error","time":1771624768436, "err":{ "type":"APIError", "message":"Unauthorized", "stack":"APIError\n at new APIError (file:///projectnode_modules/@better-auth/core/dist/error/index.mjs:15:3)\n at APIError.from (file:///project/node_modules/@better-auth/core/dist/error/index.mjs:21:10)\n at file:///project/node_modules/better-auth/dist/api/routes/session.mjs:294:40\n at process.processTicksAndRejections (node:internal/process/task_queues:103:5)\n at async Object.internalHandler [as handler] (file:///project/node_modules/better-call/dist/middleware.mjs:17:21)\n at async createInternalContext (file:///project/node_modules/better-call/dist/context.mjs:85:20)\n at async internalHandler (file:///project/node_modules/better-call/dist/middleware.mjs:11:27)\n at async createInternalContext (file:///project/node_modules/better-call/dist/context.mjs:85:20)\n at async tryCatch (file:///project/node_modules/better-call/dist/utils.mjs:61:10)", "status":"UNAUTHORIZED", "body":{"message":"Unauthorized","code":"UNAUTHORIZED"}, "statusCode":401 } } ``` I **think** `authorizeEndpoint` called by the after-hook runs through `sessionMiddleware`, which creates a new internal context (`createInternalContext`) that doesn't inherit `ctx.context.session` set by the after-hook. --- The better-call issue: ``` "message":"Cannot redefine property: Symbol(better-call:api-error-headers)" ``` I patched `better-call/dist/middleware.mjs`: ```diff Object.defineProperty(e, kAPIErrorHeaderSymbol, { enumerable: false, - configurable: false, + configurable: true, get() { return internalContext.responseHeaders; } }); ``` ### Current vs. Expected behavior The current state is that the flow basically stops at the login. I don't get redirected to the redirect URL I supplied when I start the request in the client. ### What version of Better Auth are you using? 1.5.0-beta16 ### System info ```bash I can't run this command, it fails: need to install the following packages: auth@1.5.0-beta.17 Ok to proceed? (y) y npm error code EUNSUPPORTEDPROTOCOL npm error Unsupported URL Type "workspace:": workspace:* ``` ### Which area(s) are affected? (Select all that apply) Other ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" export const auth = betterAuth({ emailAndPassword: { enabled: true }, }); ``` ### Additional context _No response_
GiteaMirror added the bug label 2026-03-13 10:26:49 -05:00
Author
Owner

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

Hi @till! I'm Dosu and I’m helping the better-auth team.

This is a known regression in how the OAuth flow handles the redirect after sign-in. The problem is that the OAuth provider plugin returns a JSON response with a redirect URL after sign-in, but the default frontend sign-in handler doesn't process this response to continue the OAuth flow.

Solution: Update your sign-in handler to explicitly handle the redirect response:

const { data, error } = await authClient.signIn.email({
  email: document.getElementById("login-email").value,
  password: document.getElementById("login-password").value,
  fetchOptions: {
    onSuccess: async (context) => {
      if (context.data?.redirect && context.data?.url) {
        window.location.href = context.data.url;
      }
    }
  }
});

This ensures the OAuth flow continues by redirecting to the authorization endpoint with the proper session context.

PRs #7960 and #7811 should address this redirection issue more comprehensively - you might want to check if they've been merged into a newer beta.

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

@dosubot[bot] commented on GitHub (Feb 21, 2026): <!-- Greeting --> Hi @till! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> This is a [known regression](https://github.com/better-auth/better-auth/issues/7558) in how the OAuth flow handles the redirect after sign-in. The problem is that the OAuth provider plugin returns a JSON response with a redirect URL after sign-in, but the default frontend sign-in handler doesn't process this response to continue the OAuth flow. **Solution**: Update your sign-in handler to explicitly handle the redirect response: ```js const { data, error } = await authClient.signIn.email({ email: document.getElementById("login-email").value, password: document.getElementById("login-password").value, fetchOptions: { onSuccess: async (context) => { if (context.data?.redirect && context.data?.url) { window.location.href = context.data.url; } } } }); ``` This ensures the OAuth flow continues by redirecting to the authorization endpoint with the proper session context. PRs [#7960](https://github.com/better-auth/better-auth/pull/7960) and [#7811](https://github.com/better-auth/better-auth/pull/7811) should address this redirection issue more comprehensively - you might want to check if they've been merged into a newer beta. <!-- 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/126c48c2-5f6c-4b5e-9e8a-2afba5900f74?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/126c48c2-5f6c-4b5e-9e8a-2afba5900f74?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/126c48c2-5f6c-4b5e-9e8a-2afba5900f74?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/126c48c2-5f6c-4b5e-9e8a-2afba5900f74?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/126c48c2-5f6c-4b5e-9e8a-2afba5900f74?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/126c48c2-5f6c-4b5e-9e8a-2afba5900f74?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/126c48c2-5f6c-4b5e-9e8a-2afba5900f74?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=This%20is%20a%20%5Bknown%20regression%5D%28https%3A//github.com/better-auth/better-auth/issues/7558%29%20in%20how%20the%20OAuth%20flow%20handles%20the%20redirect%20after%20sign-in.%20The%20problem%20is%20that%20the%20OAuth%20provider%20plugin%20returns%20a%20JSON%20response%20with%20a%20redirect%20URL%20after%20sign-in%2C%20but%20the%20default%20frontend%20sign-in%20handler%20doesn%27t%20process%20this%20response%20to%20continue%20the%20OAuth%20flow.%0A%0A%2A%2ASolution%2A%2A%3A%20Update%20your%20sign-in%20handler%20to%20explicitly%20handle%20the%20redirect%20response%3A%0A%0A%60%60%60js%0Aconst%20%7B%20data%2C%20error%20%7D%20%3D%20await%20authClient.signIn.email%28%7B%0A%20%20email%3A%20document.getElementById%28%22login-email%22%29.value%2C%0A%20%20password%3A%20document.getElementById%28%22login-password%22%29.value%2C%0A%20%20fetchOptions%3A%20%7B%0A%20%20%20%20onSuccess%3A%20async%20%28context%29%20%3D%3E%20%7B%0A%20%20%20%20%20%20if%20%28context.data%3F.redirect%20%26%26%20context.data%3F.url%29%20%7B%0A%20%20%20%20%20%20%20%20window.location.href%20%3D%20context.data.url%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%20%20%7D%0A%7D%29%3B%0A%60%60%60%0A%0AThis%20ensures%20the%20OAuth%20flow%20continues%20by%20redirecting%20to%20the%20authorization%20endpoint%20with%20the%20proper%20session%20context.%0A%0APRs%20%5B%237960%5D%28https%3A//github.com/better-auth/better-auth/pull/7960%29%20and%20%5B%237811%5D%28https%3A//github.com/better-auth/better-auth/pull/7811%29%20should%20address%20this%20redirection%20issue%20more%20comprehensively%20-%20you%20might%20want%20to%20check%20if%20they%27ve%20been%20merged%20into%20a%20newer%20beta.)&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/8088)
Author
Owner

@till commented on GitHub (Feb 21, 2026):

The workaround doesn't work, because there's a 401 in the mix. No url (or uri) is returned.

@till commented on GitHub (Feb 21, 2026): The workaround doesn't work, because there's a 401 in the mix. No `url` (or `uri`) is returned.
Author
Owner

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

Thanks for clarifying. The 401 happening before any URL is returned suggests the after-hook is failing to find the session after sign-in. A few things to check:

  1. Session database storage - The OAuth provider plugin requires session.storeSessionInDatabase: true. Without this, the after-hook can't look up the session:

    session: {
      storeSessionInDatabase: true
    }
    
  2. Version check - PR #6452 (merged in v1.5.0-beta.16) fixed an issue where user IDs were stripped from session cookies, causing 401s. You're on beta.16, so you should have this fix.

  3. Cookie setup - If you're running frontend/backend on different ports (like localhost:3000 vs 5173), session cookies may not be transmitted properly. Check your cookie configuration:

    advanced: {
      defaultCookieAttributes: {
        sameSite: "lax",
        secure: false  // for localhost
      }
    }
    

Could you confirm your session configuration? The fact that you're logged in after a reload (cookies work) but the after-hook fails suggests the session lookup in the after-hook isn't finding the newly created session token.

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

@dosubot[bot] commented on GitHub (Feb 21, 2026): <!-- Answer --> Thanks for clarifying. The 401 happening before any URL is returned suggests the [after-hook is failing to find the session](https://github.com/better-auth/better-auth/blob/9c78e5cb18945077e03697c42a7c35037ab81a86/packages/oauth-provider/src/oauth.ts#L241-L257) after sign-in. A few things to check: 1. **Session database storage** - The OAuth provider plugin [requires `session.storeSessionInDatabase: true`](https://github.com/better-auth/better-auth/pull/7000). Without this, the after-hook can't look up the session: ```typescript session: { storeSessionInDatabase: true } ``` 2. **Version check** - [PR #6452](https://github.com/better-auth/better-auth/pull/6452) (merged in v1.5.0-beta.16) fixed an issue where user IDs were stripped from session cookies, causing 401s. You're on beta.16, so you should have this fix. 3. **Cookie setup** - If you're running frontend/backend on different ports (like localhost:3000 vs 5173), session cookies may not be transmitted properly. Check your cookie configuration: ```typescript advanced: { defaultCookieAttributes: { sameSite: "lax", secure: false // for localhost } } ``` Could you confirm your `session` configuration? The fact that you're logged in after a reload (cookies work) but the after-hook fails suggests the session lookup in the after-hook isn't finding the newly created session token. <!-- 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/b4a0a7d0-7354-4784-a1a2-2f348c8438be?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/b4a0a7d0-7354-4784-a1a2-2f348c8438be?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/b4a0a7d0-7354-4784-a1a2-2f348c8438be?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/b4a0a7d0-7354-4784-a1a2-2f348c8438be?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/b4a0a7d0-7354-4784-a1a2-2f348c8438be?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/b4a0a7d0-7354-4784-a1a2-2f348c8438be?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/b4a0a7d0-7354-4784-a1a2-2f348c8438be?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%20clarifying.%20The%20401%20happening%20before%20any%20URL%20is%20returned%20suggests%20the%20%5Bafter-hook%20is%20failing%20to%20find%20the%20session%5D%28https%3A//github.com/better-auth/better-auth/blob/9c78e5cb18945077e03697c42a7c35037ab81a86/packages/oauth-provider/src/oauth.ts%23L241-L257%29%20after%20sign-in.%20A%20few%20things%20to%20check%3A%0A%0A1.%20%2A%2ASession%20database%20storage%2A%2A%20-%20The%20OAuth%20provider%20plugin%20%5Brequires%20%60session.storeSessionInDatabase%3A%20true%60%5D%28https%3A//github.com/better-auth/better-auth/pull/7000%29.%20Without%20this%2C%20the%20after-hook%20can%27t%20look%20up%20the%20session%3A%0A%20%20%20%60%60%60typescript%0A%20%20%20session%3A%20%7B%0A%20%20%20%20%20storeSessionInDatabase%3A%20true%0A%20%20%20%7D%0A%20%20%20%60%60%60%0A%0A2.%20%2A%2AVersion%20check%2A%2A%20-%20%5BPR%20%236452%5D%28https%3A//github.com/better-auth/better-auth/pull/6452%29%20%28merged%20in%20v1.5.0-beta.16%29%20fixed%20an%20issue%20where%20user%20IDs%20were%20stripped%20from%20session%20cookies%2C%20causing%20401s.%20You%27re%20on%20beta.16%2C%20so%20you%20should%20have%20this%20fix.%0A%0A3.%20%2A%2ACookie%20setup%2A%2A%20-%20If%20you%27re%20running%20frontend/backend%20on%20different%20ports%20%28like%20localhost%3A3000%20vs%205173%29%2C%20session%20cookies%20may%20not%20be%20transmitted%20properly.%20Check%20your%20cookie%20configuration%3A%0A%20%20%20%60%60%60typescript%0A%20%20%20advanced%3A%20%7B%0A%20%20%20%20%20defaultCookieAttributes%3A%20%7B%0A%20%20%20%20%20%20%20sameSite%3A%20%22lax%22%2C%0A%20%20%20%20%20%20%20secure%3A%20false%20%20//%20for%20localhost%0A%20%20%20%20%20%7D%0A%20%20%20%7D%0A%20%20%20%60%60%60%0A%0ACould%20you%20confirm%20your%20%60session%60%20configuration%3F%20The%20fact%20that%20you%27re%20logged%20in%20after%20a%20reload%20%28cookies%20work%29%20but%20the%20after-hook%20fails%20suggests%20the%20session%20lookup%20in%20the%20after-hook%20isn%27t%20finding%20the%20newly%20created%20session%20token.)&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/8088)
Author
Owner

@till commented on GitHub (Feb 21, 2026):

My session storage is database and I run frontend/backend code on the same URL/port. It's not an SPA.

@till commented on GitHub (Feb 21, 2026): My session storage is database and I run frontend/backend code on the same URL/port. It's not an SPA.
Author
Owner

@himself65 commented on GitHub (Feb 23, 2026):

The better-call issue:

"message":"Cannot redefine property: Symbol(better-call:api-error-headers)"
I patched better-call/dist/middleware.mjs:

  Object.defineProperty(e, kAPIErrorHeaderSymbol, {
    enumerable: false,
-  configurable: false,
+ configurable: true, 
    get() {
      return internalContext.responseHeaders;
    }
  });

How do you see this issue?

@himself65 commented on GitHub (Feb 23, 2026): ``` The better-call issue: "message":"Cannot redefine property: Symbol(better-call:api-error-headers)" I patched better-call/dist/middleware.mjs: Object.defineProperty(e, kAPIErrorHeaderSymbol, { enumerable: false, - configurable: false, + configurable: true, get() { return internalContext.responseHeaders; } }); ``` How do you see this issue?
Author
Owner

@till commented on GitHub (Feb 23, 2026):

@himself65 yes, I hit that.

But I think this is triggered by one of my hooks (where I fetch the current session), I am reworking this right now.

@till commented on GitHub (Feb 23, 2026): @himself65 yes, I hit that. But I think this is triggered by one of my hooks (where I fetch the current session), I am reworking this right now.
Author
Owner

@till commented on GitHub (Feb 23, 2026):

@himself65 Sorry, I re-read your comment/question, let me try to answr:

I have been trying to build a system where an org admin/owner can suspend an account.

I've been hacking around this with a custom organization role as the ban functions are not available for owner/admin (or I couldn't find out how).

Inside my hook/guard, I fetched the session of the current user, inspected it for the role and I think that triggers the better-call issue. I'm not a 100% sure, but it looks like I am setting/mutating the session before better-auth's oauth-provider and that surfaces the better-call issue.

@till commented on GitHub (Feb 23, 2026): @himself65 Sorry, I re-read your comment/question, let me try to answr: I have been trying to build a system where an org admin/owner can suspend an account. I've been hacking around this with a custom organization role as the ban functions are not available for owner/admin (or I couldn't find out how). Inside my hook/guard, I fetched the session of the current user, inspected it for the role and I think that triggers the better-call issue. I'm not a 100% sure, but it looks like I am setting/mutating the session before better-auth's oauth-provider and that surfaces the better-call issue.
Author
Owner

@till commented on GitHub (Feb 23, 2026):

@himself65 would recreating what the admin functions do (with a straight query) to ban users be a better design for my use-case?

@till commented on GitHub (Feb 23, 2026): @himself65 would recreating what the admin functions do (with a straight query) to ban users be a better design for my use-case?
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#2905