2016-01-14 11 views
7

Sto seguendo questo articolo su accessi sociali con AngularJS e ASP.Net WebAPI (che è abbastanza buona):AngularJS e ASP.Net WebAPI sociale di login su un mobile browser

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

Praticamente, il il codice funziona correttamente quando si esegue l'accesso social tramite un browser desktop (es. Chrome, FF, IE, Edge). L'accesso social si apre in una nuova finestra (non nella scheda) e puoi utilizzare il tuo account Google o Facebook e una volta che hai effettuato l'accesso tramite uno di essi, verrai reindirizzato alla pagina di richiamata (authComplete.html), e la pagina di callback ha un file JS definito (authComplete.js) che chiude la finestra ed esegue un comando sulla finestra padre.

il controllore angularJS che chiama l'url di accesso esterno e apre una finestra pop-up (non scheda) sul browser desktop:

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(); 

Il problema che sto avendo è che quando faccio funzionare l'applicazione su un dispositivo mobile (Safari, Chrome per dispositivi mobili), la finestra di login sociale, si apre in una nuova scheda e la funzione JS, che aveva lo scopo di passare indietro il frammento nella finestra principale dell'applicazione non viene eseguito e la nuova scheda non si chiude.

Si può effettivamente provare questo comportamento sia su un desktop e browser mobile attraverso l'applicazione:

http://ngauthenticationapi.azurewebsites.net/

Quello che ho provato finora in questo contesto è nel controller di accesso, ho modificato la funzione in modo che l'URL di accesso esterna si apre nella stessa finestra:

$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; 
}; 

E modificato la funzione authComplete.js common.getFragment per tornare alla pagina di login, aggiungendo il token di accesso fornito dal sociali login come stringa di query:

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 {}; 
     } 
}; 

E nel controller di accesso, ho aggiunto una funzione per analizzare il querystring e provare a chiamare la funzione $ scope.authCompletedCB (frammento) come:

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); 
     } 
} 

Ma ovviamente questo mi sta dando un errore relativo alle angolare (che non ho idea al momento):

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

Quindi, praticamente si tratta di un vicolo cieco per me in questa fase.

Qualsiasi idea o contributo sarebbe molto apprezzata.

Gracias!

Aggiornamento: sono riuscito a risolvere l'errore angolare relativo al rootbope lanciato, ma purtroppo la risoluzione non risolve il problema principale. Se provo ad aprire l'accesso social nella stessa scheda del browser in cui si trova la mia applicazione, Google può accedere e tornare all'applicazione e passare i token richiesti. È una storia diversa per Facebook, dove nella console degli strumenti dello sviluppatore c'è un avviso che sembra impedire a Facebook di visualizzare la pagina di accesso.

Praticamente, il metodo originale con cui una nuova finestra (o scheda) viene aperta è la via da seguire, ma il fissaggio degli stessi per il browser mobile sembra essere sempre più difficile.

risposta

4

Sul desktop, quando si apre la finestra di autenticazione (non scheda), la proprietà opener è impostata sulla finestra che ha aperto questa finestra, sul cellulare, come hai detto, non è una finestra popup ma una nuova scheda . quando una nuova scheda viene aperta nel browser, la proprietà è openernull quindi in realtà si ha un'eccezione qui:

window.opener.$windowScope.authCompletedCB

perché non si può fare riferimento alla proprietà $windowScope del valore nullo (window.opener) in modo che ogni linea di codice dopo questa non verrà eseguita - ecco perché la finestra non è chiusa sul cellulare.

Una soluzione

Nel file authComplete.js, invece di cercare di chiamare window.opener.$windowScope.authCompletedCB e passare il frammento degli utenti, salvare il frammento nel localStorage o in un cookie (dopo tutto la pagina a authComplete.html è nella stessa origine della tua applicazione) usando JSON.stringify() e chiudi semplicemente la finestra usando window.close().

Nel loginController.js, fare un $interval per qualcosa come 100 ms per verificare la presenza di un valore nel localStorage o in un cookie (non dimenticare di cancellare l'intervallo in cui il $scope è $destroy), se esiste afragment è possibile analizzare la sua valore utilizzando JSON.parse dalla memoria, rimuoverlo dalla memoria e chiamare $scope.authCompletedCB con il valore analizzato.

UPDATE - esempi di codice Aggiunto

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

vorrei prendere tempo per testare questo fuori più tardi il giorno . – Batuta

+0

Non è necessario, 'localStorage' fa parte dell'API HTML5 nel browser, in realtà è nella' finestra', è possibile accedervi da qualsiasi posizione usando 'window.localStorage' – udidu

+0

Ho appena provato questo e ora errore get rootcope Errore: $ rootScope: inprog Azione già in corso – Batuta

Problemi correlati