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