Better Auth Google callback URL and redirect URI setup

Updated 27 Oct 2026 · Pewterkit Guides

Better Auth's Google callback is <baseURL>/api/auth/callback/google: http://localhost:3000/api/auth/callback/google locally and https://your-domain.com/api/auth/callback/google in production, each added under Authorized redirect URIs in Google Cloud. Set baseURL (or BETTER_AUTH_URL) explicitly; if you don't, the callback can be built from the wrong host and Google answers with redirect_uri_mismatch.

The exact redirect URI for your setup

Your setup Redirect URI to register in Google
Local dev on port 3000 http://localhost:3000/api/auth/callback/google
Production https://your-domain.com/api/auth/callback/google
Auth mounted on a custom basePath, for example /auth https://your-domain.com/auth/callback/google
Custom redirectURI set on the Google provider exactly the value you set
Preview deployments using the OAuth Proxy plugin only the production callback

The default path is /api/auth/callback/${providerName} because Better Auth mounts its routes at basePath, which defaults to /api/auth. If baseURL itself contains a path, that path overrides basePath.

Callback URL vs callbackURL vs trustedOrigins

Three settings have similar names and do different jobs:

Setting Where it lives What it controls
Google redirect URI Google Cloud OAuth client Where Google sends the authorization code. Must match baseURL + basePath + /callback/google.
callbackURL authClient.signIn.social({...}) Where Better Auth sends the user inside your app after sign-in. Defaults to /.
trustedOrigins betterAuth({...}) Origins allowed for requests and for redirect targets such as callbackURL. baseURL is trusted by default.

Changing callbackURL never fixes redirect_uri_mismatch; Google never sees it. A callbackURL on another origin that isn't in trustedOrigins is blocked by Better Auth instead.

Why redirect_uri_mismatch happens with Better Auth

  • baseURL not set. Without baseURL or BETTER_AUTH_URL, the base URL is inferred from the request. Better Auth's Google page warns that the callback may then default to localhost in production.
  • Wrong host or scheme behind a proxy. In dynamic base URL mode, forwarded host and protocol headers are ignored unless you set advanced.trustedProxyHeaders: true, so a TLS-terminating proxy can produce an http:// callback.
  • www vs apex. Google treats https://www.your-domain.com/... and https://your-domain.com/... as different URIs.
  • Changed base path. Mounting the handler somewhere other than /api/auth changes the callback, and Better Auth's docs say to update the Google entry to match.
  • Preview URLs. Google rejects wildcard (*) redirect URIs, so *.vercel.app can't be registered.
  • A relative redirect_uri. A Better Auth GitHub issue reported /callback/google being sent without the origin (Google answered with Error 400 invalid_request). A maintainer said it works on the latest release, though some users still reported it, so upgrade and confirm baseURL is set before debugging further.

Step-by-step fix

1. Create or check the Google OAuth client

As of Oct 2026: Google Cloud console → Google Auth Platform → Clients → your Web application client. Under Authorized redirect URIs, add:

http://localhost:3000/api/auth/callback/google
https://your-domain.com/api/auth/callback/google

Add https://www.your-domain.com/api/auth/callback/google too if users can reach www. Google says changes can take 5 minutes to a few hours to take effect.

2. Set environment variables

# .env
BETTER_AUTH_URL=http://localhost:3000        # production: https://your-domain.com
BETTER_AUTH_SECRET=<output of: openssl rand -base64 32>
GOOGLE_CLIENT_ID=1234567890-abc.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-client-secret

Use the production origin, with https, in your production environment variables. Better Auth throws an error in production if no secret is set.

3. Configure the Google provider

// lib/auth.ts
import { betterAuth } from "better-auth"

export const auth = betterAuth({
  baseURL: process.env.BETTER_AUTH_URL,
  socialProviders: {
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID as string,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
      prompt: "select_account", // optional: always show the account chooser
    },
  },
  // only needed for redirect targets on other origins:
  trustedOrigins: ["https://app.your-domain.com"],
})

Need a refresh token? Add accessType: "offline" and prompt: "select_account consent"; Google only issues one on first consent otherwise.

4. Mount the handler (Next.js App Router example)

// app/api/auth/[...all]/route.ts
import { auth } from "@/lib/auth"
import { toNextJsHandler } from "better-auth/next-js"

export const { GET, POST } = toNextJsHandler(auth)

This catch-all serves /api/auth/*, which includes /api/auth/callback/google. On another framework, mount Better Auth's handler at the same path so the URL Google calls exists.

5. Start sign-in from the client

// lib/auth-client.ts
import { createAuthClient } from "better-auth/react"
export const authClient = createAuthClient()

// in your sign-in button handler
await authClient.signIn.social({
  provider: "google",
  callbackURL: "/dashboard",
  errorCallbackURL: "/login?error=google",
})

Pass baseURL to createAuthClient only when the auth server is on a different domain; include the full path if you changed basePath.

Several domains or preview deployments

  • A fixed set of domains: use the object form of baseURL with allowedHosts (for example ["your-domain.com", "www.your-domain.com"]), protocol: "https" and a fallback. Allowed hosts are added to trustedOrigins automatically, but you still need one Google redirect URI per host.
  • Changing preview URLs: use the OAuth Proxy plugin. Production handles the provider callback and hands the result to the preview, so only the production callback is registered with the provider. The plugin's docs show a GitHub example (https://my-production-app.com/api/auth/callback/github); following Better Auth's default path, the Google equivalent would be https://your-domain.com/api/auth/callback/google.
import { oAuthProxy } from "better-auth/plugins"

export const auth = betterAuth({
  // ...
  plugins: [
    oAuthProxy({
      productionURL: "https://your-domain.com",
      secret: process.env.OAUTH_PROXY_SECRET,
    }),
  ],
})

Use the same secret in production and in each trusted preview (productionURL defaults to BETTER_AUTH_URL if you leave it out). Better Auth warns to keep that secret out of previews that run untrusted contributor code.

Test this on a preview deployment before relying on it.

How to confirm it worked

  1. In DevTools → Network, click the Google button and open the request to accounts.google.com. Its redirect_uri query parameter must equal one of your registered URIs exactly.
  2. After choosing an account, the browser should pass through /api/auth/callback/google and land on your callbackURL.
  3. If you land on /api/auth/error?error=... instead, the Google step worked and the error code names the next problem.
  • state_mismatch: the stored state wasn't found, its cookie was dropped, or it expired (10-minute lifetime). Common causes are serverless instances not sharing a database, the callback loading twice, or preview subdomains with mismatched cookie settings. Better Auth suggests a stable custom domain and checking the cookie's SameSite attribute and HTTPS-only flag.
  • no_callback_url: the flow wasn't started through Better Auth's own methods, or a proxy or middleware altered the state parameter.

Other guides: redirect_uri_mismatch explained · redirect_uri_mismatch on localhost · NextAuth Google redirect_uri_mismatch · Google OAuth consent screen

FAQ

What is the Better Auth Google redirect URL?

By default it is <baseURL>/api/auth/callback/google, for example https://your-domain.com/api/auth/callback/google. It changes only if you change basePath, put a path in baseURL, or set redirectURI on the provider.

Is BETTER_AUTH_URL required?

Better Auth falls back to inferring the URL from the request, but its docs advise against relying on that and recommend setting baseURL or BETTER_AUTH_URL. Its Google guide names an unset base URL as a cause of redirect_uri_mismatch.

Do I add my callbackURL to Google Cloud?

No. Google only needs the /api/auth/callback/google URI. Your callbackURL is an in-app destination checked against trustedOrigins; relative paths like /dashboard must start with a single /.

Should localhost stay in trustedOrigins in production?

No. Better Auth's security docs say not to leave the localhost origin in a production instance's trusted origins list. Keep the localhost redirect URI on a separate Google client or remove it when you no longer need it.

Sources

  1. Google | Better Auth
  2. Options | Better Auth
  3. Installation | Better Auth
  4. OAuth | Better Auth
  5. Basic Usage | Better Auth
  6. Security | Better Auth
  7. Next.js integration | Better Auth
  8. OAuth Proxy | Better Auth
  9. state_mismatch | Better Auth
  10. no_callback_url | Better Auth
  11. better-auth issue #6326 | GitHub
  12. Manage OAuth Clients | Google Cloud Platform Console Help
  13. Using OAuth 2.0 for Web Server Applications | Google for Developers

How this page was made: drafted with AI from the sources listed, reviewed by Pewterkit. Docs checked 27 Oct 2026. Spotted something out of date? Tell us.

Related guides