Jak wywołać window.postMessage, kiedy są różne: źródło i odbiorca?

0

Nad tym problemem przesiedziałem kilka ładnych godzin dzisiaj. A mianowicie, w komponencie autentykacji użytkownika przez zewnętrzny serwis OAuth moja aplikacja Angular robi uwierzytelnienie użytkownika przez Web API endpoint:

private externalProviderWindow: Window; //definicja okna
(...)
var url = 'http://localhost:50962/' + "api/token/external-login/" + providerName; //url Web API
var params = "toolbar=yes,scrollbars=yes,resizable=yes,width=" + w + ", height=" + h; //parametry okna
this.externalProviderWindow = window.open(url, "ExternalProvider", params, false); //otwarcie okna z url
this.externalProviderWindow.addEventListener("message", this.handleMessage.bind(this), false); //dodanie event listenera

W powyższym kodzie komponent Angulara otwiera nowe okno z adresem prowadzącym do Web API, gdzie ono robi autentykację przez facebooka. Jeśli wynik jest pozytywny, to Web API zwraca odpowiedź JSON. Jednak żeby addEventListener został odpalony, web API zwraca JSON do widoku HTML w oknie externalProviderWindow, następnie jest wysyłana wiadomość do tego okna.

Wynik z kontrolera API przekazany do otwartego okna externalProviderWindow (nic szczególnego, tutaj działa wszystko dobrze):

TokenResponseViewModel response = GenerateResponse(user);

                return View(response);

A poniżej widok HTML który odbiera wynik, parsuje z message i wysyła do otwartego okna:

@model TokenResponseViewModel
<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>External Login</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
</head>
<body>
    <script>
        var accessToken = "@Model.Token";
        var message = {};
        if (accessToken) {
            message.status = true;
            message.token = '@Model.Token';
            message.email = '@Model.Email';
            message.refreshToken = '@Model.RefreshToken';
            message.user = '@Model.User';
        } else {
            message.status = false;
        }
        console.log('posting message');
        this.window.postMessage(JSON.stringify(message), "http://localhost:4200");
        console.log('posted message');
    </script>
</body>
</html>

I tu pojawia się problem, ponieważ, Web API jest pod domeną http://localhost:50962/ a ClientApp http://localhost:4200/. Więc okno otwarte przez klienta 4200 ładuje adres dla 50962. Po wysłaniu wiadomości this.window.postMessage(JSON.stringify(message), "http://localhost:4200"); otrzymuję w konsoli błąd:

Nie udało się wykonać „postMessage” dla „DOMWindow”: dostarczony cel („http://localhost:4200”) jest różny od domeny okna odbiorcy („http://localhost:50962”).

Do tej pory próbowałem już tego podejścia w różnych kombinacjach.

Próbowałem też odpalić Web API i SPA na jednym porcie, ale wtedy żądania do Web API end pointów nie były ogarniane przez ruter Angulara i wyrzucało mi błąd w konsoli, że routing nie istnieje. Ostatecznie zostałem przy osobnych domenach (portach).

1

Jako że port zmienia origin, to rozpiszę trochę inaczej przykład

Masz aplikacje A.com (Angular)

Naciskasz Zaloguj przez Fb i otwiera Ci się okno na stronie B.com (Web API które tak naprawdę zwraca widok) które uwierzytelnia się w FB

i chcesz przekazać te dane do strony załadowanej na A.com, dobrze rozumiem?

0

Ostatecznie obszedłem problem w ten sposób, że z widoku API return View(response); przekazanego do externalProviderWindow (np Facebook, Google itp) wysyłam tokeny do nowego komponentu Angulara w URL, tak jak w tym temacie:

<body>
    <script>
        window.location.replace('http://localhost:4200/close/@Html.Raw(Model.Email)/@Html.Raw(Model.User)/@Html.Raw(Model.Token)/@Html.Raw(Model.RefreshToken)');
    </script>
</body>

Tam są one już wykorzystane do zalogowania użytkownika:

public ngOnInit(): void {
    this.tokenResponse = this.getTokenResponse();
    if (this.tokenResponse) {
      this.auth.setAuth(this.tokenResponse);
    }

    parent.window.close();
  }

  @HostListener('window:unload', ['$event'])
  public unloadHandler() {
    parent.window.close();
  }

  private getTokenResponse(): LoginResponse {
    const email = this.route.snapshot.paramMap.get('email');
    const user = this.route.snapshot.paramMap.get('user');
    const token = this.route.snapshot.paramMap.get('token');
    const refresh = this.route.snapshot.paramMap.get('refresh');

    var model = <LoginResponse>{};
    model.email = email;
    model.displayName = user;
    model.user = user;
    model.token = token;
    model.refreshToken = refresh;

    return model;
  }

Też główne okno (SPA), opener, co jakiś czas sprawdza czy okno zewnętrznego dostawy jest wciąż otwarte. Jeśli się okaże że z jakiegoś powodu nie jest otwarte (zamknięte przypadkiem lub przez komponent po zalogowaniu), to odpala metodę handleCloseExternalProvider():

 this.closePopUpWindow();
    this.externalProviderWindow = window.open(url, "ExternalProvider", params, false);
    let checkIntervalId = setInterval(() => {
      if (this.externalProviderWindow.closed) {
        clearInterval(checkIntervalId);
        this.handleCloseExternalProvider();
      }
      else {
        //still open, do nothing
      }
    }, 1000);

1 użytkowników online, w tym zalogowanych: 0, gości: 1