OAuth pop-up keamanan lintas domain React.js

12

Saya tertarik pada cara mengimplementasikan OAuth di Bereaksi menggunakan popup ( window.open).

Misalnya saya punya:

  1. mysite.com - ini adalah tempat saya membuka popup.
  2. passport.mysite.com/oauth/authorize - muncul.

Pertanyaan utamanya adalah bagaimana membuat koneksi antara window.open(popup) dan window.opener(seperti yang diketahui window.opener adalah null karena keamanan lintas-domain oleh karena itu kami tidak dapat menggunakannya lagi).

window.openerdihapus setiap kali Anda menavigasi ke host yang berbeda (untuk alasan keamanan), tidak ada jalan lain. Satu-satunya opsi harus melakukan pembayaran dalam bingkai jika memungkinkan. Dokumen teratas harus tetap di host yang sama.

Skema:

masukkan deskripsi gambar di sini

Solusi yang memungkinkan:

  1. Periksa jendela yang terbuka menggunakan setIntervaldijelaskan di sini .
  2. Menggunakan cross-storage (tidak layak dilakukan).

Jadi apa pendekatan terbaik yang direkomendasikan pada tahun 2019?

Wrapper untuk Bereaksi - https://github.com/Ramshackle-Jamathon/react-oauth-popup

Arthur
sumber
2
Pada 2019, dukungan Penyimpanan lokal jauh lebih baik. Saya akan pergi dengan pendekatan localStorage (dijelaskan dalam stackoverflow.com/questions/18625733/… ) karena sepertinya tidak banyak solusi. Jendela induk tidak perlu secara berkala memeriksa status jendela anak. setIntervaldapat digunakan sebagai fallback untuk penyimpanan lokal
Khanh TO
@ KhanhTO, ya, saya sepenuhnya setuju dengan Anda tentang localStorage, tetapi hanya bekerja untuk domain yang sama sehingga tidak berfungsi dalam kondisi saya
Arthur
2
Setelah Anda selesai dengan OAuth, jendela anak diarahkan kembali ke domain Anda, Anda berada di domain yang sama sekarang dengan induknya
Khanh TO
@ KhanhTO, hm, ini ide bagus! Aku seharusnya tahu ..
Arthur
1
Akan lebih baik lagi jika browser mengembalikan window.openersetelah mengarahkan kembali ke domain kami, tetapi ini tidak terjadi
Khanh TO

Jawaban:

6

Disarankan oleh Khanh TO . Munculan OAuth dengan localStorage. Berdasarkan reaksi-oauth-popup .

Skema:

masukkan deskripsi gambar di sini

Kode:

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
sumber
3

Saya pernah mengalami masalah pada aliran masuk oauth saya dengan bug window.open/window.opener di ms-edge

Alur saya sebelum masalah ini adalah

  • Pada tombol login klik buka munculan
  • Setelah berhasil masuk, aplikasi luar diarahkan ke halaman domain saya
  • Kemudian saya memanggil fungsi dari jendela induk dari dalam jendela sembulan (window.opener.fn) dengan data dari respons oauth dan jendela induk kemudian tutup jendela sembulan anak

Alur saya setelah masalah ini adalah

  • Pada tombol login klik buka munculan
  • Buat setinterval jika (window.opener tidak terdefinisi)
  • Setelah berhasil masuk, aplikasi luar diarahkan ke halaman domain saya
  • Periksa apakah window.opener tersedia kemudian lakukan # 3 dari aliran di atas dan hapusInterval
  • Jika window.opener tidak tersedia maka karena saya ada di halaman domain saya, saya mencoba untuk mengatur penyimpanan lokal dan mencoba membaca penyimpanan lokal dari dalam fungsi setInterval di jendela induk kemudian menghapus penyimpanan lokal dan setInterval dan melanjutkan.
  • (untuk kompatibilitas mundur) Jika penyimpanan lokal juga tidak tersedia maka setel cookie sisi klien dengan data dengan waktu kedaluwarsa singkat (5-10 detik) dan cobalah membaca cookie (document.cookie) di dalam fungsi setInterval di jendela induk dan memproses.
Shah92
sumber