Identity Server 3 Facebook登录获取电子邮件

Rya*_*yan 7 facebook-graph-api oauth-2.0 facebook-login katana identityserver3

身份服务器已实施且运行良好.谷歌登录正在运行,并返回多项索赔,包括电子邮件.

Facebook登录正常,我的应用程序正在运行,并在新用户登录时请求电子邮件权限.

问题是我无法从oauth端点返回电子邮件,我似乎无法找到access_token来手动请求用户信息.我所拥有的只是从facebook登录端点返回的"代码".

这是IdentityServer设置.

var fb = new FacebookAuthenticationOptions
            {
                AuthenticationType = "Facebook",
                SignInAsAuthenticationType = signInAsType,
                AppId = ConfigurationManager.AppSettings["Facebook:AppId"],
                AppSecret = ConfigurationManager.AppSettings["Facebook:AppSecret"]
            };

            fb.Scope.Add("email");

            app.UseFacebookAuthentication(fb);
Run Code Online (Sandbox Code Playgroud)

当然我已经定制了AuthenticateLocalAsync方法,但我收到的声明只包括名称.没有电子邮件声明

通过身份服务器的源代码挖掘,我意识到有一些声称要转换facebook声明的事情,所以我扩展了该类调试它,看看它是否正在剥离任何声明,但事实并非如此.

我也看了fiddler的http调用,我只看到以下内容(道歉,因为代码格式化在url上不起作用.我试图将查询字符串格式化为自己的行,但它没有采取)

  1. (facebook.com)

    / dialog/oauth?response_type = code&client_id = xxx&redirect_uri = https%3A%2F%2Fidentity.[site] .com%2Fid%2Fsignin-facebook&scope = email&state = xxx

  2. (facebook.com)

    /login.php?skip_api_login = 1&api_key = xxx&signed_next = 1&next = https%3A%2F%2Fwww.facebook.com%2Fv2.7%2Fdialog%2Foauth%3Fredirect_uri%3Dhttps%253A%252F%252Fidentity.[site]. COM%252Fid%252Fsignin-的Facebook%26state%3Dxxx%26scope%3Demail%26response_type%3Dcode%26client_id%3Dxxx%26ret%3Dlogin%26logger_id%3Dxxx&cancel_url = HTTPS%3A%2F%2Fidentity.[站点] .COM%2Fid%2Fsignin- facebook%3Ferror%3Daccess_denied%26error_code%3D200%26error_description%3DPermissions%2Berror%26error_reason%3Duser_denied%26state%3Dxxx%23_%3D_&display = page&locale = en_US&logger_id = xxx

  3. (facebook.com)

    POST/cookie/consent /?pv = 1&dpr = 1 HTTP/1.1

  4. (facebook.com)

    /login.php?login_attempt = 1&next = https%3A%2F%2Fwww.facebook.com%2Fv2.7%2Fdialog%2Foauth%3Fredirect_uri%3Dhttps%253A%252F%252Fidentity.[site] .com%252Fid%252Fsignin- facebook%26state%3Dxxx%26scope%3Demail%26response_type%3Dcode%26client_id%3Dxxx%26ret%3Dlogin%26logger_id%3Dxxx&lwv = 100

  5. (facebook.com)

    /v2.7/dialog/oauth?redirect_uri = https%3A%2F%2Fidentity.[site] .com%2Fid%2Fsignin-facebook&state = xxx&scope = email&response_type = code&client_id = xxx&ret = login&logger_id = xxx&hash = XXX

  6. (身份服务器)

    / id/signin-facebook?code = xxx&state = xxx

我在最后一次调用时看到了代码参数,并认为我可以使用那里的代码从facebook API获取access_token https://developers.facebook.com/docs/facebook-login/manually-build-a-login -流

但是,当我尝试时,我从API收到一条消息,告诉我代码已被使用.

我还尝试将UserInformationEndpoint更改为FacebookAuthenticationOptions以强制它通过将?fields = email附加到默认端点位置的末尾来请求电子邮件,但这会导致身份服务器吐出错误"登录时出错外部提供程序.错误消息是:access_denied".

如果我可以更改中间件以使用response_type = id_token发送请求,我可能能够解决所有问题,但我无法弄清楚如何执行此操作或如何在首先返回时提取该访问令牌能够使用Facebook C#sdk.

所以我想任何帮助或方向都会很棒.我花了无数个小时研究并试图解决问题.我需要做的就是通过IdentityServer3获取登录用户的电子邮件地址.听起来不那么难,但我卡住了.

Rya*_*yan 4

我终于明白了这一点。答案与 Mitra 的评论有关,尽管这两个答案似乎都不符合要求,所以我在这里放了另一个答案。首先,您需要从 Facebook 的身份验证端点请求 access_token,而不是代码(授权代码)。为此,请像这样设置

        var fb = new FacebookAuthenticationOptions
        {
            AuthenticationType = "Facebook",
            SignInAsAuthenticationType = signInAsType,
            AppId = ConfigurationManager.AppSettings["Facebook:AppId"],
            AppSecret = ConfigurationManager.AppSettings["Facebook:AppSecret"],
            Provider = new FacebookAuthenticationProvider()
            {
                OnAuthenticated = (context) =>
                {
                    context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:access_token", context.AccessToken, ClaimValueTypes.String, "Facebook"));
                    return Task.FromResult(0);
                }
            }

        };

        fb.Scope.Add("email");

        app.UseFacebookAuthentication(fb);
Run Code Online (Sandbox Code Playgroud)

然后,您需要在登录后捕获响应。我使用IdentityServer3 Samples Repository中的以下文件,该文件覆盖(读取、提供功能)从外部站点登录用户所需的方法。根据此响应,我使用C# Facebook SDK以及ExternalAuthenticationContext 中新返回的 access_token 声明来请求我需要的字段并将它们添加到声明列表中。然后我可以使用该信息来创建/登录用户。

    public override async Task AuthenticateExternalAsync(ExternalAuthenticationContext ctx)
    {
        var externalUser = ctx.ExternalIdentity;
        var claimsList = ctx.ExternalIdentity.Claims.ToList();
        if (externalUser.Provider == "Facebook")
        {
            var extraClaims = GetAdditionalFacebookClaims(externalUser.Claims.First(claim => claim.Type == "urn:facebook:access_token"));
            claimsList.Add(new Claim("email", extraClaims.First(k => k.Key == "email").Value.ToString()));
            claimsList.Add(new Claim("given_name", extraClaims.First(k => k.Key == "first_name").Value.ToString()));
            claimsList.Add(new Claim("family_name", extraClaims.First(k => k.Key == "last_name").Value.ToString()));
        }

        if (externalUser == null)
        {
            throw new ArgumentNullException("externalUser");
        }

        var user = await userManager.FindAsync(new Microsoft.AspNet.Identity.UserLoginInfo(externalUser.Provider, externalUser.ProviderId));
        if (user == null)
        {
            ctx.AuthenticateResult = await ProcessNewExternalAccountAsync(externalUser.Provider, externalUser.ProviderId, claimsList);
        }
        else
        {
            ctx.AuthenticateResult = await ProcessExistingExternalAccountAsync(user.Id, externalUser.Provider, externalUser.ProviderId, claimsList);
        }
    }
Run Code Online (Sandbox Code Playgroud)

就是这样!如果您对简化此过程有任何建议,请告诉我。我打算修改此代码以执行从 FacebookAuthenticationOptions 对 API 的调用,但 Events 属性显然不再存在。

编辑:GetAdditionalFacebookClaims 方法只是一种根据已提取的访问令牌创建新 FacebookClient 的方法,并在 Facebook API 中查询您需要的其他用户声明。例如,我的方法如下所示:

protected static JsonObject GetAdditionalFacebookClaims(Claim accessToken)
    {
        var fb = new FacebookClient(accessToken.Value);
        return fb.Get("me", new {fields = new[] {"email", "first_name", "last_name"}}) as JsonObject;
    }
Run Code Online (Sandbox Code Playgroud)

  • 如果您将从 Facebook 获取电子邮件的逻辑放在“FacebookAuthenticationProvider.OnAuthenticated”中,那么您不需要“AuthenticateExternalAsync()”中的特殊情况逻辑 (3认同)