Firebase auth/cancelled-popup-request, why a second popup cancels the first
auth/cancelled-popup-request means a new popup sign-in started while an earlier one was still pending, so Firebase canceled the older one.
Stop the duplicate call: disable the button and add a guard flag while sign-in is pending, start sign-in from a click handler instead of an effect, and treat this code as harmless.
What Firebase does
The Firebase JS SDK allows one popup operation at a time. From the SDK source (as of Oct 2026):
- A source comment says: "Only one popup is ever shown at once."
- When a new popup operation is created, the SDK cancels the current one, which rejects with
AuthErrorCodes.EXPIRED_POPUP_REQUEST. That constant's value is"auth/cancelled-popup-request", and the message is "This operation has been cancelled due to another conflicting popup being opened." - Cleaning up the canceled operation closes its window, if one was open.
signInWithPopup,linkWithPopupandreauthenticateWithPopupall create this same popup operation, so any two of them can cancel each other.
The cancel happens the moment the second call is made. signInWithPopup is an async function, and MDN explains that an async function's code "up to and including the first await expression (if there is one), is run synchronously." The popup operation is created in that synchronous part.
So the error is never about the provider or your settings. Your code (or the user) started sign-in twice. The newest call keeps going.
Common causes
- Double clicks and repeated clicks. MDN: "
dblclickfires after twoclickevents." A double click on a sign-in button runs aclickhandler twice. So does clicking again while the popup is open. - Two handlers start sign-in from one click. For example, the button's click handler and the form's submit handler both call
signInWithPopup. A<button>inside a<form>is a submit button by default: MDN sayssubmit"is the default if the attribute is not specified for buttons associated with a<form>." - React Strict Mode in development. React's docs say that with Strict Mode on, "React will also run one extra setup+cleanup cycle in development for every Effect." An effect that calls
signInWithPopupon mount therefore calls it twice, and the first call is canceled. The docs add that these checks are "development-only and do not impact the production build," which is why the error can show up locally and vanish in production. - Starting a second popup on purpose, such as a "Link GitHub" popup while a Google sign-in popup is still open.
Fix 1: plain JavaScript (disable the button and add a guard flag)
import {
getAuth, GoogleAuthProvider, signInWithPopup, AuthErrorCodes,
} from 'firebase/auth';
const auth = getAuth();
const provider = new GoogleAuthProvider();
let signInPending = false;
signInButton.addEventListener('click', () => {
if (signInPending) return; // ignore clicks while a popup is open
signInPending = true;
signInButton.disabled = true;
signInWithPopup(auth, provider)
.then((result) => showSignedIn(result.user))
.catch((error) => {
if (error.code === AuthErrorCodes.EXPIRED_POPUP_REQUEST) return; // replaced by a newer popup
if (error.code === AuthErrorCodes.POPUP_CLOSED_BY_USER) return; // user backed out
showSignInError(error);
})
.finally(() => {
signInPending = false;
signInButton.disabled = false;
});
});
Why both? MDN says the disabled attribute "prevents the user from interacting with the button: it cannot be pressed or focused." The flag also stops calls that don't come from that button, such as a keyboard shortcut or another component calling the same function.
Keep signInWithPopup as the first awaited call in the handler. Waiting on other work first can get the popup blocked; see auth/popup-blocked.
Fix 2: React (a ref for the guard, state for the UI)
import { useRef, useState } from 'react';
import { GoogleAuthProvider, signInWithPopup, AuthErrorCodes } from 'firebase/auth';
import { auth } from './firebase'; // created once with getAuth(app)
const provider = new GoogleAuthProvider();
export function GoogleSignInButton() {
const [pending, setPending] = useState(false);
const inFlight = useRef(false);
function handleClick() {
if (inFlight.current) return; // second click before React re-renders
inFlight.current = true;
setPending(true);
signInWithPopup(auth, provider)
.catch((error) => {
if (
error.code === AuthErrorCodes.EXPIRED_POPUP_REQUEST ||
error.code === AuthErrorCodes.POPUP_CLOSED_BY_USER
) {
return; // not a failure
}
console.error(error.code, error.message);
})
.finally(() => {
inFlight.current = false;
setPending(false);
});
}
return (
<button type="button" onClick={handleClick} disabled={pending}>
Continue with Google
</button>
);
}
Why a ref as well as state: React's docs say the set function "only updates the state variable for the next render," so a fast second click can still see pending as false. A ref is a plain mutable object, and React's docs say you can read or write refs from event handlers. type="button" stops the button from also submitting a surrounding form.
Fix 3: move sign-in out of effects (React Strict Mode)
Before (two calls in development; the first fails with this error):
useEffect(() => {
signInWithPopup(auth, provider); // runs twice under Strict Mode in development
}, []);
After (one call, from the click):
<button type="button" onClick={handleClick} disabled={pending}>
Continue with Google
</button>
React's own rule: "If this logic is caused by a particular interaction, keep it in the event handler." Sign-in is caused by a click. A popup started from an effect also has no click behind it, so browsers may block it anyway.
Don't fix this by removing Strict Mode. It only shows the double call in development; double clicks still happen in production.
How to confirm it is fixed
- Double-click the sign-in button quickly. Only one popup should open, and the console should show no
auth/cancelled-popup-request. - With React Strict Mode on, load the page in development. No popup should open until you click.
- Search your code for every popup call and check each one sits in a single event handler:
bash
grep -rnE "signInWithPopup|linkWithPopup|reauthenticateWithPopup" src/
- Sign in normally.
signInWithPopupshould resolve withresult.user.
Related problems
- The popup never opens: auth/popup-blocked.
- The popup opened and Firebase says it was closed: auth/popup-closed-by-user.
- Full setup walkthroughs: Vite + React and Next.js.
FAQ
Is auth/cancelled-popup-request a real failure?
Not by itself. It means an older popup operation was replaced by a newer one, and the newer one carries on. Fix the duplicate call, and treat the code as a no-op in your error handling.
Why does it only happen in development?
React Strict Mode runs an extra setup and cleanup cycle for every Effect in development only. If an effect starts sign-in, it starts it twice there. Move sign-in into a click handler.
Does Firebase close the first popup?
Yes, if it had opened. The SDK's cleanup for the canceled operation closes that operation's window.
Can I just ignore the error?
You can safely ignore the code in your error handler. Still remove the duplicate call: opening a second window needs its own user gesture, per MDN, so a duplicate call can also end in auth/popup-blocked.
Sources
- Firebase JS SDK source: packages/auth/src/platform_browser/strategies/popup.ts (one popup at a time, cancel, cleanup)
- Firebase JS SDK source: packages/auth/src/core/errors.ts (cancelled-popup-request message)
- Firebase JS API reference: firebase/auth (signInWithPopup, AuthErrorCodes)
- MDN: async function
- MDN: Element: dblclick event
- MDN: The button element (disabled, type)
- MDN: Window: open() method
- React: StrictMode
- React: You Might Not Need an Effect
- React: useState
- React: useRef
How this page was made: drafted with AI from the sources listed, reviewed by Pewterkit. Docs checked 26 Oct 2026. Spotted something out of date? Tell us.