Segurança entre domínios pop-up do OAuth React.js

12

Estou interessado em como implementar o OAuth no React usando popup ( window.open).

Por exemplo, eu tenho:

  1. mysite.com - é aqui que eu abro o pop-up.
  2. passport.mysite.com/oauth/authorize - Aparecer.

A principal questão é como criar conexão entre window.open(pop-up) e window.opener(como é conhecido o window.opener é nulo devido à segurança entre domínios, portanto, não podemos mais usá-lo).

window.openeré removido sempre que você navega para um host diferente (por motivos de segurança), não há como contorná-lo. A única opção deve ser o pagamento em um quadro, se possível. O documento principal precisa permanecer no mesmo host.

Esquema:

insira a descrição da imagem aqui

Soluções possíveis:

  1. Verifique uma janela aberta usando o setIntervaldescrito aqui .
  2. Usando armazenamento cruzado (não vale a pena imho).

Então, qual é a melhor abordagem recomendada em 2019?

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

Arthur
fonte
2
Em 2019, o suporte ao localStorage é muito melhor. Eu usaria a abordagem localStorage (descrita em stackoverflow.com/questions/18625733/… ), pois não parece muito uma solução alternativa. A janela pai não precisa verificar periodicamente o status da janela filho. setIntervalpoderia ser usado como alternativa para localStorage
Khanh A
@KhanhTO, sim, eu concordo completamente com você sobre localStorage, mas ele só funciona para o mesmo domínio para que ele não funciona na minha condição
Arthur
2
Depois de terminar com OAuth, a janela filho é redirecionado de volta para seu domínio, você está no mesmo domínio agora com o pai
Khanh A
@ KhanhTO, hm, é uma ótima idéia! Eu deveria ter conhecido ..
Arthur
11
Seria ainda melhor se as restaurações do navegador window.openerapós redirecionando de volta para o nosso domínio, mas este não é o caso
Khanh A

Respostas:

6

Sugerido por Khanh TO . Pop-up OAuth com localStorage. Baseado em react-oauth-popup .

Esquema:

insira a descrição da imagem aqui

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;
Arthur
fonte
3

Uma vez, encontrei um problema no meu fluxo de login oauth com o bug window.open/window.opener no ms-edge

Meu fluxo antes deste problema foi

  • No botão de login, clique em abrir um pop-up
  • Após o login bem-sucedido, o aplicativo oauth é redirecionado para a página do meu domínio
  • Então eu chamo uma função da janela pai de com no pop-up (window.opener.fn) com dados da resposta oauth e da janela pai e fecho a janela pop-up filho

Meu fluxo após esse problema foi

  • No botão de login, clique em abrir um pop-up
  • Crie um setinterval no caso (window.opener é indefinido)
  • Após o login bem-sucedido, o aplicativo oauth é redirecionado para a página do meu domínio
  • Verifique se window.opener está disponível, faça o # 3 do fluxo acima e clearInterval
  • Se window.opener não estiver disponível, como estou na página de meus domínios, tento definir o armazenamento local e tento ler o armazenamento local de dentro da função setInterval na janela pai, limpe o armazenamento local e o setInterval e prossiga.
  • (para compatibilidade com versões anteriores) Se o armazenamento local também não estiver disponível, defina um cookie do lado do cliente com os dados com um tempo de expiração curto (5 a 10 segundos) e tente ler o cookie (document.cookie) dentro da função setInterval na janela pai e Continuar.
Shah92
fonte