(Update) Prospective Solution
It looks like the google developers have added the error handlers now into the new Google Identity Services. :)
Checkout the documentation at https://developers.google.com/identity/oauth2/web/guides/error.
(I still haven't tested it. Hence putting it as a prospective solution). Happy coding!
Original Answer
Here are the two solutions which you can consider if you're facing this issue.
Solution 1
Go back to the old gapi
based login. (Not recommended, as it will be deprecated soon). For more details, on deprecation, refer to this blog by Google.
Solution 2
We add a javascript focus
event listener just after opening the popup. So, whenever the user closes the popup and returns to the parent window
, we shall consider it as client_focused_back_to_window
/ pop_up_closed
event.
The only edge case is when the user doesn't close the popup and directly returns to the window; the focus
event listener will be fired. But I think that's okay because if the user again clicks on Sign In with Google
button again, the same pop-up window gets reused (thanks to _blank
parameter used by Google Identity services while creating the popUp window).
const client = (window as any).google.accounts.oauth2.initTokenClient({
client_id: process.env.GOOGLE_CLIENT_ID,
scope: `profile email`,
callback: '' // defined at request time
});
/**
* Function to login the user and return the tokenResponse
*
* It throws error if the login fails or the user cancels the login process
*/
const loginUser = async () => {
const tokenResponse = await new Promise<google.accounts.oauth2.TokenResponse>(
(resolve, reject) => {
const focusEventHandler = () => {
reject({
error: 'client_focused_back_to_window',
});
window.removeEventListener('focus', focusEventHandler); // removing the event listener to avoid memory leaks
};
// adding an event listener to detect if user is back to the webpage
// if the user "focus" back to window then we shall close the current auth session
window.addEventListener('focus', focusEventHandler);
// Settle this promise in the response callback for requestAccessToken()
client.callback = (resp) => {
if (resp.error) {
reject(resp);
}
resolve(resp);
};
// requesting access token
client.requestAccessToken({ prompt: 'consent' });
},
);
return tokenResponse;
}
PS: We've been using this solution in production, and so far, thousands, if not millions, of users have tried to log in via Google. Everything is working fine so far.