Better Auth Google callback URL and redirect URI setup
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
baseURLnot set. WithoutbaseURLorBETTER_AUTH_URL, the base URL is inferred from the request. Better Auth's Google page warns that the callback may then default tolocalhostin 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 anhttp://callback. wwwvs apex. Google treatshttps://www.your-domain.com/...andhttps://your-domain.com/...as different URIs.- Changed base path. Mounting the handler somewhere other than
/api/authchanges the callback, and Better Auth's docs say to update the Google entry to match. - Preview URLs. Google rejects wildcard (
*) redirect URIs, so*.vercel.appcan't be registered. - A relative
redirect_uri. A Better Auth GitHub issue reported/callback/googlebeing sent without the origin (Google answered with Error 400invalid_request). A maintainer said it works on the latest release, though some users still reported it, so upgrade and confirmbaseURLis 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
baseURLwithallowedHosts(for example["your-domain.com", "www.your-domain.com"]),protocol: "https"and afallback. Allowed hosts are added totrustedOriginsautomatically, 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 behttps://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
- In DevTools → Network, click the Google button and open the request to
accounts.google.com. Itsredirect_uriquery parameter must equal one of your registered URIs exactly. - After choosing an account, the browser should pass through
/api/auth/callback/googleand land on yourcallbackURL. - If you land on
/api/auth/error?error=...instead, the Google step worked and the error code names the next problem.
Related errors after the callback
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'sSameSiteattribute and HTTPS-only flag.no_callback_url: the flow wasn't started through Better Auth's own methods, or a proxy or middleware altered thestateparameter.
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
- Google | Better Auth
- Options | Better Auth
- Installation | Better Auth
- OAuth | Better Auth
- Basic Usage | Better Auth
- Security | Better Auth
- Next.js integration | Better Auth
- OAuth Proxy | Better Auth
- state_mismatch | Better Auth
- no_callback_url | Better Auth
- better-auth issue #6326 | GitHub
- Manage OAuth Clients | Google Cloud Platform Console Help
- 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 Oct 27, 2026. Spotted something out of date? Tell us.