WEB API 2 : 在 oauth RegisterExternal (facebook) 期间获取配置文件数据

WEB API 2 : get profile data during oauth RegisterExternal (facebook)

开箱即用 ASP.NET WEB API 新用户调用后的 oAuth 实现:

GET api/Account/ExternalLogins?returnUrl=%2F&generateState=true

用户被重定向到外部登录(在我的例子中是 Facebook)导致他们用于注册的令牌(下面的开箱即用代码)

        // POST api/Account/RegisterExternal
        [OverrideAuthentication]
        [HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
        [Route("RegisterExternal")]
        public async Task<IHttpActionResult> RegisterExternal([FromBody]RegisterExternalBindingModel model)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);

            if (externalLogin == null)
            {
                return InternalServerError();
            }

            IdentityUser user = new IdentityUser
            {
                UserName = model.UserName
            };
            user.Logins.Add(new IdentityUserLogin
            {
                LoginProvider = externalLogin.LoginProvider,
                ProviderKey = externalLogin.ProviderKey
            });
            IdentityResult result = await UserManager.CreateAsync(user);
            IHttpActionResult errorResult = GetErrorResult(result);

            if (errorResult != null)
            {
                return errorResult;
            }

            return Ok();
        }

在 RegisterExternal 期间,我想使用他们 Facebook 上的数据(名字、姓氏、电子邮件、朋友、分机..)填充另一个数据库

我在注册期间获得的 Bearer 令牌可以不能简单地这样称呼:

var accessToken = "token from header";
var client = new FacebookClient(accessToken);

因此,据我了解,我需要修改 Startup.Auth 并声明此数据,就像我通过添加所做的那样:

        var facebookProvider = new FacebookAuthenticationProvider()
        {
            OnAuthenticated = (context) =>
            {
                // Add the email id to the claim
                context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.Email));
                return Task.FromResult(0);
            }
        };
        var options = new FacebookAuthenticationOptions()
        {
            AppId = "xxxxxxxxxxxxxxxxx",
            AppSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
            Provider = facebookProvider
        };
        options.Scope.Add("email");
        options.Scope.Add("user_friends");
        options.Scope.Add("public_profile");
        app.UseFacebookAuthentication(options);

但是我该如何在我的 RegisterExternal 方法中获取该数据?

外部提供商(在本例中为 Facebook)将填充声明,这些可以在您在 LoginInfo 中的回调方法中访问。

这是读取 Facebook 访问令牌的代码:

var accessToken = loginInfo.ExternalIdentity.Claims.FirstOrDefault(x => x.Type == Constants.FacebookAccessToken).Value;

如果您在此处设置断点,您将能够看到 Facebook 返回的其他内容。

John Mc 确实为我指明了正确的方向,这是一个更完整的解决方案。

// POST api/Account/RegisterExternalToken
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
[Route("RegisterExternalToken")]
public async Task<IHttpActionResult> RegisterExternalToken()
{
    ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);

    if (externalLogin == null)
    {
        return InternalServerError();
    }

    var facebookToken = externalLogin.Token;

然后在声明中(这是关键部分),正如 John 所指出的:

        private class ExternalLoginData
        {
            public string LoginProvider { get; set; }
            public string ProviderKey { get; set; }
            public string UserName { get; set; }
            public string Token { get; set; }

            public IList<Claim> GetClaims()
            {
                IList<Claim> claims = new List<Claim>();
                claims.Add(new Claim(ClaimTypes.NameIdentifier, ProviderKey, null, LoginProvider));

                if (UserName != null)
                {
                    claims.Add(new Claim(ClaimTypes.Name, UserName, null, LoginProvider));
                }

                if (Token != null)
                {
                    claims.Add(new Claim("FacebookAccessToken", Token, null, LoginProvider));
                }

                return claims;
            }

            public static ExternalLoginData FromIdentity(ClaimsIdentity identity)
            {
                if (identity == null)
                {
                    return null;
                }

                Claim providerKeyClaim = identity.FindFirst(ClaimTypes.NameIdentifier);

                if (providerKeyClaim == null || String.IsNullOrEmpty(providerKeyClaim.Issuer)
                    || String.IsNullOrEmpty(providerKeyClaim.Value))
                {
                    return null;
                }

                if (providerKeyClaim.Issuer == ClaimsIdentity.DefaultIssuer)
                {
                    return null;
                }

                return new ExternalLoginData
                {
                    LoginProvider = providerKeyClaim.Issuer,
                    ProviderKey = providerKeyClaim.Value,
                    UserName = identity.FindFirstValue(ClaimTypes.Name),
                    Token = identity.Claims.FirstOrDefault(x => x.Type.Contains("FacebookAccessToken")).Value
                };
            }
        }

我遇到了同样的问题(我认为)- 问题是 FB OAuth 基础设施只填充了基本数据,我想要更多。

在深入研究 ASP.NET 身份的源代码后,我得到了以下结果:

app.UseFacebookAuthentication(new FacebookAuthenticationOptions
{
    AppId = "",
    AppSecret = "",
    Scope = { "public_profile", "email", "user_birthday", "user_location" },
    Provider = new FacebookAuthProvider(),
    UserInformationEndpoint = "https://graph.facebook.com/v2.5/me?fields=id,name,email,first_name,last_name,location,birthday,picture",
});

这里的重要部分是自定义提供程序:

private class FacebookAuthProvider : FacebookAuthenticationProvider
{
    /// <summary>
    /// Invoked whenever Facebook succesfully authenticates a user
    /// </summary>
    /// <param name="context">Contains information about the login session as well as the user <see cref="T:System.Security.Claims.ClaimsIdentity" />.</param>
    /// <returns>A <see cref="T:System.Threading.Tasks.Task" /> representing the completed operation.</returns>
    public override Task Authenticated(FacebookAuthenticatedContext context)
    {
        TryParseProperty(context, "first_name", Claims.FirstName);
        TryParseProperty(context, "last_name", Claims.LastName);
        TryParseProperty(context, "picture.data.url", Claims.PhotoUrl);

        return base.Authenticated(context);
    }

    private void TryParseProperty(FacebookAuthenticatedContext context, string name, string targetName)
    {
        var value = context.User.SelectToken(name);
        if (value != null)
        {
            context.Identity.AddClaim(targetName, value.ToString());
        }
    }

}

这基本上将所有数据都放在声明中,并且可以用相同的方式在其他任何地方检索。