AngularJS和ASP.Net WebAPI在移动浏览器上进行社交登录

我正在关注AngularJS和ASP.Net WebAPI的Social Logins这篇文章(这很好):

ASP.NET Web API 2外部登录Facebook和谷歌在AngularJS应用程序

当你通过桌面浏览器(例如Chrome,FF,IE,Edge)运行社交登录时,代码很好。 社交登录会在新窗口中打开(不是选项卡),您可以使用Google或Facebook帐户,并且一旦通过其中的任何一个登录,就会重定向到回调页面(authComplete.html),而回调页面定义了一个JS文件(authComplete.js),可以关闭窗口并在父窗口上执行命令。

调用外部登录URL并在桌面浏览器上打开一个弹出窗口(不是选项卡)的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 for Mobile)上运行应用程序时,社交登录窗口将在新选项卡中打开,并且JS函数旨在将片段传递回主应用程序窗口不执行新的选项卡不关闭。

您可以通过应用程序在台式机和移动浏览器上实际尝试这种行为:

http://ngauthenticationapi.azurewebsites.net/

我在此上下文中尝试的是在登录控制器中,我修改了该函数,以便外部登录URL在同一个窗口中打开:

$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(fragment)函数,如:

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

所以,在这个阶段,对我来说,这几乎是一个死胡同。

任何想法或意见将不胜感激。

格拉西亚斯!

更新:我设法解决了有关被引发的Rootcope的Angular错误,但可悲的是,解决这个问题并没有解决主要问题。 如果我试图在我的应用程序的相同浏览器选项卡上打开社交登录,Google可以登录并返回到应用程序并传递所需的令牌。 对于Facebook来说,这是一个不同的故事,在开发者工具控制台中,有一个警告似乎阻止Facebook显示登录页面。

很多时候,打开一个新窗口(或标签)的原始方法是前进的方向,但修复移动浏览器的方法似乎变得更具挑战性。


在桌面上,当auth窗口弹出(不是选项卡)时,它将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的$interval来检查localStorage或cookie中的值(不要忘记在$scope$destroy时清除间隔),如果存在可以解析的碎片,则可以解析它的值使用存储中的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();
    });

}]);
链接地址: http://www.djcxy.com/p/89693.html

上一篇: AngularJS and ASP.Net WebAPI Social Login on a Mobile Browser

下一篇: More than 1 different Urls and only One listview