Google Identity Service Oauth2 检测同意弹出窗口是否关闭

sub*_*amX 7 javascript google-oauth google-signin google-identity react-google-login

我正在使用 Google 身份服务,并面临一些问题。看一下下面的函数loginUser并得到access_token

const client = (window as any).google.accounts.oauth2.initTokenClient({
  client_id: process.env.GOOGLE_CLIENT_ID,
  scope: `profile email`,
  callback: '' // defined at request time
});

const loginUser = async () => {
  const tokenResponse = await new Promise<TokenResponse>((resolve, reject) => {
    try {
      // Settle this promise in the response callback for requestAccessToken()
      client.callback = (resp) => {
        if (resp.error !== undefined) {
          reject(resp);
        }
        resolve(resp);
      };
      // requesting access token
      client.requestAccessToken({ prompt: 'consent' });
    } catch (err) {
      console.log(err)
    }
  });
  return tokenResponse;
}
Run Code Online (Sandbox Code Playgroud)

调用loginUser()会导致一个新的弹出窗口。

  • 如果用户选择一个帐户,我会得到tokenResponse(其中包含access_token)。效果很好。
  • 但是,如果用户关闭pop-up,则Promise永远不会解析,因为我们正在等待回调触发,而这永远不会发生。

有没有办法检测用户是否关闭了pop-up

小智 7

我认为你可以在“error_callback”中做一些事情。您可以在以下位置找到详细信息:处理错误

const client = google.accounts.oauth2.initCodeClient({
  client_id: 'YOUR_GOOGLE_CLIENT_ID',
  scope: 'https://www.googleapis.com/auth/calendar.readonly',
  ux_mode: 'popup',
  callback: myCallback,
  error_callback: myErrorCallback  // You can do something when popup window closed
});
Run Code Online (Sandbox Code Playgroud)

  • 看起来 Google 最近将这些 error_handlers 添加到了 Google 身份服务中。感谢你的分享。让我在演示应用程序中检查一下。 (2认同)

sub*_*amX 5

(更新)预期解决方案

看起来谷歌开发人员现在已经将错误处理程序添加到新的谷歌身份服务中。:) 查看文档https://developers.google.com/identity/oauth2/web/guides/error

(我还没有测试过它。因此将其作为一个潜在的解决方案)。快乐编码!

原答案

如果您遇到此问题,可以考虑以下两种解决方案。

解决方案1

返回到旧的gapi登录状态。(不推荐,因为它很快就会被弃用)。有关弃用的更多详细信息,请参阅Google 的此博客。

解决方案2

focus我们在打开弹出窗口后添加一个 JavaScript事件监听器。因此,每当用户关闭弹出窗口并返回到 时parent window,我们都将其视为client_focused_back_to_window/pop_up_closed事件。

唯一的边缘情况是用户不关闭弹出窗口并直接返回到窗口;事件focus监听器将被触发。但我认为这没关系,因为如果用户再次单击Sign In with Google按钮,相同的弹出窗口将被重复使用(感谢_blankGoogle Identity 服务在创建弹出窗口时使用的参数)。

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;
}
Run Code Online (Sandbox Code Playgroud)

PS:我们一直在生产中使用这个解决方案,到目前为止,已有数千甚至数百万用户尝试通过 Google 登录。到目前为止一切都运行良好。

  • 很好的解决方法。不幸的是,当需要对页面加载进行授权并且弹出窗口被浏览器阻止时,该功能不起作用。解决此问题的方法可能是挂接 console.error 并监视日志“无法在 url 上打开弹出窗口:...可能被浏览器阻止?” (2认同)