[GH-ISSUE #3881] Google provider on production return nothing but user expected #9752

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

Originally created by @SpinBoxx on GitHub (Aug 8, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/3881

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Create a backend
  2. Use Google provider
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID as string,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
      mapProfileToUser: (user) => {
        console.log({ googleUser: user });
        return user;
      },
    },
  1. Create a first that handle google signIn
@Get('/sso')
  public async loginSSO(@Request() request: express.Request) {
    const _res = await auth.api
      .signInSocial({
        body: {
          provider,
          callbackURL: '/callback/' + provider,
        },
        asResponse: true,
        returnHeaders: true,
      })
      .then(async (res) => {
        const data = await res.json();
        return data as { url: string };
      })
      .catch((error) => {
        logger.error(`${this.context} logginSSO failed, ${error}`);
        return error;
      });
    return request.res?.redirect(_res.url);
  }
  1. Create a second route to be used as the default callback.

  app.get('/callback/:providerId', (req, res) => {
    const { providerId } = req.params;
    const query = req.query;
    const headers = req.headers;

    const redirectUrl = `/ecom/auth/sso/callback?providerId=${encodeURIComponent(providerId)}`;
    res.redirect(307, redirectUrl);
  });
  1. Create a third route that handle logic after signin and redirect to our frontend.

Current vs. Expected behavior

The current behavior is that during signInSocial from BetterAuth, the console.log({ googleUser: user }) in auth.ts#googleProvider logs null, so no new session is created. Is social login broken in production? Because in development mode it works properly.

Thanks you for your response !

What version of Better Auth are you using?

1.2.12

Provide environment information

-

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

Backend

Auth config (if applicable)

import { betterAuth } from 'better-auth';
import { prismaAdapter } from 'better-auth/adapters/prisma';
import prisma from './authDb';
import { hashPassword, testPassword } from '@cv-erp/common';

export const auth = betterAuth({
  database: prismaAdapter(prisma, {
    provider: 'postgresql',
  }),

  emailAndPassword: {
    enabled: true,
    password: {
      hash: async (password) => {
        const hashedPassword = hashPassword(password);
        return hashedPassword;
      },
      verify: async ({ hash, password }) => {
        const isOK = testPassword(password, hash);
        return isOK;
      },
    },
  },
  socialProviders: {
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID as string,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,

      mapProfileToUser: (user) => {
        console.log({ googleUser: user });

        return user;
      },
      redirectURI: process.env.BETTER_AUTH_URL + '/auth/sso/callback',
    },
  },

  trustedOrigins: ['http://localhost:5173', 'http://localhost:3000', 'https://accounts.google.com'],
  user: {
    additionalFields: {
      cartId: {
        input: false,
        required: false,
        returned: true,
        type: 'string',
      },
      contactId: {
        input: false,
        required: false,
        returned: true,
        type: 'string',
      },
    },
  },
});

Additional context

No response

Originally created by @SpinBoxx on GitHub (Aug 8, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/3881 ### Is this suited for github? - [ ] Yes, this is suited for github ### To Reproduce 1. Create a backend 2. Use Google provider ``` google: { clientId: process.env.GOOGLE_CLIENT_ID as string, clientSecret: process.env.GOOGLE_CLIENT_SECRET as string, mapProfileToUser: (user) => { console.log({ googleUser: user }); return user; }, }, ``` 3. Create a first that handle google signIn ``` @Get('/sso') public async loginSSO(@Request() request: express.Request) { const _res = await auth.api .signInSocial({ body: { provider, callbackURL: '/callback/' + provider, }, asResponse: true, returnHeaders: true, }) .then(async (res) => { const data = await res.json(); return data as { url: string }; }) .catch((error) => { logger.error(`${this.context} logginSSO failed, ${error}`); return error; }); return request.res?.redirect(_res.url); } ``` 4. Create a second route to be used as the default callback. ``` app.get('/callback/:providerId', (req, res) => { const { providerId } = req.params; const query = req.query; const headers = req.headers; const redirectUrl = `/ecom/auth/sso/callback?providerId=${encodeURIComponent(providerId)}`; res.redirect(307, redirectUrl); }); ``` 5. Create a third route that handle logic after signin and redirect to our frontend. ### Current vs. Expected behavior The current behavior is that during signInSocial from BetterAuth, the console.log({ googleUser: user }) in auth.ts#googleProvider logs null, so no new session is created. Is social login broken in production? Because in development mode it works properly. Thanks you for your response ! ### What version of Better Auth are you using? 1.2.12 ### Provide environment information ```bash - ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from 'better-auth'; import { prismaAdapter } from 'better-auth/adapters/prisma'; import prisma from './authDb'; import { hashPassword, testPassword } from '@cv-erp/common'; export const auth = betterAuth({ database: prismaAdapter(prisma, { provider: 'postgresql', }), emailAndPassword: { enabled: true, password: { hash: async (password) => { const hashedPassword = hashPassword(password); return hashedPassword; }, verify: async ({ hash, password }) => { const isOK = testPassword(password, hash); return isOK; }, }, }, socialProviders: { google: { clientId: process.env.GOOGLE_CLIENT_ID as string, clientSecret: process.env.GOOGLE_CLIENT_SECRET as string, mapProfileToUser: (user) => { console.log({ googleUser: user }); return user; }, redirectURI: process.env.BETTER_AUTH_URL + '/auth/sso/callback', }, }, trustedOrigins: ['http://localhost:5173', 'http://localhost:3000', 'https://accounts.google.com'], user: { additionalFields: { cartId: { input: false, required: false, returned: true, type: 'string', }, contactId: { input: false, required: false, returned: true, type: 'string', }, }, }, }); ``` ### Additional context _No response_
GiteaMirror added the locked label 2026-04-13 05:26:42 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Aug 8, 2025):

The most common reason Google social login returns a null user in production is a mismatch or misconfiguration in your environment variables, redirect URIs, or OAuth setup. In Better Auth, if the idToken is missing from Google's response, the user profile can't be decoded and mapProfileToUser receives null, which means the OAuth flow didn't complete successfully or the profile couldn't be fetched (source).

Here’s a quick checklist to help you debug:

  1. Check your GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET: Make sure they're set and correct in your production environment.
  2. Verify redirect URI configuration: The redirect URI in your Better Auth config must exactly match the one registered in Google Cloud Console (e.g., https://your-domain.com/api/auth/callback/google). If they don’t match, Google won’t return a valid authorization code or idToken (source).
  3. Ensure callbackURL is absolute and matches a trusted origin: If your callbackURL is relative or not included in trustedOrigins, Better Auth may redirect incorrectly or fail the flow (source).
  4. Check your user schema: If required fields like image are missing, user creation during OAuth sign-in can fail, resulting in a null user (source).
  5. Session cookies: Make sure cookies are set correctly in production (secure flag, domain, etc.), or sessions may not persist after login (source).

If you enable more detailed logging in production, you may catch silent failures during the OAuth flow. If everything above checks out and the issue persists, consider testing with a newer Better Auth version, as recent releases improved error handling for missing tokens and user info.

If you have specific error logs or debug output from production, share them—there may be more clues there.

To reply, just mention @dosu.


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

<!-- gh-comment-id:3168183970 --> @dosubot[bot] commented on GitHub (Aug 8, 2025): <!-- Answer --> The most common reason Google social login returns a null user in production is a mismatch or misconfiguration in your environment variables, redirect URIs, or OAuth setup. In Better Auth, if the `idToken` is missing from Google's response, the user profile can't be decoded and `mapProfileToUser` receives null, which means the OAuth flow didn't complete successfully or the profile couldn't be fetched ([source](https://github.com/better-auth/better-auth/blob/0fe17704048bca8d52b547eb57a53ca89e84b78c/packages/better-auth/src/social-providers/google.ts)). Here’s a quick checklist to help you debug: 1. **Check your GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET**: Make sure they're set and correct in your production environment. 2. **Verify redirect URI configuration**: The redirect URI in your Better Auth config must exactly match the one registered in Google Cloud Console (e.g., `https://your-domain.com/api/auth/callback/google`). If they don’t match, Google won’t return a valid authorization code or idToken ([source](https://github.com/better-auth/better-auth/issues/3062)). 3. **Ensure callbackURL is absolute and matches a trusted origin**: If your callbackURL is relative or not included in `trustedOrigins`, Better Auth may redirect incorrectly or fail the flow ([source](https://github.com/better-auth/better-auth/issues/3407)). 4. **Check your user schema**: If required fields like `image` are missing, user creation during OAuth sign-in can fail, resulting in a null user ([source](https://github.com/better-auth/better-auth/issues/1025)). 5. **Session cookies**: Make sure cookies are set correctly in production (secure flag, domain, etc.), or sessions may not persist after login ([source](https://github.com/better-auth/better-auth/issues/2196)). If you enable more detailed logging in production, you may catch silent failures during the OAuth flow. If everything above checks out and the issue persists, consider testing with a newer Better Auth version, as recent releases improved error handling for missing tokens and user info. If you have specific error logs or debug output from production, share them—there may be more clues there. <!-- 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/21afc953-f954-4d81-bb31-96a6abc44166?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/21afc953-f954-4d81-bb31-96a6abc44166?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/21afc953-f954-4d81-bb31-96a6abc44166?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/21afc953-f954-4d81-bb31-96a6abc44166?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/21afc953-f954-4d81-bb31-96a6abc44166?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/21afc953-f954-4d81-bb31-96a6abc44166?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/21afc953-f954-4d81-bb31-96a6abc44166?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&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/3881)
Author
Owner

@SpinBoxx commented on GitHub (Aug 8, 2025):

i created exactly the same route /api/auth/callback/:providerId. Like this :

  app.get('/api/auth/callback/:providerId', (req, res) => {
    const { providerId } = req.params;

    const query = req.query;
    console.log({ query });

    const headers = req.headers;
    console.log({ headersServier: headers });

    const redirectUrl = `/ecom/auth/sso/callback?providerId=${encodeURIComponent(providerId)}`;
    console.log(redirectUrl);

    res.redirect(307, redirectUrl);
  });

and my callbackUrl:

 const _res = await auth.api
      .signInSocial({
        body: {
          provider,
          callbackURL: process.env.BETTER_AUTH_URL + '/api/auth/callback/' + provider,
        },
        asResponse: true,
        returnHeaders: true,
      })
      .then(async (res) => {
        const data = await res.json();
        return data as { url: string };
      })
      .catch((error) => {
        logger.error(`${this.context} logginSSO failed, ${error}`);
        return error;
      });

but now i have this error :

Image
<!-- gh-comment-id:3168241264 --> @SpinBoxx commented on GitHub (Aug 8, 2025): i created exactly the same route /api/auth/callback/:providerId. Like this : ``` app.get('/api/auth/callback/:providerId', (req, res) => { const { providerId } = req.params; const query = req.query; console.log({ query }); const headers = req.headers; console.log({ headersServier: headers }); const redirectUrl = `/ecom/auth/sso/callback?providerId=${encodeURIComponent(providerId)}`; console.log(redirectUrl); res.redirect(307, redirectUrl); }); ``` and my callbackUrl: ``` const _res = await auth.api .signInSocial({ body: { provider, callbackURL: process.env.BETTER_AUTH_URL + '/api/auth/callback/' + provider, }, asResponse: true, returnHeaders: true, }) .then(async (res) => { const data = await res.json(); return data as { url: string }; }) .catch((error) => { logger.error(`${this.context} logginSSO failed, ${error}`); return error; }); ``` but now i have this error : <img width="536" height="510" alt="Image" src="https://github.com/user-attachments/assets/5e8ace8f-13d0-45dd-8b5a-d5e07c565ca1" />
Author
Owner

@dosubot[bot] commented on GitHub (Aug 8, 2025):

The state_not_found error happens because the callbackURL you pass to signInSocial is set to your OAuth callback endpoint (/api/auth/callback/:providerId). This causes the callback endpoint to be hit twice: first with the correct code and state params (which works), then again without them (which triggers the error) see explanation.

To fix this, set the callbackURL in your signInSocial call to a user-facing route (like your frontend dashboard or landing page), not the callback endpoint. The OAuth provider's redirect URI (in Google Cloud Console) should still point to your backend callback endpoint (e.g., /api/auth/callback/google). Example:

const _res = await auth.api.signInSocial({
  body: {
    provider,
    callbackURL: 'https://your-frontend.com/dashboard', // or wherever you want users to land after login
  },
  asResponse: true,
  returnHeaders: true,
});

Make sure your frontend domain is included in trustedOrigins, and if your frontend and backend are on different domains, use absolute URLs and configure CORS and cookies accordingly details.

If you want to debug further, try passing disableRedirect to signInSocial and check if the returned URL includes the state parameter debug tip.

This change should resolve the state_not_found error and get your social login working in production.

To reply, just mention @dosu.


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

<!-- gh-comment-id:3168254357 --> @dosubot[bot] commented on GitHub (Aug 8, 2025): <!-- Answer --> The `state_not_found` error happens because the callbackURL you pass to `signInSocial` is set to your OAuth callback endpoint (`/api/auth/callback/:providerId`). This causes the callback endpoint to be hit twice: first with the correct `code` and `state` params (which works), then again without them (which triggers the error) [see explanation](https://github.com/better-auth/better-auth/issues/3488). To fix this, set the `callbackURL` in your `signInSocial` call to a user-facing route (like your frontend dashboard or landing page), not the callback endpoint. The OAuth provider's redirect URI (in Google Cloud Console) should still point to your backend callback endpoint (e.g., `/api/auth/callback/google`). Example: ```js const _res = await auth.api.signInSocial({ body: { provider, callbackURL: 'https://your-frontend.com/dashboard', // or wherever you want users to land after login }, asResponse: true, returnHeaders: true, }); ``` Make sure your frontend domain is included in `trustedOrigins`, and if your frontend and backend are on different domains, use absolute URLs and configure CORS and cookies accordingly [details](https://github.com/better-auth/better-auth/issues/3407). If you want to debug further, try passing `disableRedirect` to `signInSocial` and check if the returned URL includes the `state` parameter [debug tip](https://github.com/better-auth/better-auth/issues/796). This change should resolve the `state_not_found` error and get your social login working in production. <!-- 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/9435f68b-3ed2-4358-8340-ea06e90da9f4?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/9435f68b-3ed2-4358-8340-ea06e90da9f4?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/9435f68b-3ed2-4358-8340-ea06e90da9f4?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/9435f68b-3ed2-4358-8340-ea06e90da9f4?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/9435f68b-3ed2-4358-8340-ea06e90da9f4?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/9435f68b-3ed2-4358-8340-ea06e90da9f4?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/9435f68b-3ed2-4358-8340-ea06e90da9f4?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&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/3881)
Author
Owner

@SpinBoxx commented on GitHub (Aug 8, 2025):

Okai the last problem is resolved but the response from google is still null. I checked the url that signInSocial created and the redirect_uri in query param is not the same setted in auth.ts#redirect_url:

redirect_uri=https%3A%2F%2Fourdomain%2Fecom%2Fcallback%2Fgoogle (from the url of signInSocial)
process.env.BETTER_AUTH_URL + '/api/auth/callback/google',
<!-- gh-comment-id:3168329380 --> @SpinBoxx commented on GitHub (Aug 8, 2025): Okai the last problem is resolved but the response from google is still null. I checked the url that signInSocial created and the redirect_uri in query param is not the same setted in auth.ts#redirect_url: ``` redirect_uri=https%3A%2F%2Fourdomain%2Fecom%2Fcallback%2Fgoogle (from the url of signInSocial) ``` ``` process.env.BETTER_AUTH_URL + '/api/auth/callback/google', ```
Author
Owner

@dosubot[bot] commented on GitHub (Aug 8, 2025):

The root cause is that Better Auth v1.2.12 does not support custom redirectURI for Google OAuth—the framework always uses a hardcoded path like /api/auth/callback/google for the OAuth flow, regardless of what you set in the provider config. If you set a custom redirectURI, it can break the flow and cause Google to return a null user response see details.

To fix this, remove the redirectURI field from your Google provider config. Make sure the redirect URI registered in Google Cloud Console is exactly https://your-domain.com/api/auth/callback/google, and let Better Auth handle the callback at that endpoint reference. Any post-authentication logic should be handled after the default callback route, not by customizing the redirectURI.

If you need more flexible redirectURI handling, consider upgrading Better Auth, but for your current version, aligning all configs to use the default callback is the reliable solution.

To reply, just mention @dosu.


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

<!-- gh-comment-id:3168340931 --> @dosubot[bot] commented on GitHub (Aug 8, 2025): <!-- Answer --> The root cause is that Better Auth v1.2.12 does not support custom redirectURI for Google OAuth—the framework always uses a hardcoded path like `/api/auth/callback/google` for the OAuth flow, regardless of what you set in the provider config. If you set a custom redirectURI, it can break the flow and cause Google to return a null user response [see details](https://github.com/better-auth/better-auth/issues/3877). To fix this, remove the `redirectURI` field from your Google provider config. Make sure the redirect URI registered in Google Cloud Console is exactly `https://your-domain.com/api/auth/callback/google`, and let Better Auth handle the callback at that endpoint [reference](https://github.com/better-auth/better-auth/issues/3062). Any post-authentication logic should be handled after the default callback route, not by customizing the redirectURI. If you need more flexible redirectURI handling, consider upgrading Better Auth, but for your current version, aligning all configs to use the default callback is the reliable solution. <!-- 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/088abb79-ded0-4d56-848d-4d860b53d32b?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/088abb79-ded0-4d56-848d-4d860b53d32b?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/088abb79-ded0-4d56-848d-4d860b53d32b?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/088abb79-ded0-4d56-848d-4d860b53d32b?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/088abb79-ded0-4d56-848d-4d860b53d32b?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/088abb79-ded0-4d56-848d-4d860b53d32b?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/088abb79-ded0-4d56-848d-4d860b53d32b?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&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/3881)
Author
Owner

@Kinfe123 commented on GitHub (Aug 8, 2025):

Yeah, the redirectURI is handled by Better Auth and is hardcoded for convenience, similar to other OAuth providers like GitHub, using /api/auth/callback/:providerId, where providerId is predefined social providers (e.g., google). For more flexibility, you can use the genericOauth plugin in Better Auth.

<!-- gh-comment-id:3168753451 --> @Kinfe123 commented on GitHub (Aug 8, 2025): Yeah, the redirectURI is handled by Better Auth and is hardcoded for convenience, similar to other OAuth providers like GitHub, using `/api/auth/callback/:providerId`, where providerId is predefined social providers (e.g., google). For more flexibility, you can use the [genericOauth](https://www.better-auth.com/docs/plugins/generic-oauth) plugin in Better Auth.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#9752