jjzjj

javascript - 移动浏览器上的 AngularJS 和 ASP.Net WebAPI 社交登录

coder 2024-07-25 原文

我正在关注这篇关于使用 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),它将关闭窗口并在父窗口上执行命令。

调用外部登录 url 并在桌面浏览器上打开弹出窗口(不是选项卡)的 angularJS Controller :

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/

到目前为止我在这个上下文中尝试的是在登录 Controller 中,我修改了函数以便外部登录 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 函数以返回登录页面,通过附加社交登录提供的访问 token 作为查询字符串:

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

在登录 Controller 中,我添加了一个函数来解析查询字符串并尝试调用 $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);
        }
}

但显然这给了我一个与 Angular 相关的错误(我目前还不知道):

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

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

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

谢谢!

更新:我设法解决了关于抛出根作用域的 Angular 错误,但遗憾的是,解决这个问题并没有解决主要问题。如果我尝试在我的应用程序所在的同一浏览器选项卡上打开社交登录,Google 可以登录并返回到应用程序并传递所需的 token 。 Facebook 的情况则不同,在开发人员工具控制台中,有一条警告似乎阻止 Facebook 显示登录页面。

几乎,打开新窗口(或选项卡)的原始方法是前进的方向,但为移动浏览器修复相同的方法似乎变得更具挑战性。

最佳答案

在桌面上,当 auth 窗口弹出(不是选项卡)时,它的 opener 属性设置为打开此弹出窗口的窗口,在移动设备上,如您所说,它不是弹出窗口向上窗口,但一个新的标签。当在浏览器中打开一个新选项卡时,opener 属性为 null 所以实际上这里有一个异常(exception):

window.opener.$windowScope.authCompletedCB

因为您不能引用空值 (window.opener) 的 $windowScope 属性,所以这行之后的每一行代码都不会被执行 - 这就是为什么该窗口在移动设备上未关闭。

解决方案

在您的 authComplete.js 文件中,而不是尝试调用 window.opener.$windowScope.authCompletedCB 并传递用户的片段,将片段保存在 localStorage 或 cookie 中(毕竟 authComplete.html 的页面是与您的应用程序同源)使用 JSON.stringify() 并使用 window.close() 关闭窗口。

loginController.js 中,创建一个 $interval 大约 100 毫秒,以检查 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();
    });

}]);

关于javascript - 移动浏览器上的 AngularJS 和 ASP.Net WebAPI 社交登录,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34795485/

有关javascript - 移动浏览器上的 AngularJS 和 ASP.Net WebAPI 社交登录的更多相关文章

  1. ruby - 多次弹出/移动 ruby​​ 数组 - 2

    我的代码目前看起来像这样numbers=[1,2,3,4,5]defpop_threepop=[]3.times{pop有没有办法在一行中完成pop_three方法中的内容?我基本上想做类似numbers.slice(0,3)的事情,但要删除切片中的数组项。嗯...嗯,我想我刚刚意识到我可以试试slice! 最佳答案 是numbers.pop(3)或者numbers.shift(3)如果你想要另一边。 关于ruby-多次弹出/移动ruby​​数组,我们在StackOverflow上找到一

  2. ruby-on-rails - date_field_tag,如何设置默认日期? [ rails 上的 ruby ] - 2

    我想设置一个默认日期,例如实际日期,我该如何设置?还有如何在组合框中设置默认值顺便问一下,date_field_tag和date_field之间有什么区别? 最佳答案 试试这个:将默认日期作为第二个参数传递。youcorrectlysetthedefaultvalueofcomboboxasshowninyourquestion. 关于ruby-on-rails-date_field_tag,如何设置默认日期?[rails上的ruby],我们在StackOverflow上找到一个类似的问

  3. ruby-on-rails - openshift 上的 rails 控制台 - 2

    我将我的Rails应用程序部署到OpenShift,它运行良好,但我无法在生产服务器上运行“Rails控制台”。它给了我这个错误。我该如何解决这个问题?我尝试更新ruby​​gems,但它也给出了权限被拒绝的错误,我也无法做到。railsc错误:Warning:You'reusingRubygems1.8.24withSpring.UpgradetoatleastRubygems2.1.0andrun`gempristine--all`forbetterstartupperformance./opt/rh/ruby193/root/usr/share/rubygems/rubygems

  4. ruby - 在 Ruby 中用键盘诅咒数组浏览 - 2

    我正在尝试在Ruby中制作一个cli应用程序,它接受一个给定的数组,然后将其显示为一个列表,我可以使用箭头键浏览它。我觉得我已经在Ruby中看到一个库已经这样做了,但我记不起它的名字了。我正在尝试对soundcloud2000中的代码进行逆向工程做类似的事情,但他的代码与SoundcloudAPI的使用紧密耦合。我知道cursesgem,我正在考虑更抽象的东西。广告有没有人见过可以做到这一点的库或一些概念证明的Ruby代码可以做到这一点? 最佳答案 我不知道这是否是您正在寻找的,但也许您可以使用我的想法。由于我没有关于您要完成的工作

  5. ruby-on-rails - 相关表上的范围为 "WHERE ... LIKE" - 2

    我正在尝试从Postgresql表(table1)中获取数据,该表由另一个相关表(property)的字段(table2)过滤。在纯SQL中,我会这样编写查询:SELECT*FROMtable1JOINtable2USING(table2_id)WHEREtable2.propertyLIKE'query%'这工作正常:scope:my_scope,->(query){includes(:table2).where("table2.property":query)}但我真正需要的是使用LIKE运算符进行过滤,而不是严格相等。然而,这是行不通的:scope:my_scope,->(que

  6. ruby-on-rails - 浏览 Ruby 源代码 - 2

    我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru

  7. ruby-on-rails - 如何重命名或移动 Rails 的 README_FOR_APP - 2

    当我在我的Rails应用程序根目录中运行rakedoc:app时,API文档是使用/doc/README_FOR_APP作为主页生成的。我想向该文件添加.rdoc扩展名,以便它在GitHub上正确呈现。更好的是,我想将它移动到应用程序根目录(/README.rdoc)。有没有办法通过修改包含的rake/rdoctask任务在我的Rakefile中执行此操作?是否有某个地方可以查找可以修改的主页文件的名称?还是我必须编写一个新的Rake任务?额外的问题:Rails应用程序的两个单独文件/README和/doc/README_FOR_APP背后的逻辑是什么?为什么不只有一个?

  8. ruby-on-rails - rbenv:从 RVM 移动到 rbenv 后,在 Jenkins 执行 shell 中找不到命令 - 2

    我从Ubuntu服务器上的RVM转移到rbenv。当我使用RVM时,使用bundle没有问题。转移到rbenv后,我在Jenkins的执行shell中收到“找不到命令”错误。我内爆并删除了RVM,并从~/.bashrc'中删除了所有与RVM相关的行。使用后我仍然收到此错误:rvmimploderm~/.rvm-rfrm~/.rvmrcgeminstallbundlerecho'exportPATH="$HOME/.rbenv/bin:$PATH"'>>~/.bashrcecho'eval"$(rbenvinit-)"'>>~/.bashrc.~/.bashrcrbenvversions

  9. ruby-on-rails - Ruby - 如何从 ruby​​ 上的 .pfx 文件中提取公钥、rsa 私钥和 CA key - 2

    我有一个.pfx格式的证书,我需要使用ruby​​提取公共(public)、私有(private)和CA证书。使用shell我可以这样做:#ExtractPublicKey(askforpassword)opensslpkcs12-infile.pfx-outfile_public.pem-clcerts-nokeys#ExtractCertificateAuthorityKey(askforpassword)opensslpkcs12-infile.pfx-outfile_ca.pem-cacerts-nokeys#ExtractPrivateKey(askforpassword)o

  10. ruby - 在 ASP 页面上 Mechanize 中断 - 2

    require'mechanize'agent=Mechanize.newlogin=agent.get('http://www.schoolnet.ch/DE/HomeDE.htm')agent.clicklogin.link_withtext:/Login/然后我得到Mechanize::UnsupportedSchemeError。 最佳答案 Mechanize不支持javascript但您可以将搜索字段添加到表单并为其分配搜索词并使用mechanize提交表单form=page.forms.firstform.add_fie

随机推荐