javascriptreactjsnext.jsreact-hooksoauth

Why does window.opener become null when using Google OAuth in a popup, and how can I communicate the access token back to the parent window?


I'm working with Google OAuth in my React application, and I'm implementing it through a popup. I'm using Passport with my custom backend. The initial URL that I supply to the popup window is the backend entry point that initializes the OAuth process. Google then sends a response to my callback on my backend.

After that, I need to communicate the JWT access token back that I generate on the backend to my frontend. I'm doing this by redirecting respondign with a redirect to a frontend route, with the access token as a query parameter in the redirect URL. The problem I'm facing, however, is that the window.opener in the popup becomes null after the Google OAuth process. This prevents me from sending the access token back to the parent window using window.opener.postMessage.

I have verified that the window.opener is not null when I pass in the same route my backend redirects my frontend to, as the initial url to the popup. The issue occurs when I pass in my backend endpoint for initializing google OAUTH (so it redirects to Googles sign in).

Unfortunately, I can't use cookies or localStorage due to my application's restrictions.

Has anyone faced a similar issue? Any advice or guidance would be greatly appreciated. Thanks!


Solution

  • You can use a BroadcastChannel to communicate without having a direct reference:

    The BroadcastChannel interface represents a named channel that any browsing context of a given origin can subscribe to. It allows communication between different documents (in different windows, tabs, frames or iframes) of the same origin.

    The listener:

    const channel = new BroadcastChannel('auth');
    channel.addEventListener ('message', (event) => {
     console.log(event.data);
    });
    

    The sender:

    const channel = new BroadcastChannel('auth');
    channel.postMessage(data);
    

    Hook:

    You can wrap this in a custom hook that returns the message (received items) and a sendMessage to post items. Since BroadcastChannel is bi-directional you can use either or both.

    const useBroadcastChannel = name => {
      const [message, setMessage] = useState()
      const channel = useRef()
      
      useEffect(() => {
        channel.current = new BroadcastChannel(name);
        
        channel.current.addEventListener('message', event => {
          setMessage(event.data)
        })
        
        return () => {
          channel.current?.close()
        }
      }, [name])
      
      const sendMessage = useCallback(data => {
        channel.current?.postMessage(data)
      }, [])
      
      return {
        message,
        sendMessage
      }
    }
    

    Usage:

    const { message, sendMessage } = useBroadcastChannel('auth')
    

    You can handle received messages with useEffect.