"Error 400: invalid_request" on Google sign-in: read the details and fix the cause

Updated Oct 27, 2026 · Pewterkit Guides

Error 400: invalid_request means Google refused the authorization request itself: it is malformed, missing a required parameter, or uses a sign-in method Google no longer supports. The detail line under the code (for example "Missing required parameter: client_id") tells you which, and each cause has a specific fix below.

What Google is telling you

Google documents invalid_request as a request that was improperly formatted, was missing required parameters, or used an authorization method Google doesn't support (Google OAuth 2.0 for web server apps). Google's help page for users adds that the app used an authorization method Google doesn't allow, and that only the app developer can fix it (Google Account Help).

How to read the details

  1. On the "Access blocked" page, click "If you are a developer of this app, see error details."
  2. Note the line under Error 400: invalid_request. Real examples: "Missing required parameter: client_id" (example), "Custom URI scheme is not enabled for your Android client" (example), or a message that the app doesn't comply with Google's OAuth 2.0 policy, followed by Request details: redirect_uri=... (example).
  3. Match the detail to a cause below.

A "doesn't comply with Google's OAuth 2.0 policy" message points at the redirect or flow type (causes 2 to 4). A "Missing required parameter" message points at the request parameters (causes 1 and 2).

If the code is something else, such as redirect_uri_mismatch, see Access blocked: This app's request is invalid for the full list.

Cause 1: a required parameter is missing or empty

Google's authorization endpoint (https://accounts.google.com/o/oauth2/v2/auth) needs client_id, redirect_uri, response_type and scope (Google OAuth 2.0 for web server apps). For OpenID Connect sign-in, scope must begin with openid (OpenID Connect).

The usual reason a parameter is missing is an environment variable that exists locally but not in production. Fail early instead of sending a broken request:

// Node / any server runtime
const clientId = process.env.GOOGLE_CLIENT_ID;
if (!clientId) {
  throw new Error("GOOGLE_CLIENT_ID is not set in this environment");
}

For Auth.js, the Google provider reads AUTH_GOOGLE_ID and AUTH_GOOGLE_SECRET (Auth.js Google provider). Add both to your host's production environment variables and redeploy so the new values are picked up.

Also check parameters your code builds by hand: scope values are space-separated, and server flows use response_type=code (Google OAuth 2.0 for web server apps).

Cause 2: the redirect URI breaks Google's rules

Google rejects redirect URIs that use plain HTTP (except localhost), raw IP addresses (except localhost IPs), wildcards, fragments (#...), path traversal, open redirects or URL-shortener domains (Google OAuth 2.0 for web server apps). Google's OAuth 2.0 Policies also say it may reject OAuth requests that don't come from, or resolve to, a protected (HTTPS) context (OAuth 2.0 Policies).

Rejected:  http://app.example.com/auth/callback     (HTTP on a public host)
Rejected:  https://203.0.113.10/auth/callback       (raw IP)
Rejected:  https://app.example.com/auth#callback    (fragment)
Accepted:  https://app.example.com/auth/callback
Accepted:  http://localhost:3000/auth/callback      (localhost is exempt from HTTPS)

Fix: serve the callback over HTTPS on a real domain and register that exact URI on your OAuth client. If the URI is valid but not registered, Google returns redirect_uri_mismatch instead; see redirect_uri_mismatch.

Cause 3: the old out-of-band (copy/paste) flow

Requests with redirect_uri set to urn:ietf:wg:oauth:2.0:oob, urn:ietf:wg:oauth:2.0:oob:auto or oob use the out-of-band flow, which Google blocked for all production clients on January 31, 2023; Google says the flow still works for apps with the Testing publishing status (OOB migration guide). Older CLI tools and scripts have reported this as Error 400: invalid_request (example), and Google notes it can also appear as redirect_uri_mismatch (Google OAuth 2.0 for web server apps).

Fix for desktop and CLI apps: switch to the loopback flow. Use a Desktop app client, start a local HTTP listener on a free port, and send redirect_uri=http://127.0.0.1:<port> (OAuth 2.0 for iOS & desktop apps). Search your code and .env files for urn:ietf:wg:oauth:2.0:oob, since some libraries set it for you (OOB migration guide).

Cause 4: a custom URI scheme redirect

Redirects such as myapp://callback are custom URI schemes. Google says they are no longer supported because of the risk of app impersonation, and are not supported on Android or Chrome apps (OAuth 2.0 for iOS & desktop apps). The request details show the scheme, for example redirect_uri=cursor://... (example). On Android the detail line may read "Custom URI scheme is not enabled for your Android client" (example).

Fix: on Android and iOS, use Google's recommended platform sign-in SDKs; for Chrome apps, use the Chrome Identity API; for desktop apps, use the loopback flow from cause 3 (OOB migration guide).

Cause 5: sign-in opened inside an embedded web view

Google's policies forbid sending the authorization request to an embedded user-agent under the developer's control (OAuth 2.0 Policies). This normally shows as Error 403: disallowed_useragent rather than 400, and is common with iOS/macOS WKWebView (Google OAuth 2.0 for web server apps). Open the sign-in URL in the system browser instead. Google lists SFSafariViewController, Google Sign-In for iOS and AppAuth as supported options. For a web page that may be loaded inside another app's in-app browser, see Sign in with Google button not working.

Cause 6: PKCE parameters are wrong

If you send PKCE parameters, Google expects a code_verifier of 43 to 128 characters (letters, digits, -, ., _, ~), a code_challenge that is the Base64URL-encoded SHA-256 of it with code_challenge_method=S256, or the verifier itself with plain. Only S256 and plain are accepted, and if you omit the method it defaults to plain (OAuth 2.0 for iOS & desktop apps). So if you compute an S256 challenge but omit code_challenge_method, Google treats it as plain and the code exchange fails. A missing or wrong verifier at the token step also makes the exchange fail, typically with an error such as invalid_grant rather than invalid_request.

A correct request, built in Node.js 16+:

import crypto from "node:crypto";

const verifier = crypto.randomBytes(32).toString("base64url"); // 43 chars
const challenge = crypto.createHash("sha256").update(verifier).digest("base64url");
const state = crypto.randomBytes(16).toString("hex");
// Store verifier and state in the user's session before redirecting.

const params = new URLSearchParams({
  client_id: process.env.GOOGLE_CLIENT_ID,
  redirect_uri: "https://app.example.com/auth/callback",
  response_type: "code",
  scope: "openid email profile",
  state,
  code_challenge: challenge,
  code_challenge_method: "S256",
});
const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?${params}`;

Then exchange the code, sending the same redirect_uri (Google OAuth 2.0 for web server apps) and the stored verifier as code_verifier (OAuth 2.0 for iOS & desktop apps):

const res = await fetch("https://oauth2.googleapis.com/token", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    code,                                   // from the callback query string
    client_id: process.env.GOOGLE_CLIENT_ID,
    client_secret: process.env.GOOGLE_CLIENT_SECRET,
    redirect_uri: "https://app.example.com/auth/callback",
    grant_type: "authorization_code",
    code_verifier: verifier,                // from the session
  }),
});

Make sure the callback runs on the same site that stored the verifier, so it can read it back.

How to confirm it worked

  • Start sign-in in a private window: Google shows the account chooser instead of Error 400: invalid_request.
  • Your callback receives code and the same state you sent, and the token request returns HTTP 200 with tokens.
  • Repeat on the production URL, not only on localhost, since environment variables and redirect URIs differ there.

FAQ

What does "Error 400: invalid_request" mean on Google sign-in?

Google rejected the authorization request because it was malformed, missing a required parameter, or used an authorization method Google doesn't support (Google OAuth 2.0 for web server apps). The detail line under the code says which.

How do I fix "Missing required parameter: client_id"?

Your app sent the request without a client ID, usually because an environment variable is unset in that environment. Set it on your host (for Auth.js, AUTH_GOOGLE_ID), redeploy, and retry (Auth.js Google provider).

Why do I get invalid_request with urn:ietf:wg:oauth:2.0:oob?

That is the out-of-band copy/paste flow, which Google blocked for all production clients in January 2023. Switch to the loopback redirect (http://127.0.0.1:<port>) or a platform SDK (OOB migration guide).

Can I use an http:// redirect URI with Google OAuth?

Only for localhost. Other redirect URIs must use HTTPS, and raw IP addresses are not allowed except localhost IPs (Google OAuth 2.0 for web server apps).

Is a user able to fix this error themselves?

No. Google says only the app developer can fix invalid_request; users can report it to the developer (Google Account Help).

Sources

  1. Using OAuth 2.0 for Web Server Applications - Google for Developers
  2. OAuth 2.0 for iOS & Desktop Apps - Google for Developers
  3. Out-Of-Band (OOB) flow Migration Guide - Google for Developers
  4. OAuth 2.0 Policies - Google for Developers
  5. OpenID Connect - Google for Developers
  6. Understand & fix error codes - Google Account Help
  7. Google provider - Auth.js
  8. Zammad community: Missing required parameter: client_id (example screen)
  9. B4X forum: Access blocked, the request is invalid (custom URI scheme example)
  10. Cursor forum: Google Auth on Cursor (custom scheme request details example)
  11. google/clusterfuzz issue #2576 (OOB example)

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.

Related guides