Bug Report: invalid_code Error During Social Sign-In (better-auth) [Google] #1503

Closed
opened 2026-03-13 08:43:50 -05:00 by GiteaMirror · 5 comments
Owner

Originally created by @Daanish2003 on GitHub (Jul 16, 2025).

Is this suited for github?

  • Yes, this is suited for github

Description

I'm encountering an invalid_code error while performing Google OAuth using Better Auth v1.2.10.
The flow fails at the callback step with the following UI error:

Better Auth Error
Error Code: invalid_code
We encountered an issue while processing your request. Please try again or contact the application owner if the problem persists.


🔁 Reproduction Steps

  1. Start frontend at http://localhost:3000
  2. Start backend at http://localhost:4000
  3. Call signIn.social({ provider: "google", callbackURL: "http://localhost:3000/dashboard/overview" })
  4. OAuth redirects to:
    http://localhost:4000/api/v1/auth/callback/google?code=...
  5. Error shown: invalid_code

📦 Setup Details

  • better-auth version: v1.2.10
  • Frontend: Next.js @ localhost:3000
  • Backend: Express (Node) @ localhost:4000
  • Database: PostgreSQL via prismaAdapter
  • Provider: Google OAuth

Relevant config:

socialProviders: {
  google: {
    clientId: process.env.GOOGLE_CLIENT_ID,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    redirectURI: "http://localhost:4000/api/v1/auth/callback/google",
  },
}

frontend usage

signIn.social({
  provider: "google",
  callbackURL: "http://localhost:3000/dashboard/overview"
})

Current vs. Expected behavior

Expected Behavior:
User should be redirected to the dashboard after successful Google login.

Current Behaviour:
OAuth exchange fails with the invalid_code error at the callback endpoint.

What version of Better Auth are you using?

1.2.10

Provide environment information

- OS [Windows 11 with wsl Ubuntu 24.04]

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

Backend

Auth config (if applicable)

export const auth = betterAuth({
  appName: "talk2partners",
  database: prismaAdapter(prisma, {
    provider: "postgresql",
  }),
  baseURL: process.env.BETTER_AUTH_URL!,
  basePath: '/api/v1/auth',

  user: {
    deleteUser: {
      enabled: true,
    },
  },

  advanced: {
    crossSubDomainCookies: {
      enabled: process.env.NODE_ENV === "production",
      domain: process.env.DOMAIN,
    },
  },

  hooks: {
    before: createAuthMiddleware(async (ctx) => {
      if (ctx.path.startsWith("/admin/list-users")) {
        if (
          ctx.query?.filterField === "banned" &&
          typeof ctx.query?.filterValue === "string"
        ) {
          const value = ctx.query.filterValue.toLowerCase();
          ctx.query.filterValue = value === "true" ? true : value === "false" ? false : ctx.query.filterValue;
        }
      }
    }),
  },

  account: {
    accountLinking: {
      enabled: true,
      trustedProviders: ['google', 'email-password'],
    },
  },

  session: {
    cookieCache: {
      enabled: true,
      maxAge: 60 * 60,
    },
    disableSessionRefresh: true,
    expiresIn: 30 * 24 * 60 * 60,
    storeSessionInDatabase: true,
    preserveSessionInDatabase: true,
  },

  secret: process.env.BETTER_AUTH_SECRET!,

  socialProviders: {
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID as string,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
      redirectURI: `${process.env.BETTER_AUTH_URL}/api/v1/auth/callback/google`,
    },
  },

  trustedOrigins: [
    process.env.FRONTEND_URL!,
    process.env.BACKEND_URL!,
  ],

  plugins: [
    admin(),
    openAPI(),
    bearer(),

    phoneNumber({
      allowedAttempts: 3,
      expiresIn: 300,
      otpLength: 4,
      requireVerification: true,

      sendOTP: async ({ phoneNumber, code }) => {
        await sendFlowMessage({
          authKey: process.env.MSG_AUTH_KEY!,
          templateId: process.env.MSG_TEMPLATE_ID!,
          mobiles: phoneNumber,
          variables: {
            var: code,
          },
          shortUrl: true,
          shortUrlExpiry: 300,
          realTimeResponse: 1,
        });
      },

      signUpOnVerification: {
        getTempEmail: (phoneNumber) => `${phoneNumber}@Talk2Partners.com`,
        getTempName: (phoneNumber) => phoneNumber,
      },

      phoneNumberValidator: (phoneNumber: string): boolean => {
        try {
          const parsed = parsePhoneNumberFromString(phoneNumber, 'IN');
          return !!parsed && parsed.isValid() && parsed.country === 'IN';
        } catch {
          return false;
        }
      },
    }),

    jwt({
      jwt: {
        issuer: process.env.BACKEND_URL!,
        audience: process.env.CHAT_URL!,
        expirationTime: "1h",
      },
    }),
  ],
});

Additional context

  • Google OAuth consent screen is published and verified.

  • Client ID & secret are correct.

  • The redirectURI is consistent between:

  • Google Developer Console

  • Better Auth config

  • Possible mismatch or expiration of code_verifier?

  • Also tried with different version fo better-auth by upgrading and downgrading

Originally created by @Daanish2003 on GitHub (Jul 16, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ## Description I'm encountering an `invalid_code` error while performing Google OAuth using **Better Auth `v1.2.10`**. The flow fails at the callback step with the following UI error: > **Better Auth Error** > **Error Code: `invalid_code`** > We encountered an issue while processing your request. Please try again or contact the application owner if the problem persists. --- ## 🔁 Reproduction Steps 1. Start frontend at `http://localhost:3000` 2. Start backend at `http://localhost:4000` 3. Call `signIn.social({ provider: "google", callbackURL: "http://localhost:3000/dashboard/overview" })` 4. OAuth redirects to: `http://localhost:4000/api/v1/auth/callback/google?code=...` 5. Error shown: `invalid_code` --- ## 📦 Setup Details - **better-auth version**: `v1.2.10` - **Frontend**: `Next.js @ localhost:3000` - **Backend**: `Express (Node) @ localhost:4000` - **Database**: PostgreSQL via `prismaAdapter` - **Provider**: Google OAuth ### Relevant config: ```ts socialProviders: { google: { clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET, redirectURI: "http://localhost:4000/api/v1/auth/callback/google", }, } ``` frontend usage ```ts signIn.social({ provider: "google", callbackURL: "http://localhost:3000/dashboard/overview" }) ``` ### Current vs. Expected behavior Expected Behavior: User should be redirected to the dashboard after successful Google login. Current Behaviour: OAuth exchange fails with the invalid_code error at the callback endpoint. ### What version of Better Auth are you using? 1.2.10 ### Provide environment information ```bash - OS [Windows 11 with wsl Ubuntu 24.04] ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript export const auth = betterAuth({ appName: "talk2partners", database: prismaAdapter(prisma, { provider: "postgresql", }), baseURL: process.env.BETTER_AUTH_URL!, basePath: '/api/v1/auth', user: { deleteUser: { enabled: true, }, }, advanced: { crossSubDomainCookies: { enabled: process.env.NODE_ENV === "production", domain: process.env.DOMAIN, }, }, hooks: { before: createAuthMiddleware(async (ctx) => { if (ctx.path.startsWith("/admin/list-users")) { if ( ctx.query?.filterField === "banned" && typeof ctx.query?.filterValue === "string" ) { const value = ctx.query.filterValue.toLowerCase(); ctx.query.filterValue = value === "true" ? true : value === "false" ? false : ctx.query.filterValue; } } }), }, account: { accountLinking: { enabled: true, trustedProviders: ['google', 'email-password'], }, }, session: { cookieCache: { enabled: true, maxAge: 60 * 60, }, disableSessionRefresh: true, expiresIn: 30 * 24 * 60 * 60, storeSessionInDatabase: true, preserveSessionInDatabase: true, }, secret: process.env.BETTER_AUTH_SECRET!, socialProviders: { google: { clientId: process.env.GOOGLE_CLIENT_ID as string, clientSecret: process.env.GOOGLE_CLIENT_SECRET as string, redirectURI: `${process.env.BETTER_AUTH_URL}/api/v1/auth/callback/google`, }, }, trustedOrigins: [ process.env.FRONTEND_URL!, process.env.BACKEND_URL!, ], plugins: [ admin(), openAPI(), bearer(), phoneNumber({ allowedAttempts: 3, expiresIn: 300, otpLength: 4, requireVerification: true, sendOTP: async ({ phoneNumber, code }) => { await sendFlowMessage({ authKey: process.env.MSG_AUTH_KEY!, templateId: process.env.MSG_TEMPLATE_ID!, mobiles: phoneNumber, variables: { var: code, }, shortUrl: true, shortUrlExpiry: 300, realTimeResponse: 1, }); }, signUpOnVerification: { getTempEmail: (phoneNumber) => `${phoneNumber}@Talk2Partners.com`, getTempName: (phoneNumber) => phoneNumber, }, phoneNumberValidator: (phoneNumber: string): boolean => { try { const parsed = parsePhoneNumberFromString(phoneNumber, 'IN'); return !!parsed && parsed.isValid() && parsed.country === 'IN'; } catch { return false; } }, }), jwt({ jwt: { issuer: process.env.BACKEND_URL!, audience: process.env.CHAT_URL!, expirationTime: "1h", }, }), ], }); ``` ### Additional context * Google OAuth consent screen is published and verified. * Client ID & secret are correct. * The redirectURI is consistent between: * Google Developer Console * Better Auth config * Possible mismatch or expiration of code_verifier? * Also tried with different version fo better-auth by upgrading and downgrading
GiteaMirror added the bug label 2026-03-13 08:43:50 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Jul 16, 2025):

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

The invalid_code error during Google OAuth in better-auth is most often caused by one of these issues: a mismatch in the redirect URI, a missing or expired code_verifier (PKCE), or a problem with how session/cookie data is handled between the auth request and callback.

Here’s what to check:

  • Make sure the redirect URI is exactly the same in your Google Developer Console, your better-auth config, and the actual request (including protocol, port, path, and no trailing slashes or case differences). Even a small difference can cause this error due to strict normalization and comparison logic in better-auth.
  • Confirm that the code_verifier is being generated, stored, and retrieved correctly. better-auth uses cookies or session storage to persist this between the initial OAuth request and the callback. If the code_verifier is missing or not matched at the callback, you’ll get this error. This can happen if cookies aren’t shared properly between frontend and backend, or if you’re switching domains/ports (like between localhost:3000 and localhost:4000) — check your browser’s cookie storage and CORS settings.
  • Double-check that all environment variables (GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, BETTER_AUTH_URL, etc.) are set and available at runtime for both backend and frontend.
  • If you’re running in development, cross-domain cookies can be tricky. Try using the same domain and port for both frontend and backend if possible, or ensure your cookie settings (like SameSite, domain, and secure flags) are compatible with your local setup.
  • If possible, enable debug logging in better-auth to get more detailed error output about what’s failing in the code exchange step.

The better-auth codebase enforces these checks strictly, and any mismatch or missing value (especially for code_verifier or redirect URI) will result in an invalid_code error during the callback step — see the Google provider implementation for details.

If you’ve confirmed all of the above and still see the error, try clearing your cookies and restarting both frontend and backend to reset any session state.

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 (Jul 16, 2025): <!-- Greeting --> Hi @Daanish2003! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> The `invalid_code` error during Google OAuth in better-auth is most often caused by one of these issues: a mismatch in the redirect URI, a missing or expired code_verifier (PKCE), or a problem with how session/cookie data is handled between the auth request and callback. Here’s what to check: - Make sure the redirect URI is **exactly** the same in your Google Developer Console, your better-auth config, and the actual request (including protocol, port, path, and no trailing slashes or case differences). Even a small difference can cause this error due to strict normalization and comparison logic in better-auth. - Confirm that the code_verifier is being generated, stored, and retrieved correctly. better-auth uses cookies or session storage to persist this between the initial OAuth request and the callback. If the code_verifier is missing or not matched at the callback, you’ll get this error. This can happen if cookies aren’t shared properly between frontend and backend, or if you’re switching domains/ports (like between localhost:3000 and localhost:4000) — check your browser’s cookie storage and CORS settings. - Double-check that all environment variables (GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, BETTER_AUTH_URL, etc.) are set and available at runtime for both backend and frontend. - If you’re running in development, cross-domain cookies can be tricky. Try using the same domain and port for both frontend and backend if possible, or ensure your cookie settings (like SameSite, domain, and secure flags) are compatible with your local setup. - If possible, enable debug logging in better-auth to get more detailed error output about what’s failing in the code exchange step. The better-auth codebase enforces these checks strictly, and any mismatch or missing value (especially for code_verifier or redirect URI) will result in an `invalid_code` error during the callback step — see the [Google provider implementation](https://github.com/better-auth/better-auth/blob/7364c169f6de0eec4f095112047e23480b240078/packages/better-auth/src/social-providers/google.ts) for details. If you’ve confirmed all of the above and still see the error, try clearing your cookies and restarting both frontend and backend to reset any session state. <!-- 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/11c95ab0-0b6a-4f4a-8292-1c79efbf8663?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/11c95ab0-0b6a-4f4a-8292-1c79efbf8663?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/11c95ab0-0b6a-4f4a-8292-1c79efbf8663?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/11c95ab0-0b6a-4f4a-8292-1c79efbf8663?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/11c95ab0-0b6a-4f4a-8292-1c79efbf8663?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/11c95ab0-0b6a-4f4a-8292-1c79efbf8663?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/11c95ab0-0b6a-4f4a-8292-1c79efbf8663?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/3401)
Author
Owner

@Daanish2003 commented on GitHub (Jul 16, 2025):

I narrowed down the error to unable to send the code_verifier due to different localhost: 3000 and 4000

@Daanish2003 commented on GitHub (Jul 16, 2025): I narrowed down the error to unable to send the code_verifier due to different localhost: 3000 and 4000
Author
Owner

@raphaeldefalcoayres commented on GitHub (Jul 21, 2025):

What was the solution? How to debug this?

@raphaeldefalcoayres commented on GitHub (Jul 21, 2025): What was the solution? How to debug this?
Author
Owner

@massimilianoporzio commented on GitHub (Jul 31, 2025):

I have the same issue using Nuxt and localhost:3000, either using github or Google oauth and better-auth 1.3.4

@massimilianoporzio commented on GitHub (Jul 31, 2025): I have the same issue using Nuxt and localhost:3000, either using github or Google oauth and better-auth 1.3.4
Author
Owner

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

is this fixed?

@lips5476 commented on GitHub (Dec 8, 2025): is this fixed?
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#1503