Sécurité inter-domaines popup OAuth React.js

12

Je suis intéressé par la façon d'implémenter OAuth dans React en utilisant popup ( window.open).

Par exemple, j'ai:

  1. mysite.com - c'est là que j'ouvre le popup.
  2. passport.mysite.com/oauth/authorize - apparaitre.

La question principale est de savoir comment créer une connexion entre window.open(popup) et window.opener(comme on le sait, window.opener est nul en raison de la sécurité entre domaines, nous ne pouvons donc plus l'utiliser).

window.openerest supprimé chaque fois que vous accédez à un hôte différent (pour des raisons de sécurité), il n'y a aucun moyen de le contourner. La seule option devrait être d'effectuer le paiement dans un cadre si cela est possible. Le document supérieur doit rester sur le même hôte.

Schème:

entrez la description de l'image ici

Solutions possibles:

  1. Vérifiez une fenêtre ouverte en utilisant setIntervaldécrit ici .
  2. Utiliser le stockage croisé (ça ne vaut pas la peine à mon humble avis).

Quelle est donc la meilleure approche recommandée en 2019?

Wrapper pour React - https://github.com/Ramshackle-Jamathon/react-oauth-popup

Arthur
la source
2
En 2019, le support localStorage est bien meilleur. J'irais avec l'approche localStorage (décrite dans stackoverflow.com/questions/18625733/… ) car cela ne semble pas être une solution de contournement. La fenêtre parent n'a pas besoin de vérifier périodiquement l'état de la fenêtre enfant. setIntervalpourrait être utilisé comme solution de rechange pour localStorage
Khanh TO
@KhanhTO, oui, je suis entièrement d'accord avec vous localStorage, mais cela ne fonctionne que pour le même domaine, donc cela ne fonctionne pas dans mon état
Arthur
2
Une fois que vous avez terminé avec OAuth, la fenêtre enfant est redirigée vers votre domaine, vous êtes maintenant dans le même domaine avec le parent
Khanh TO
@KhanhTO, hm, c'est une excellente idée! J'aurais dû savoir ..
Arthur
1
Ce serait encore mieux si le navigateur restaure window.openeraprès la redirection vers notre domaine, mais ce n'est pas le cas
Khanh TO

Réponses:

6

Proposé par Khanh TO . Popup OAuth avec localStorage. Basé sur react-oauth-popup .

Schème:

entrez la description de l'image ici

Code:

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;
Arthur
la source
3

J'ai rencontré une fois un problème sur mon flux de connexion oauth avec le bug window.open/window.opener sur ms-edge

Mon flux avant ce problème était

  • Cliquez sur le bouton de connexion pour ouvrir une fenêtre contextuelle
  • Après une connexion réussie, l'application oauth redirige vers la page de mon domaine
  • Ensuite, j'appelle une fonction de la fenêtre parent avec avec dans la fenêtre contextuelle (window.opener.fn) avec les données de la réponse oauth et la fenêtre parent puis ferme la fenêtre contextuelle enfant

Mon flux après ce problème était

  • Cliquez sur le bouton de connexion pour ouvrir une fenêtre contextuelle
  • Créez un setinterval au cas où (window.opener n'est pas défini)
  • Après une connexion réussie, l'application oauth redirige vers la page de mon domaine
  • Vérifiez si window.opener est disponible puis faites # 3 à partir du flux ci-dessus et effacezInterval
  • Si window.opener n'est pas disponible, puisque je suis sur ma page de domaines, j'essaie de définir localstorage et d'essayer de lire le localstorage depuis l'intérieur de la fonction setInterval dans la fenêtre parente, puis effacez localstorage et setInterval et continuez.
  • (pour des raisons de compatibilité descendante) Si le stockage local n'est pas non plus disponible, définissez un cookie côté client avec les données avec une courte durée d'expiration (5 à 10 secondes) et essayez de lire le cookie (document.cookie) à l'intérieur de la fonction setInterval dans la fenêtre parent et procéder.
Shah92
la source