I follow this article on social inputs with AngularJS and ASP.Net WebAPI (which is pretty good):
ASP.NET Web API 2 external logins with Facebook and Google in AngularJS application
To a large extent, the code works great when you launch social login through a desktop browser (e.g. Chrome, FF, IE, Edge). The social login opens in a new window (not in the form of tabs), and you can use your Google or Facebook account, and as soon as you log in through any of them, you are redirected to the callback page (authComplete.html), and to The callback page has a JS file defined (authComplete.js) that will close the window and execute the command in the parent window.
angularJS controller, which calls an external login URL and opens a popup window (not a tab) on desktop browsers:
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 {
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();
The problem is that when the application is launched on the mobile device (Safari, Chrome for Mobile), the social window for entering the new tab opens and the JS function that was supposed to transfer the fragment to the main application window is not executed, and the new tab does not close.
In fact, you can try this behavior both on the desktop and on the mobile browser through the application:
http://ngauthenticationapi.azurewebsites.net/
What I tried so far in this context is in the login controller, I modified the function so that the external login URL opens in the same window:
$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; };
And changed the authComplete.js common.getFragment function to return to the login page, adding the access token provided by the social login as the query string:
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 {}; } };
And in the input controller, I added a function to parse the request and try to call the $ scope.authCompletedCB function (fragment), for example:
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); } }
But obviously this gives me an error related to angular (which I don't have about at the moment):
https://docs.angularjs.org/error/$rootScope/inprog?p0=$digest
So, pretty much this is a dead end for me at this point.
Any ideas or materials would be highly appreciated.
Gracias!
Update: I managed to resolve the angular error about root crop failure, but, unfortunately, solving the problem does not fix the main problem. If I tried to open a social login on the same browser tab where my application is, Google can log in and return to the application and transfer the necessary tokens. This is another story for Facebook, where there is a warning in the developer tools console that does not appear to allow Facebook to display the login page.
To a large extent, the original method with which a new window (or tab) is opened is the way forward, but fixing the same for a mobile browser seems more complicated.