Wyskakujące okienko bezpieczeństwa OAuth między domenami React.js

12

Interesuje mnie, jak zaimplementować OAuth w React za pomocą popup ( window.open).

Na przykład mam:

  1. mysite.com - tutaj otwieram wyskakujące okienko.
  2. passport.mysite.com/oauth/authorize - wyskakujące okienko.

Główne pytanie brzmi: jak utworzyć połączenie między window.open(wyskakującym oknem) a window.opener(jak wiadomo, window.opener ma wartość zerową ze względu na bezpieczeństwo w wielu domenach, dlatego nie możemy go więcej używać).

window.openerjest usuwany za każdym razem, gdy nawigujesz do innego hosta (ze względów bezpieczeństwa), nie można tego obejść. Jedyną opcją powinno być dokonywanie płatności w ramce, jeśli jest to możliwe. Najważniejszy dokument musi pozostać na tym samym hoście.

Schemat:

wprowadź opis zdjęcia tutaj

Możliwe rozwiązania:

  1. Sprawdź otwarte okno, używając setIntervalopisanego tutaj .
  2. Korzystanie z przechowywania krzyżowego (nie warto imho).

Jakie jest zatem najlepsze zalecane podejście w 2019 roku?

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

Artur
źródło
2
W 2019 r. Obsługa localStorage jest znacznie lepsza. Wybrałbym podejście localStorage (opisane w stackoverflow.com/questions/18625733/… ), ponieważ nie wydaje się to zbyt dużym obejściem. Okno nadrzędne nie musi okresowo sprawdzać statusu okna podrzędnego. setIntervalmoże być wykorzystany jako zapasowy dla localStorage
Khanh TO
@KhanhTO, tak, całkowicie się z tobą zgadzam localStorage, ale działa tylko w tej samej domenie, więc nie działa w moim stanie
Arthur
2
Po zakończeniu korzystania z protokołu OAuth okno potomne zostaje przekierowane z powrotem do Twojej domeny, jesteś teraz w tej samej domenie z rodzicem
Khanh TO
@ KhanhTO, hm, to świetny pomysł! Powinienem był wiedzieć ...
Arthur
1
Byłoby jeszcze lepiej, gdyby przeglądarka przywróciła window.openerpo przekierowaniu z powrotem do naszej domeny, ale tak nie jest
Khanh TO

Odpowiedzi:

6

Sugerowane przez Khanh TO . Wyskakujące okienko OAuth z localStorage. Na podstawie wyskakującego wyskakującego polecenia .

Schemat:

wprowadź opis zdjęcia tutaj

Kod:

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;
Artur
źródło
3

Raz napotkałem problem z moim oauth logowaniem z błędem window.open/window.opener na ms-edge

Mój przepływ przed tym problemem był

  • Po zalogowaniu kliknij otwórz wyskakujące okienko
  • Po udanym zalogowaniu aplikacja oauth przekierowuje na stronę mojej domeny
  • Następnie wywołuję funkcję okna nadrzędnego z poziomu w wyskakującym okienku (window.opener.fn) z danymi z odpowiedzi oauth i okna nadrzędnego, a następnie zamykam okno podrzędne

Mój przepływ po tym problemie był

  • Po zalogowaniu kliknij otwórz wyskakujące okienko
  • Utwórz setinterval w przypadku (window.opener jest niezdefiniowany)
  • Po udanym zalogowaniu aplikacja oauth przekierowuje na stronę mojej domeny
  • Sprawdź, czy window.opener jest dostępny, następnie wykonaj # 3 z powyższego przepływu i wyczyść Interwał
  • Jeśli plik window.opener nie jest dostępny, ponieważ jestem na stronie mojej domeny, próbuję ustawić magazyn lokalny i spróbować odczytać magazyn lokalny z wnętrza funkcji setInterval w oknie nadrzędnym, a następnie wyczyścić magazyn lokalny i setInterval i kontynuować.
  • (w celu zachowania zgodności z poprzednimi wersjami) Jeśli lokalne przechowywanie również nie jest dostępne, ustaw plik cookie po stronie klienta z danymi z krótkim czasem wygaśnięcia (5–10 sekund) i spróbuj odczytać plik cookie (document.cookie) wewnątrz funkcji setInterval w oknie nadrzędnym i kontynuować.
Shah92
źródło