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


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;

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ć.
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.