Sign in with Google button or One Tap not showing: how to fix it

Updated 27 Oct 2026 · Pewterkit Guides

When the Google Identity Services (GIS) button stays blank or fails on click, the cause is usually an origin missing from Authorized JavaScript origins (exact scheme, host and port), or the gsi/client script being blocked or used before it loads. When only One Tap is missing, it is usually suppressed on purpose: the page isn't on HTTPS, a recent close started a cooldown, or, with FedCM, the browser's settings decide when it shows.

Match your symptom

What you see Most likely cause Go to
Console: [GSI_LOGGER]: The given origin is not allowed for the given client ID Page origin not in Authorized JavaScript origins Fix 1
Popup shows Error 400: origin_mismatch Same as above Fix 1
google is not defined / Cannot read properties of undefined (reading 'accounts') Your code runs before gsi/client loads Fix 2
CSP violation in console, or FedCM get() rejects with NetworkError Content Security Policy blocks accounts.google.com/gsi/ Fix 3
FedCM get() rejects with NotAllowedError inside an iframe Missing allow="identity-credentials-get" Fix 3
Popup opens blank Cross-Origin-Opener-Policy (when FedCM is off) Fix 3
Button works, One Tap never appears HTTPS, cooldown, browser setting, or auto_prompt off Fix 4
Click works but your server never gets the credential popup vs redirect mode mismatch Fix 5

Fix 1: Add the exact origin to Authorized JavaScript origins

GIS checks the page's origin, not a path. As of Oct 2026, open Google Cloud console → Google Auth Platform → Clients → your Web application client, and under Authorized JavaScript origins add every origin that shows the button:

http://localhost
http://localhost:5173
https://example.com
https://www.example.com
  • Google's GIS setup guide says to add both http://localhost and http://localhost:<port> for local testing.
  • Include the port whenever it isn't the default; an origin with a different port is a different origin.
  • No path, query, fragment or trailing slash, and no wildcard (*). Only localhost may use http.
  • www and the apex domain are separate entries.
  • Make sure the client_id in your page belongs to the client you edited.
  • Google says changes can take 5 minutes to a few hours to take effect, so retest later before changing more.

Fix 2: Load the library before you call it

Load the script from Google (Google says self-hosting it isn't supported):

<script src="https://accounts.google.com/gsi/client" async></script>

HTML API (no JavaScript needed):

<div id="g_id_onload"
     data-client_id="YOUR_CLIENT_ID.apps.googleusercontent.com"
     data-callback="handleCredentialResponse"
     data-auto_prompt="false">
</div>
<div class="g_id_signin" data-type="standard"></div>

<script>
  function handleCredentialResponse(response) {
    // response.credential is the ID token (JWT): send it to your server
  }
</script>

JavaScript API: because the script is async, wait for it. window.onGoogleLibraryLoad fires after the library loads (it is a shortcut for window.onload):

<div id="buttonDiv"></div>
<script>
  window.onGoogleLibraryLoad = () => {
    google.accounts.id.initialize({
      client_id: "YOUR_CLIENT_ID.apps.googleusercontent.com",
      callback: handleCredentialResponse,
    });
    google.accounts.id.renderButton(
      document.getElementById("buttonDiv"),
      { theme: "outline", size: "large" }
    );
    google.accounts.id.prompt(); // optional: also request One Tap
  };
</script>

Don't mix the two: Google warns against using g_id_onload / g_id_signin together with the JavaScript API.

React and other SPAs: inject the script once and render after it loads, into an element that exists:

import { useEffect, useRef } from "react";

export function GoogleButton({ onCredential }) {
  const ref = useRef(null);

  useEffect(() => {
    const render = () => {
      window.google.accounts.id.initialize({
        client_id: import.meta.env.VITE_GOOGLE_CLIENT_ID, // Vite; use your framework's env access
        callback: (res) => onCredential(res.credential),
      });
      window.google.accounts.id.renderButton(ref.current, {
        theme: "outline",
        size: "large",
        width: 300, // pixels, maximum 400
      });
    };

    if (window.google?.accounts?.id) return render();

    let script = document.querySelector('script[src="https://accounts.google.com/gsi/client"]');
    if (!script) {
      script = document.createElement("script");
      script.src = "https://accounts.google.com/gsi/client";
      script.async = true;
      document.head.appendChild(script);
    }
    script.addEventListener("load", render, { once: true });
    return () => script.removeEventListener("load", render);
  }, [onCredential]);

  return <div ref={ref} />;
}

This component uses only the documented initialize and renderButton calls. Test this on a preview deployment before relying on it.

Fix 3: Allow GIS in your CSP and headers

If you send a Content Security Policy, add Google's documented sources:

Content-Security-Policy:
  script-src 'self' https://accounts.google.com/gsi/client;
  frame-src https://accounts.google.com/gsi/;
  connect-src 'self' https://accounts.google.com/gsi/;
  style-src 'self' https://accounts.google.com/gsi/style;

If you rely on default-src, add https://accounts.google.com/gsi/ there. Google notes that without the right CSP, the FedCM One Tap or button won't display, and a blocked connect-src shows up as FedCM get() rejects with NetworkError.

Other headers:

  • Referrer-Policy: Google recommends strict-origin-when-cross-origin; for local http testing it documents no-referrer-when-downgrade.
  • Cross-Origin-Opener-Policy: only when FedCM is disabled, use same-origin-allow-popups; otherwise the popup can open blank.
  • Iframes: if the button or One Tap runs in a cross-origin iframe, add allow="identity-credentials-get" to every parent iframe:
<iframe src="https://login.example.com/widget" allow="identity-credentials-get"></iframe>

Fix 4: One Tap not showing

Work through these in order:

  1. HTTPS. Serve the page over HTTPS (localhost is the exception for testing). Google's JavaScript reference lists secure_http_required among the reasons One Tap is not displayed.
  2. Is it requested? One Tap appears only if you call google.accounts.id.prompt() or leave data-auto_prompt on. data-auto_prompt="false" turns it off.
  3. Cooldown after closing. Without FedCM, each close suppresses One Tap for longer: two hours, then one day, one week, and four weeks. A successful sign-in with One Tap or the button resets it. With FedCM, browser vendors may set their own cooldown; Google says Chrome users can reset it from the lock icon in the address bar → Reset Permission (the icon can look different in newer Chrome versions).
  4. Chrome's third-party sign-in setting. With FedCM, One Tap needs the browser's Third-party sign-in setting allowed (globally or for your site). Google notes this affects One Tap, not the button.
  5. Browsers with Intelligent Tracking Prevention. Google lists Safari, Firefox and Chrome on iOS: they show a different One Tap flow (welcome page, then a popup), automatic sign-in isn't supported, and the upgraded flow needs itp_support: true.
  6. No Google session. If nobody is signed in to Google in that browser, there is no account to offer in One Tap. Keep the button as a visible fallback.
  7. Outdated code. With FedCM, Google says the isDisplayMoment(), isDisplayed(), isNotDisplayed() and getNotDisplayedReason() notifications are not supported, and getSkippedReason() no longer gives a detailed reason. Code that waits for those callbacks before showing a fallback will never fire. use_fedcm_for_prompt is deprecated and ignored, so removing it is harmless.
  8. After sign-out, call google.accounts.id.disableAutoSelect() so automatic sign-in doesn't loop.

Fix 5: Popup mode vs redirect mode

ux_mode: "popup" (default) ux_mode: "redirect"
Where the credential goes Your JavaScript callback POSTed to login_uri
Required setting callback (popup mode doesn't support redirects) login_uri: absolute https URI (http only on localhost)
Google Cloud entry Authorized JavaScript origin JavaScript origin and the login_uri under Authorized redirect URIs

ux_mode has no effect on One Tap. Set either callback or login_uri, not both: Google's One Tap guide says you can't set both, and its button guide says data-callback takes precedence if you do. For a redirect_uri_mismatch in redirect mode, see redirect_uri_mismatch explained.

How to confirm it worked

  1. The button renders and the console has no [GSI_LOGGER] or CSP errors.
  2. Clicking it, choosing an account, and returning calls your callback with response.credential (or POSTs it to login_uri).
  3. Your server validates that ID token: Google signature, aud equal to your client ID, iss of accounts.google.com or https://accounts.google.com, and an unexpired exp. In Node.js, OAuth2Client.verifyIdToken from google-auth-library checks all four.
  4. Test One Tap in a fresh browser profile where you haven't closed it before.

FAQ

Why is my Google sign-in button not showing at all?

Check the console first. The given origin is not allowed for the given client ID means the page's exact origin (including port) is missing from Authorized JavaScript origins. No error usually means your code ran before gsi/client loaded, or a CSP blocked it.

Why does One Tap show on one of my domains but not another?

One Tap may not display on a page that isn't served over HTTPS, and each origin needs its own Authorized JavaScript origin entry. With FedCM, Chrome users can also allow or block third-party sign-in prompts per site, so one domain can be blocked while another is allowed.

How do I reset the One Tap cooldown while testing?

A successful sign-in with One Tap or the button resets it. With FedCM in Chrome, Google says to click the lock icon in the address bar and select Reset Permission; a new browser profile also works.

Can I self-host or bundle the gsi/client script?

No. Google's setup guide says to load https://accounts.google.com/gsi/client on each page where users might sign in and that self-hosting it isn't supported. If your CSP blocks that request, the button can't render (see Fix 3).

What is Error 400: origin_mismatch?

Google returns it when a request comes from a JavaScript origin that isn't registered on the OAuth client. Add the exact origin and wait for the change to apply.

Sources

  1. Setup | Sign In With Google for Web | Google for Developers
  2. Display the Sign In With Google button | Google for Developers
  3. Display Google One Tap | Google for Developers
  4. Understand the One Tap user experience | Google for Developers
  5. Migrate to FedCM | Google for Developers
  6. Sign In With Google JavaScript API reference | Google for Developers
  7. Verify the Google ID token on your server side | Google for Developers
  8. Manage OAuth Clients | Google Cloud Platform Console Help
  9. Sign in with Google ends with "[GSI_LOGGER]: The given origin is not allowed for the given client ID" | Google Developer forums

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