Seguridad emergente entre dominios OAuth React.js

12

Estoy interesado en cómo implementar OAuth en React usando popup ( window.open).

Por ejemplo tengo:

  1. mysite.com - Aquí es donde abro la ventana emergente.
  2. passport.mysite.com/oauth/authorize - surgir.

La pregunta principal es cómo crear una conexión entre window.open(ventana emergente) y window.opener(como se conoce, el window.opener es nulo debido a la seguridad entre dominios, por lo tanto, ya no podemos usarlo).

window.openerse elimina cada vez que navega a un host diferente (por razones de seguridad), no hay forma de evitarlo. La única opción debería ser hacer el pago en un marco si es posible. El documento superior debe permanecer en el mismo host.

Esquema:

ingrese la descripción de la imagen aquí

Soluciones posibles:

  1. Verifique una ventana abierta usando la que se setIntervaldescribe aquí .
  2. Uso de almacenamiento cruzado (no vale la pena en mi humilde opinión).

Entonces, ¿cuál es el mejor enfoque recomendado en 2019?

Contenedor para reaccionar: https://github.com/Ramshackle-Jamathon/react-oauth-popup

Arturo
fuente
2
En 2019, el soporte localStorage es mucho mejor. Optaría por el enfoque localStorage (descrito en stackoverflow.com/questions/18625733/… ) ya que no parece una solución alternativa. La ventana principal no necesita verificar periódicamente el estado de la ventana secundaria. setIntervalpodría utilizarse como reserva para localStorage
Khanh TO
@KhanhTO, sí, estoy completamente de acuerdo contigo localStorage, pero solo funciona para el mismo dominio, por lo que no funciona en mi condición
Arthur
2
Después de que termine con OAuth, la ventana secundaria se redirige a su dominio, ahora está en el mismo dominio con el padre
Khanh AL
@KhanhTO, hm, ¡esta es una gran idea! Debería haberlo sabido ..
Arthur
1
Sería aún mejor si el navegador se restaura window.openerdespués de redirigir de nuevo a nuestro dominio, pero este no es el caso
Khanh TO

Respuestas:

6

Sugerido por Khanh TO . OAuth emergente con localStorage. Basado en react-oauth-popup .

Esquema:

ingrese la descripción de la imagen aquí

Código:

oauth-popup.tsx:

import React, {PureComponent, ReactChild} from 'react'

type Props = {
  width: number,
  height: number,
  url: string,
  title: string,
  onClose: () => any,
  onCode: (params: any) => any,
  children?: ReactChild,
}

export default class OauthPopup extends PureComponent<Props> {

  static defaultProps = {
    onClose: () => {},
    width: 500,
    height: 500,
    url: "",
    title: ""
  };

  externalWindow: any;
  codeCheck: any;

  componentWillUnmount() {
    if (this.externalWindow) {
      this.externalWindow.close();
    }
  }

  createPopup = () => {
    const {url, title, width, height, onCode} = this.props;
    const left = window.screenX + (window.outerWidth - width) / 2;
    const top = window.screenY + (window.outerHeight - height) / 2.5;

    const windowFeatures = `toolbar=0,scrollbars=1,status=1,resizable=0,location=1,menuBar=0,width=${width},height=${height},top=${top},left=${left}`;

    this.externalWindow = window.open(
        url,
        title,
        windowFeatures
    );

    const storageListener = () => {
      try {
        if (localStorage.getItem('code')) {
          onCode(localStorage.getItem('code'));
          this.externalWindow.close();
          window.removeEventListener('storage', storageListener);
        }
      } catch (e) {
        window.removeEventListener('storage', storageListener);
      }
    }

    window.addEventListener('storage', storageListener);

    this.externalWindow.addEventListener('beforeunload', () => {
      this.props.onClose()
    }, false);
  };

  render() {
    return (
      <div onClick={this.createPopup)}>
        {this.props.children}
      </div>
    );
  }
}

app.tsx

import React, {FC} from 'react'

const onCode = async (): Promise<undefined> => {
  try {
    const res = await <your_fetch>
  } catch (e) {
    console.error(e);
  } finally {
    window.localStorage.removeItem('code'); //remove code from localStorage
  }
}

const App: FC = () => (
  <OAuthPopup
    url={<your_url>}
    onCode={onCode}
    onClose={() => console.log('closed')}
    title="<your_title>">
    <button type="button">Enter</button>
  </OAuthPopup>
);

export default App;
Arturo
fuente
3

Una vez me encuentro con un problema en mi flujo de inicio de sesión oauth con el error window.open/window.opener en ms-edge

Mi flujo antes de este problema fue

  • En el botón de inicio de sesión, haga clic en abrir una ventana emergente
  • Después de iniciar sesión correctamente, la aplicación oauth redirige a la página de mi dominio
  • Luego llamo a una función de la ventana principal desde con en la ventana emergente (window.opener.fn) con datos de la respuesta oauth y la ventana principal y luego cierro la ventana emergente secundaria

Mi flujo después de este problema fue

  • En el botón de inicio de sesión, haga clic en abrir una ventana emergente
  • Cree un setinterval en caso (window.opener no está definido)
  • Después de iniciar sesión correctamente, la aplicación oauth redirige a la página de mi dominio
  • Verifique si window.opener está disponible, luego haga el # 3 del flujo anterior y clearInterval
  • Si window.opener no está disponible, ya que estoy en mi página de dominios, trato de configurar localstorage e intento leer el localstorage desde dentro de la función setInterval en la ventana principal, luego borro localstorage y setInterval y continúo.
  • (para compatibilidad con versiones anteriores) Si el almacenamiento local tampoco está disponible, configure una cookie del lado del cliente con los datos con un breve tiempo de caducidad (5-10 segundos) e intente leer la cookie (document.cookie) dentro de la función setInterval en la ventana principal y Continuar.
Shah92
fuente