[GH-ISSUE #6535] GenericOAuthPlugin: signInWithOAuth2 does not seem to work on server-side #10545

Closed
opened 2026-04-13 06:45:42 -05:00 by GiteaMirror · 6 comments
Owner

Originally created by @Blitz2145 on GitHub (Dec 4, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/6535

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

As part of a migration from next-auth to better-auth stateless, we have a server side route that needs to initiate an oauth login flow from the server. We call auth.api.signInWithOAuth2 and pass it our headers, which then returns a redirect url. Unfortunately, it does not return the cookies to set or a way to pass in a response object to set cookies on. This means that we always get a state_mismatch "ERROR [Better Auth]: State Mismatch.

Repro: https://github.com/Blitz2145/better-auth/tree/stateless_repro

  1. Install deps and setup .env file with github oauth creds
  2. Press sign-in button
  3. Redirect leads to error page instead of signing user in.

Current vs. Expected behavior

Currently leads to "restart process" error page, should instead sign the user in. Once we change the code to use the client-side signIn.oauth2 it works fine.

What version of Better Auth are you using?

1.4.4

System info

2025-12-04T22:08:43.021Z ERROR [Better Auth]: [#better-auth]: Couldn't read your auth config.

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

Backend

Auth config (if applicable)

import { betterAuth } from 'better-auth';
import { genericOAuth } from 'better-auth/plugins';

export const auth = betterAuth({
  baseURL: process.env.NEXT_PUBLIC_URL,
  basePath: '/api/better-auth',
  secret: process.env.BETTER_AUTH_SECRET,
  session: {
    cookieCache: {
      enabled: true,
      maxAge: 5 * 60, // 5 minutes
      refreshCache: true,
    },
  },
  account: {
    storeStateStrategy: 'cookie',
    storeAccountCookie: true,
  },
  plugins: [
    genericOAuth({
      config: [
        {
          providerId: 'okta',
          clientId: process.env.AUTH_OKTA_ID as string,
          clientSecret: process.env.AUTH_OKTA_SECRET as string,
          scopes: ['openid', 'profile', 'email', 'offline_access', 'groups'],
          discoveryUrl: `${process.env.AUTH_OKTA_ISSUER}/.well-known/openid-configuration`,
          accessType: 'offline',
        },
      ],
    }),
  ],
});

Additional context

Reproduces with latest versions 1.4.4, related reports of similar problems https://github.com/better-auth/better-auth/issues/4969#issuecomment-3496188961

Originally created by @Blitz2145 on GitHub (Dec 4, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/6535 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce As part of a migration from next-auth to better-auth stateless, we have a server side route that needs to initiate an oauth login flow from the server. We call auth.api.signInWithOAuth2 and pass it our headers, which then returns a redirect url. Unfortunately, it does not return the cookies to set or a way to pass in a response object to set cookies on. This means that we always get a state_mismatch "ERROR [Better Auth]: State Mismatch. Repro: https://github.com/Blitz2145/better-auth/tree/stateless_repro 1. Install deps and setup .env file with github oauth creds 2. Press sign-in button 3. Redirect leads to error page instead of signing user in. ### Current vs. Expected behavior Currently leads to "restart process" error page, should instead sign the user in. Once we change the code to use the client-side `signIn.oauth2` it works fine. ### What version of Better Auth are you using? 1.4.4 ### System info ```bash 2025-12-04T22:08:43.021Z ERROR [Better Auth]: [#better-auth]: Couldn't read your auth config. ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from 'better-auth'; import { genericOAuth } from 'better-auth/plugins'; export const auth = betterAuth({ baseURL: process.env.NEXT_PUBLIC_URL, basePath: '/api/better-auth', secret: process.env.BETTER_AUTH_SECRET, session: { cookieCache: { enabled: true, maxAge: 5 * 60, // 5 minutes refreshCache: true, }, }, account: { storeStateStrategy: 'cookie', storeAccountCookie: true, }, plugins: [ genericOAuth({ config: [ { providerId: 'okta', clientId: process.env.AUTH_OKTA_ID as string, clientSecret: process.env.AUTH_OKTA_SECRET as string, scopes: ['openid', 'profile', 'email', 'offline_access', 'groups'], discoveryUrl: `${process.env.AUTH_OKTA_ISSUER}/.well-known/openid-configuration`, accessType: 'offline', }, ], }), ], }); ``` ### Additional context Reproduces with latest versions 1.4.4, related reports of similar problems https://github.com/better-auth/better-auth/issues/4969#issuecomment-3496188961
GiteaMirror added the lockedbug labels 2026-04-13 06:45:42 -05:00
Author
Owner

@better-auth-agent[bot] commented on GitHub (Dec 4, 2025):

Hi there, thanks for raising this issue. Based on your configuration and symptoms, it appears that the server-side invocation of signInWithOAuth2 is not properly handling the state storage—most likely because cookies aren’t being set as expected in the server environment. In Better Auth, the OAuth flow relies on saving state in a cookie (as set with storeStateStrategy) so that on callback the state can be validated. When your server-side call isn’t given access to a response object for setting cookies, you end up with a state mismatch error.

I recommend checking that:
• Your server-side route supports cookie handling (or consider using middleware that sets cookies).
• The environment variables and secrets are correctly loaded at runtime, as issues in reading the auth config can also cause these symptoms.
• If possible, try a client-side OAuth flow where cookie management is native, then use that as a basis to refactor the server-side approach if necessary.

Does this help clarify the next steps? Tag @better-auth-agent if you would like an answer from the Agent.

If you need more help, tag @better-auth-agent in a comment so I can respond.

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3614528546 --> @better-auth-agent[bot] commented on GitHub (Dec 4, 2025): Hi there, thanks for raising this issue. Based on your configuration and symptoms, it appears that the server-side invocation of signInWithOAuth2 is not properly handling the state storage—most likely because cookies aren’t being set as expected in the server environment. In Better Auth, the OAuth flow relies on saving state in a cookie (as set with storeStateStrategy) so that on callback the state can be validated. When your server-side call isn’t given access to a response object for setting cookies, you end up with a state mismatch error. I recommend checking that: • Your server-side route supports cookie handling (or consider using middleware that sets cookies). • The environment variables and secrets are correctly loaded at runtime, as issues in reading the auth config can also cause these symptoms. • If possible, try a client-side OAuth flow where cookie management is native, then use that as a basis to refactor the server-side approach if necessary. Does this help clarify the next steps? Tag @better-auth-agent if you would like an answer from the Agent. _If you need more help, tag @better-auth-agent in a comment so I can respond._ <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) • [Discord](https://discord.gg/fG2XXEuQX3) • [GitHub](https://github.com/Skyvern-AI/Skyvern) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/fG2XXEuQX3) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/Skyvern-AI/Skyvern)
Author
Owner

@jslno commented on GitHub (Dec 6, 2025):

Hey, I noticed that you didn't add the nextCookies plugin to your auth config. Adding that fixes it for me

<!-- gh-comment-id:3619006418 --> @jslno commented on GitHub (Dec 6, 2025): Hey, I noticed that you didn't add the `nextCookies` plugin to your auth config. Adding that fixes it for me
Author
Owner

@Blitz2145 commented on GitHub (Dec 6, 2025):

Hey, I noticed that you didn't add the nextCookies plugin to your auth config. Adding that fixes it for me

Thank you for responding. I forgot to mention it in my issue, we already tried that solution but unfortunately the nextCookies plugin causes crashes instead because it dynamically imports next/headers. It errors sporadically with "Module not found".

I really think proper server side support for this functionality should be implemented, maybe through an option to pass in response obj or something else. The nextCookies workaround just triggers other bugs for us, and is framework specific, so I don't think it's the best approach.

<!-- gh-comment-id:3620984136 --> @Blitz2145 commented on GitHub (Dec 6, 2025): > Hey, I noticed that you didn't add the `nextCookies` plugin to your auth config. Adding that fixes it for me Thank you for responding. I forgot to mention it in my issue, we already tried that solution but unfortunately the nextCookies plugin causes crashes instead because it dynamically imports next/headers. It errors sporadically with "Module not found". I really think proper server side support for this functionality should be implemented, maybe through an option to pass in response obj or something else. The nextCookies workaround just triggers other bugs for us, and is framework specific, so I don't think it's the best approach.
Author
Owner

@jslno commented on GitHub (Dec 6, 2025):

You should be able to forward the set-cookie header by setting asResponse to true.

Every framework handles cookies differently so it's impossible to have one generic solution for forwarding cookies to the response headers.

<!-- gh-comment-id:3621363231 --> @jslno commented on GitHub (Dec 6, 2025): You should be able to forward the set-cookie header by setting `asResponse` to `true`. Every framework handles cookies differently so it's impossible to have one generic solution for forwarding cookies to the response headers.
Author
Owner

@Blitz2145 commented on GitHub (Dec 8, 2025):

You should be able to forward the set-cookie header by setting asResponse to true.

Every framework handles cookies differently so it's impossible to have one generic solution for forwarding cookies to the response headers.

Thanks, let me try this it looks like exactly the option we need, didn't see it in the docs, but seems to be available.

<!-- gh-comment-id:3627650881 --> @Blitz2145 commented on GitHub (Dec 8, 2025): > You should be able to forward the set-cookie header by setting `asResponse` to `true`. > > Every framework handles cookies differently so it's impossible to have one generic solution for forwarding cookies to the response headers. Thanks, let me try this it looks like exactly the option we need, didn't see it in the docs, but seems to be available.
Author
Owner

@Blitz2145 commented on GitHub (Dec 9, 2025):

You should be able to forward the set-cookie header by setting asResponse to true.
Every framework handles cookies differently so it's impossible to have one generic solution for forwarding cookies to the response headers.

Thanks, let me try this it looks like exactly the option we need, didn't see it in the docs, but seems to be available.

Thanks! I was able to get something working using the following code:

    const response = await auth.api.signInWithOAuth2({
      body: {
        providerId: 'github2',
        callbackURL: 'http://localhost:3000/dashboard',
      },
      headers: req.headers,
      asResponse: true,
    });
    const redirectInfo = (await response.json()) as {
      url: string;
      redirect: boolean;
    };

    if (redirectInfo.redirect) {
      const redirectHeaders = new Headers();
      redirectHeaders.set('Location', redirectInfo.url);
      redirectHeaders.set('Set-Cookie', response.headers.get('Set-Cookie')!);
      const redirectResponse = new Response(null, {
        status: 302,
        headers: redirectHeaders,
      });

      return redirectResponse;
    }

would be great if the docs mentioned this option somewhere.

<!-- gh-comment-id:3629991171 --> @Blitz2145 commented on GitHub (Dec 9, 2025): > > You should be able to forward the set-cookie header by setting `asResponse` to `true`. > > Every framework handles cookies differently so it's impossible to have one generic solution for forwarding cookies to the response headers. > > Thanks, let me try this it looks like exactly the option we need, didn't see it in the docs, but seems to be available. Thanks! I was able to get something working using the following code: ```ts const response = await auth.api.signInWithOAuth2({ body: { providerId: 'github2', callbackURL: 'http://localhost:3000/dashboard', }, headers: req.headers, asResponse: true, }); const redirectInfo = (await response.json()) as { url: string; redirect: boolean; }; if (redirectInfo.redirect) { const redirectHeaders = new Headers(); redirectHeaders.set('Location', redirectInfo.url); redirectHeaders.set('Set-Cookie', response.headers.get('Set-Cookie')!); const redirectResponse = new Response(null, { status: 302, headers: redirectHeaders, }); return redirectResponse; } ``` would be great if the docs mentioned this option somewhere.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#10545