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

时间:2016-08-26 17:53:01

标签: facebook-graph-api oauth-2.0 facebook-login identityserver3 katana

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

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);

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

通过身份服务器的源代码,我意识到有一些声称正在发生转换facebook声明的事情,所以我扩展了该类调试它,看看它是否正在删除任何声明,但事实并非如此。 / p>

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

  1. (facebook.com)

    /对话框/ OAuth的 ?RESPONSE_TYPE =代码 &安培; CLIENT_ID = XXX &安培; REDIRECT_URI = HTTPS%3A%2F%2Fidentity [站点] .COM%2Fid%2Fsignin Facebook的 &安培;范围=电子邮件 &安培;状态= XXX

  2. (facebook.com)

    /login.php ?skip_api_login = 1 &安培; API_KEY = XXX &安培; signed_next = 1 &安培;下一= HTTPS%3A%2F%2Fwww.facebook.com%2Fv2.7%2Fdialog%2Foauth%3Fredirect_uri%3Dhttps%253A%252F%252Fidentity [站点] .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_ &安培;显示=页 &安培;语言环境= 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 &安培;下一= HTTPS%3A%2F%2Fwww.facebook.com%2Fv2.7%2Fdialog%2Foauth%3Fredirect_uri%3Dhttps%253A%252F%252Fidentity [站点] .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。[站点] .COM%2Fid%2Fsignin Facebook的 &安培;状态= XXX &安培;范围=电子邮件 &安培; RESPONSE_TYPE =代码 &安培; CLIENT_ID = XXX &安培; RET =登录 &安培; logger_id = XXX &安培;散列= XXX

  6. (身份服务器)

    / ID /登入Facebook的 ?代码= XXX &安培;状态= XXX

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

    然而,当我尝试时,我从API收到一条消息,告诉我代码已被使用。

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

    如果我可以更改中间件以使用response_type = id_token发送请求,我可以解决这个问题,但我无法弄清楚如何执行此操作或如何在第一次返回时提取该访问令牌能够使用Facebook C#sdk的地方。

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

1 个答案:

答案 0 :(得分:4)

我终于弄明白了。答案与米特拉的评论有关,虽然这些答案似乎都不符合要求,所以我在这里再说一个。首先,您需要从Facebook的Authentication端点请求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);

然后,您需要在登录后捕获响应。我正在使用file中的以下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);
        }
    }

就是这样!如果您对简化此过程有任何建议,请告诉我们。我打算修改此代码以执行从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;
    }