Bat*_*uta 7 javascript asp.net asp.net-web-api angularjs
我正在使用AngularJS和ASP.Net WebAPI关注社交登录这篇文章(非常好):
在AngularJS应用程序中使用Facebook和Google进行ASP.NET Web API 2外部登录
当您通过桌面浏览器(即Chrome,FF,IE,Edge)运行社交登录时,代码工作正常.社交登录在新窗口(不是标签)中打开,您可以使用您的Google或Facebook帐户,一旦您通过其中任何一个登录,您将被重定向到回调页面(authComplete.html),并且回调页面定义了一个JS文件(authComplete.js),该文件将关闭窗口并在父窗口上执行命令.
angularJS控制器调用外部登录URL并在桌面浏览器上打开一个弹出窗口(不是tab):
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;
});
}
});
}
}]);
Run Code Online (Sandbox Code Playgroud)
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>
Run Code Online (Sandbox Code Playgroud)
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();
Run Code Online (Sandbox Code Playgroud)
我遇到的问题是,当我在移动设备(Safari,Chrome for Mobile)上运行应用程序时,社交登录窗口将在新选项卡和JS功能中打开,该功能旨在将片段传回主应用程序窗口不执行nad新选项卡不关闭.
您实际上可以通过应用程序在桌面和移动浏览器上尝试此行为:
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;
};
Run Code Online (Sandbox Code Playgroud)
并修改了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 {};
}
};
Run Code Online (Sandbox Code Playgroud)
在登录控制器中,我添加了一个函数来解析查询字符串并尝试调用$ 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);
}
}
Run Code Online (Sandbox Code Playgroud)
但显然这给了我一个与角度有关的错误(我现在还没有线索):
https://docs.angularjs.org/error/$rootScope/inprog?p0=$digest
所以,在这个阶段,对我来说几乎是死路一条.
任何想法或意见都将受到高度赞赏.
格拉西亚斯!
更新:我设法解决了关于rootcope被抛出的Angular错误,但遗憾的是,解决这个问题并不能解决主要问题.如果我尝试在我的应用程序所在的同一浏览器选项卡上打开社交登录,Google可以登录并返回应用程序并传递所需的令牌.对于Facebook来说,这是一个不同的故事,在开发人员的工具控制台中,有一个警告似乎阻止Facebook显示登录页面.
实际上,打开新窗口(或标签)的原始方法是前进的方法,但为移动浏览器修复相同的方法似乎变得更具挑战性.
在桌面上,当弹出身份验证窗口(不是选项卡)时,它的属性opener设置为打开此弹出窗口的窗口,在移动设备上,正如您所说,它不是弹出窗口,而是新选项卡。当在浏览器中打开新选项卡时,该opener属性null实际上是这样的,这里有一个例外:
window.opener.$windowScope.authCompletedCB
因为您无法引用$windowScopenull 值 ( window.opener) 的属性,所以此行之后的每一行代码都不会被执行 - 这就是窗口在移动设备上未关闭的原因。
一个办法
在您的authComplete.js文件中,不要尝试调用
window.opener.$windowScope.authCompletedCB并传递用户的片段,而是使用 将片段保存在 localStorage 或 cookie 中(毕竟页面与authComplete.html您的应用程序同源)JSON.stringify(),然后使用 关闭窗口window.close()。
在 中loginController.js,创建$interval一个类似 100ms 的内容来检查 localStorage 或 cookie 中的值(不要忘记清除 is 时的间隔$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();
Run Code Online (Sandbox Code Playgroud)
登录控制器.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();
});
}]);
Run Code Online (Sandbox Code Playgroud)