2016-01-14 36 views
7

我這篇文章AngularJS和ASP.Net的WebAPI(這是相當不錯的)社會登錄以下:AngularJS和ASP.Net的WebAPI社會登錄移動瀏覽器上

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

漂亮的多,在通過桌面瀏覽器(例如Chrome,FF,IE,Edge)運行社交登錄時,代碼可以正常工作。社交登錄會在新窗口中打開(不是選項卡),您可以使用Google或Facebook帳戶,並且一旦通過其中的任何一個登錄,就會重定向到回調頁面(authComplete.html),而回調頁面定義了一個JS文件(authComplete.js),可以關閉窗口並在父窗口上執行命令。

的angularJS控制器,它調用外部登錄網址和桌面瀏覽器打開一個彈出窗口(不是標籤):

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

我遇到的問題是,當我運行在移動設備(Safari瀏覽器,Chrome瀏覽器手機)上的應用,社交登錄窗口中的一個新的標籤和其目的是通過JS函數打開返回片段到主應用程序窗口不執行並且新選項卡不關閉。

實際上,你可以通過應用程序嘗試了桌面和移動瀏覽器的這種行爲:

http://ngauthenticationapi.azurewebsites.net/

我已經在這方面到目前爲止已經試過在登錄控制器,我修改了功能,所以該外部登錄網址在同一窗口內打開:

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

並修改了authComplete.js common.getFragment函數返回到登錄頁面,通過附加由社會提供的訪問令牌登錄,查詢字符串:

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

而在登錄控制器,我添加了一個函數來解析查詢字符串,並嘗試調用像$ scope.authCompletedCB(片段)功能:

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

但很明顯這是給我相關的角度(這是我目前所面對的任何線索)的錯誤:

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

所以,幾乎是它是我的一個死衚衕在這個階段。

任何想法或意見將不勝感激。

Gracias!

更新:我設法解決有關被引發的rootcope的角度錯誤,但可悲的是,解決這並不能解決主要問題。如果我試圖在我的應用程序的相同瀏覽器選項卡上打開社交登錄,Google可以登錄並返回到應用程序並傳遞所需的令牌。對於Facebook來說,這是一個不同的故事,在開發者工具控制檯中,有一個警告似乎阻止Facebook顯示登錄頁面。

非常多,打開一個新窗口(或標籤)的原始方法是前進的方向,但修復移動瀏覽器的方法似乎越來越具有挑戰性。

回答

4

在桌面上,當彈出窗口(不是選項卡)時,它將opener屬性設置爲打開此彈出窗口的窗口,如您所說,它不是彈出窗口,而是新選項卡。當一個新的選項卡在瀏覽器中打開時,opener屬性是null所以實際上你這裏有一個例外:

window.opener.$windowScope.authCompletedCB

,因爲你不能引用空值(window.opener)的$windowScope屬性,以便每這一行之後的代碼行將不會被執行 - 這就是爲什麼窗口沒有在手機上關閉。

一個解決方案

在你authComplete.js文件,而不是試圖調用 window.opener.$windowScope.authCompletedCB並通過用戶的片段,保存片段中的localStorage或者cookie(所有頁面後,在authComplete.html是與您的應用程序具有相同的來源),使用JSON.stringify()並使用window.close()關閉窗口。

loginController.js,使這樣的事情100ms的檢查在localStorage的或在cookie中值的$interval(不要忘了清除間隔時$scope$destroy),如果afragment存在,您可以解析其值使用存儲中的JSON.parse,將其從存儲中移除並使用解析的值調用$scope.authCompletedCB

更新 - 增加了代碼樣本

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

我會花時間在當天晚些時候測試了這一點。 – Batuta

+0

你不需要,'localStorage'是瀏覽器中的HTML5 API的一部分,實際上它在'window'上,你可以在任何地方使用'window.localStorage'來訪問它。 – udidu

+0

剛剛試過這個,現在我我得到了一些rootcope錯誤錯誤:$ rootScope:inprog 操作已在進行中 – Batuta