2016-01-14 9 views
7

Ich folge diesem Artikel auf Social Logins mit AngularJS und ASP.Net WebAPI (das ist ziemlich gut):AngularJS und ASP.Net WebAPI Social Login auf einem mobilen Browser

ASP.NET Web API 2 external logins with Facebook and Google in AngularJS app

Ziemlich viel, die Der Code funktioniert einwandfrei, wenn Sie den sozialen Login über einen Desktop-Browser (z. B. Chrome, FF, IE, Edge) ausführen. Das soziale Login öffnet sich in einem neuen Fenster (nicht Tab) und Sie können entweder Ihr Google - oder Facebook - Konto verwenden und sobald Sie über eines von ihnen eingeloggt sind, werden Sie auf die Callback - Seite (authComplete.html) weitergeleitet Callback-Seite hat eine JS-Datei definiert (authComplete.js), die das Fenster schließen und einen Befehl im übergeordneten Fenster ausführen würde.

die AngularJS Steuerung, die die externe Login-URL aufruft und öffnet sich ein Popup-Fenster (nicht Reiter) auf Desktop-Browsern:

loginController.js

'use strict'; 
app.controller('loginController', ['$scope', '$location', 'authService', 'ngAuthSettings', function ($scope, $location, authService, ngAuthSettings) { 

    $scope.loginData = { 
     userName: "", 
     password: "", 
     useRefreshTokens: false 
    }; 

    $scope.message = ""; 

    $scope.login = function() { 

     authService.login($scope.loginData).then(function (response) { 

      $location.path('/orders'); 

     }, 
     function (err) { 
      $scope.message = err.error_description; 
     }); 
    }; 

    $scope.authExternalProvider = function (provider) { 

     var redirectUri = location.protocol + '//' + location.host + '/authcomplete.html'; 

     var externalProviderUrl = ngAuthSettings.apiServiceBaseUri + "api/Account/ExternalLogin?provider=" + provider 
                    + "&response_type=token&client_id=" + ngAuthSettings.clientId 
                    + "&redirect_uri=" + redirectUri; 
     window.$windowScope = $scope; 

     var oauthWindow = window.open(externalProviderUrl, "Authenticate Account", "location=0,status=0,width=600,height=750"); 
    }; 

    $scope.authCompletedCB = function (fragment) { 

     $scope.$apply(function() { 

      if (fragment.haslocalaccount == 'False') { 

       authService.logOut(); 

       authService.externalAuthData = { 
        provider: fragment.provider, 
        userName: fragment.external_user_name, 
        externalAccessToken: fragment.external_access_token 
       }; 

       $location.path('/associate'); 

      } 
      else { 
       //Obtain access token and redirect to orders 
       var externalData = { provider: fragment.provider, externalAccessToken: fragment.external_access_token }; 
       authService.obtainAccessToken(externalData).then(function (response) { 

        $location.path('/orders'); 

       }, 
      function (err) { 
       $scope.message = err.error_description; 
      }); 
      } 

     }); 
    } 
}]); 

authComplete.html

<!DOCTYPE html> 
<html xmlns="http://www.w3.org/1999/xhtml"> 
<head> 
    <title></title> 

</head> 
<body> 
    <script src="scripts/authComplete.js"></script> 
</body> 
</html> 

authComplete.js

window.common = (function() { 
    var common = {}; 

    common.getFragment = function getFragment() { 
     if (window.location.hash.indexOf("#") === 0) { 
      return parseQueryString(window.location.hash.substr(1)); 
     } else { 
      return {}; 
     } 
    }; 

    function parseQueryString(queryString) { 
     var data = {}, 
      pairs, pair, separatorIndex, escapedKey, escapedValue, key, value; 

     if (queryString === null) { 
      return data; 
     } 

     pairs = queryString.split("&"); 

     for (var i = 0; i < pairs.length; i++) { 
      pair = pairs[i]; 
      separatorIndex = pair.indexOf("="); 

      if (separatorIndex === -1) { 
       escapedKey = pair; 
       escapedValue = null; 
      } else { 
       escapedKey = pair.substr(0, separatorIndex); 
       escapedValue = pair.substr(separatorIndex + 1); 
      } 

      key = decodeURIComponent(escapedKey); 
      value = decodeURIComponent(escapedValue); 

      data[key] = value; 
     } 

     return data; 
    } 

    return common; 
})(); 

var fragment = common.getFragment(); 
window.location.hash = fragment.state || ''; 
window.opener.$windowScope.authCompletedCB(fragment); 
window.close(); 

Das Problem, das ich habe, ist, dass, wenn ich die Anwendung auf einem mobilen Gerät (Safari, Chrome for Mobile) laufen, die soziale Login-Fenster in einem neuen Tab öffnet und die JS-Funktion, die passieren sollte zu Zurück das Fragment zum Hauptanwendungsfenster wird nicht ausgeführt und die neue Registerkarte wird nicht geschlossen.

Sie eigentlich dieses Verhalten sowohl auf einem Desktop und mobilen Browser durch die Anwendung ausprobieren können:

http://ngauthenticationapi.azurewebsites.net/

Was ich in diesem Zusammenhang bisher versucht haben, ist in der Login-Controller, ich die Funktion so modifiziert, dass die externe uRL Login im selben Fenster öffnet sich:

$scope.authExternalProvider = function (provider) { 
     var redirectUri = location.protocol + '//' + location.host + '/authcomplete.html'; 
     var externalProviderUrl = ngAuthSettings.apiServiceBaseUri + "api/Account/ExternalLogin?provider=" + provider 
                                   + "&response_type=token&client_id=" + ngAuthSettings.clientId 
                                   + "&redirect_uri=" + redirectUri; 
     window.location = externalProviderUrl; 
}; 

und modifiziert, um die authComplete.js common.getFragment Funktion zur Anmeldeseite zurückzukehren, durch das Zugriffstoken durch die soziale bereitgestellt Anfügen melden Sie sich als Query-String:

common.getFragment = function getFragment() { 
     if (window.location.hash.indexOf("#") === 0) { 
       var hash = window.location.hash.substr(1); 
       var redirectUrl = location.protocol + '//' + location.host + '/#/login?ext=' + hash; 
       window.location = redirectUrl; 
     } else { 
       return {}; 
     } 
}; 

Und im Login-Controller, habe ich eine Funktion, um die Abfragezeichenfolgeflag zu analysieren und versuchen, die $ scope.authCompletedCB (Fragment) Funktion wie zu nennen:

var vm = this; 
var fragment = null; 

vm.testFn = function (fragment) { 
     $scope.$apply(function() { 

       if (fragment.haslocalaccount == 'False') { 

         authenticationService.logOut(); 

         authenticationService.externalAuthData = { 
           provider: fragment.provider, 
           userName: fragment.external_user_name, 
           externalAccessToken: fragment.external_access_token 
         }; 

         $location.path('/associate'); 

       } 
       else { 
         //Obtain access token and redirect to orders 
         var externalData = { provider: fragment.provider, externalAccessToken: fragment.external_access_token }; 
         authenticationService.obtainAccessToken(externalData).then(function (response) { 

           $location.path('/home'); 

         }, 
       function (err) { 
         $scope.message = err.error_description; 
       }); 
       } 

     }); 
} 

init(); 

function parseQueryString(queryString) { 
     var data = {}, 
       pairs, pair, separatorIndex, escapedKey, escapedValue, key, value; 

     if (queryString === null) { 
       return data; 
     } 

     pairs = queryString.split("&"); 

     for (var i = 0; i < pairs.length; i++) { 
       pair = pairs[i]; 
       separatorIndex = pair.indexOf("="); 

       if (separatorIndex === -1) { 
         escapedKey = pair; 
         escapedValue = null; 
       } else { 
         escapedKey = pair.substr(0, separatorIndex); 
         escapedValue = pair.substr(separatorIndex + 1); 
       } 

       key = decodeURIComponent(escapedKey); 
       value = decodeURIComponent(escapedValue); 

       data[key] = value; 
     } 

     return data; 
} 

function init() { 
     var idx = window.location.hash.indexOf("ext="); 

     if (window.location.hash.indexOf("#") === 0) { 
       fragment = parseQueryString(window.location.hash.substr(idx)); 
       vm.testFn(fragment); 
     } 
} 

Aber offensichtlich dies gibt mir einen Fehler Winkel Zusammenhang (die ich keine Ahnung im Moment haben):

https://docs.angularjs.org/error/$rootScope/inprog?p0=$digest

So ziemlich viel es eine Sackgasse ist für mich in diesem Stadium.

Alle Ideen oder Eingaben würden sehr geschätzt werden.

Gracias!

Update: Ich habe es geschafft, den Angular-Fehler über das rootscope zu lösen, aber leider löst das Auflösen das Hauptproblem nicht. Wenn ich versucht habe, das soziale Login auf derselben Browser-Registerkarte zu öffnen, auf der sich meine Anwendung befindet, kann Google sich anmelden und zur Anwendung zurückkehren und die erforderlichen Token übergeben. Anders ist es bei Facebook, wo in der Entwickler-Tool-Konsole eine Warnung erscheint, die Facebook davon abzuhalten scheint, die Login-Seite anzuzeigen.

Ziemlich viel, die ursprüngliche Methode, mit der ein neues Fenster (oder Registerkarte) geöffnet wird, ist der Weg nach vorne, aber das gleiche für den mobilen Browser zu beheben scheint schwieriger zu werden.

Antwort

4

Auf dem Desktop, wenn das Auth-Fenster erscheint (nicht Tab) hat es die opener -Eigenschaft auf das Fenster, das dieses Pop-up-Fenster geöffnet, auf Mobile, wie Sie sagten, es ist kein Popup-Fenster, sondern eine neue Registerkarte . wenn ein neuer Tab im Browser geöffnet wird, ist die opener Eigenschaft null so tatsächlich eine Ausnahme hier haben:

window.opener.$windowScope.authCompletedCB

, weil Sie nicht die $windowScope Eigenschaft des Nullwert beziehen (window.opener), so dass jeder Codezeile nach dieser wird nicht ausgeführt - deshalb ist das Fenster auf Mobile nicht geschlossen.

Eine Lösung

In Ihrer authComplete.js Datei, anstatt zu versuchen, window.opener.$windowScope.authCompletedCB und übergeben das Fragment des Benutzers zu nennen, das Fragment in den localstorage oder in einem Cookie speichern (nach dem ganzen Seite an authComplete.html ist in der gleichen Herkunft wie Ihre Anwendung) mit JSON.stringify() und schließen Sie einfach das Fenster mit window.close().

Im loginController.js, machen eine $interval für so etwas wie 100 ms für einen Wert in der localstorage oder in einem Cookie zu überprüfen (vergessen Sie nicht, um das Intervall zu löschen, wenn das $scope$destroy ist), wenn afragment existieren können Sie seine analysieren Wert mit JSON.parse aus dem Speicher, entfernen Sie es aus dem Speicher und rufen Sie $scope.authCompletedCB mit dem geparsten Wert.

UPDATE - Added Codebeispiele

authComplete.js

... 
var fragment = common.getFragment(); 
// window.location.hash = fragment.state || ''; 
// window.opener.$windowScope.authCompletedCB(fragment); 
localStorage.setItem("auth_fragment", JSON.stringify(fragment)) 
window.close(); 

loginController.js

app.controller('loginController', ['$scope', '$interval', '$location', 'authService', 'ngAuthSettings', 
function ($scope, $interval, $location, authService, ngAuthSettings) { 

    ... 

    // check for fragment every 100ms 
    var _interval = $interval(_checkForFragment, 100); 

    function _checkForFragment() { 
     var fragment = localStorage.getItem("auth_fragment"); 
     if(fragment && (fragment = JSON.parse(fragment))) { 

      // clear the fragment from the storage 
      localStorage.removeItem("auth_fragment"); 

      // continue as usual 
      $scope.authCompletedCB(fragment); 

      // stop looking for fragmet 
      _clearInterval(); 
     } 
    } 

    function _clearInterval() { 
     $interval.cancel(_interval); 
    } 

    $scope.$on("$destroy", function() { 
     // clear the interval when $scope is destroyed 
     _clearInterval(); 
    }); 

}]); 
+0

Ich würde einige Zeit dauern, diese später am Tag zu testen, . – Batuta

+0

Sie müssen nicht, die 'localStorage' ist Teil der HTML5-API im Browser, eigentlich ist es auf dem' Fenster', Sie können es von überall mit 'window.localStorage' zugreifen – udidu

+0

Habe es gerade ausprobiert und ich bekomme einen rootscope Fehler Fehler: $ rootScope: inprog Aktion läuft bereits – Batuta

Verwandte Themen