Fix NextAuth (Auth.js) Google redirect_uri_mismatch
Auth.js / NextAuth sends Google a redirect_uri of <your origin>/api/auth/callback/google, and Google returns Error 400 redirect_uri_mismatch unless that exact string is listed under Authorized redirect URIs on your OAuth client.
Add one entry per origin you sign in from (localhost, production, www), and make sure Auth.js computes the origin you expect (AUTH_URL / NEXTAUTH_URL, trustHost).
Why it happens
Auth.js builds the redirect URI from three parts: the origin of your app, the auth base path (default /api/auth in next-auth), and /callback/google. Google compares the result with your registered list character by character: the http or https scheme, case and trailing slash must all match, and wildcards (*) are not allowed. Plain http is accepted only for localhost.
The mismatch usually comes from one of these:
| Cause | What Google receives | Registered value |
|---|---|---|
| Only localhost registered | https://example.com/api/auth/callback/google |
http://localhost:3000/api/auth/callback/google |
App reached on www (or apex) only |
https://www.example.com/... |
https://example.com/... |
| Proxy host not trusted or not forwarded, so Auth.js sees the internal host | http://localhost:3000/... or http://10.x.x.x/... |
your public URL |
Leftover AUTH_URL / NEXTAUTH_URL from another environment |
that old origin | your current origin |
| Custom base path | https://example.com/custom/auth/callback/google |
https://example.com/api/auth/callback/google |
| Vercel preview deployment | https://my-app-git-branch-team.vercel.app/... |
production only (wildcards can't be registered) |
Step 1: See the redirect_uri Auth.js actually sent
- Open DevTools → Network, click your Google sign-in button, and find the request to
accounts.google.com. - Copy the value of its
redirect_uriquery parameter. That is the exact string Google checked. - Optional (NextAuth v4 documents this endpoint): open
/api/auth/providerson the same origin. It returns each configured provider with its sign-in and callback URLs, so you can comparecallbackUrlwith your Google entries.
If the value is the wrong origin, fix the environment (Step 3 or 4) first; if it is correct, register it (Step 2).
Step 2: Add the exact entries in Google Cloud
As of Oct 2026, the OAuth client lives under Google Auth Platform → Clients in the Google Cloud console. Open your Web application client and add, under Authorized redirect URIs, one line per origin:
http://localhost:3000/api/auth/callback/google
https://example.com/api/auth/callback/google
https://www.example.com/api/auth/callback/google
- Use the port your dev server really runs on.
- Add the
wwwline only if users can reach that host. - No trailing slash, no wildcard, no fragment.
- Save, then wait: Google says changes to these settings can take 5 minutes to a few hours to take effect.
You don't need an Authorized JavaScript origin for this server-side flow; the redirect URI is what Google checks.
Step 3: Auth.js v5 (next-auth@5) configuration
auth.ts in the project root:
import NextAuth from "next-auth"
import Google from "next-auth/providers/google"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Google],
})
app/api/auth/[...nextauth]/route.ts:
import { handlers } from "@/auth"
export const { GET, POST } = handlers
.env.local (v5 reads AUTH_GOOGLE_ID and AUTH_GOOGLE_SECRET automatically):
AUTH_SECRET="generate-a-long-random-value"
AUTH_GOOGLE_ID="1234567890-abc.apps.googleusercontent.com"
AUTH_GOOGLE_SECRET="your-client-secret"
How v5 decides the origin:
- No
AUTH_URLneeded in most setups. The upgrade guide says the host is detected from the request headers. - Behind a reverse proxy (Docker, nginx, a load balancer), set
AUTH_TRUST_HOST=true(same astrustHost: truein the config) so Auth.js trusts theX-Forwarded-Hostheader. Without it, sign-in may fail or the callback may be built from the wrong host. Make sure your proxy forwards that header. - On Vercel and Cloudflare Pages trust is inferred automatically when the
VERCELorCF_PAGESvariable is present. - Custom base path: set
AUTH_URLto the full auth route, for exampleAUTH_URL=https://example.com/web/auth, and registerhttps://example.com/web/auth/callback/google. - Stale values: v5 treats
NEXTAUTH_URLas an alias ofAUTH_URL. If either is set to an old origin, remove or correct it, because a value that is set can override the detected host.
Step 4: NextAuth v4 differences
// pages/api/auth/[...nextauth].js
import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"
export default NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
],
})
- Set
NEXTAUTH_URLto the canonical URL of your site in production, for exampleNEXTAUTH_URL=https://example.com. - On Vercel you can leave it unset, but only if Automatically expose System Environment Variables is enabled in the project settings (as of Oct 2026).
- With a custom base path, include the full route:
NEXTAUTH_URL=https://example.com/custom-route/api/auth, and passbasePathto<SessionProvider>. NEXTAUTH_URL_INTERNALchanges only server-side calls; it does not change the redirect URI Google sees.
Step 5: Vercel preview deployments
Each preview gets a new hostname, and Google won't accept a wildcard. Auth.js v5 solves this with a redirect proxy:
- Pick a stable deployment, for example
https://auth.example.com. - In both the stable and preview environments, set:
AUTH_REDIRECT_PROXY_URL="https://auth.example.com/api/auth"
AUTH_SECRET="same-value-in-every-environment"
- Register only the stable callback in Google:
https://auth.example.com/api/auth/callback/google.
The proxy is not enabled if the variable is missing in the stable environment, and AUTH_SECRET must be identical across the stable and preview deployments. Auth.js describes this variable as meant for advanced use cases.
Test this on a preview deployment before relying on it.
How to confirm it worked
- The
redirect_uriin the Network request matches one of your Google entries exactly. - After choosing an account, the browser lands on
/api/auth/callback/googleand then back in your app, with no Google error page. GET /api/auth/sessionreturns a session object instead of an empty object.- Test each origin you registered (localhost, apex, www, stable auth domain) at least once.
Related problems
- The general version of this error for any stack: redirect_uri_mismatch explained
- Only failing on your machine: redirect_uri_mismatch on localhost
- Error 400 with a different reason: Error 400: invalid_request on Google sign-in
- Using Better Auth instead: Better Auth Google callback URL
FAQ
What is the NextAuth Google callback URL?
It is https://<your-domain>/api/auth/callback/google, and http://localhost:3000/api/auth/callback/google in development. If you change the auth base path, the callback moves with it, so register the full path that /api/auth/providers or the Network tab shows.
Do I need AUTH_URL or NEXTAUTH_URL with Auth.js v5?
Usually not: v5 detects the host from request headers. Set AUTH_URL only for a custom base path, and set AUTH_TRUST_HOST=true when you run behind a reverse proxy that is not Vercel or Cloudflare Pages.
Why does production send a localhost redirect_uri?
Either AUTH_URL / NEXTAUTH_URL still holds http://localhost:3000, or a proxy hides the public host and Auth.js isn't set to trust forwarded headers. Remove the stale variable or set AUTH_TRUST_HOST=true, redeploy, and check the Network request again.
Can I add a wildcard for Vercel preview URLs in Google Cloud?
No. Google rejects * in redirect URIs. Use AUTH_REDIRECT_PROXY_URL so every preview signs in through one stable callback.
I added the URI but still see the error. Why?
Google notes that client changes can take 5 minutes to a few hours to apply. Also check that you edited the same client ID your app uses, and that scheme, host, port and path match with no trailing slash.
Sources
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.