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

北战南征 提交于 2020-01-01 13:19:08

问题


In the out of the box ASP.NET WEB API oAuth implementation after a new user calls:

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

user is redirected to external log in (in my case Facebook) resulting in a token that they use for registration (out of the box code bellow)

        // 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();
        }

During RegisterExternal I want to populate another database using the data on their Facebook (first name, last name, email, friends, ext..)

The Bearer token I am getting during registration can not simply be called as such:

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

So from what I understand I need to modify Startup.Auth with claims for this data as i have done by adding:

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

But then how do I go about fetching that data in my RegisterExternal method?


回答1:


I had the same problem (I think) - the problem was that FB the OAuth infrastructure was only filling the basic data and I wanted a bit more.

After doing some digging into the source code of ASP.NET identity, I turned out with the following:

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",
});

and the important part here is the custom provider:

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

}

This basically puts all the data in the claim and can be retrieved anywhere else the same way.




回答2:


The external provider, in this case Facebook, will populate the Claims and these can be accessed in your callback method in LoginInfo.

Here's the code for reading the Facebook Access token:

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

If you set a breakpoint there you'll be able to see what else is returned by Facebook.




回答3:


John Mc really pointed me in the right direction, here is a more full solution.

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

And then in the claims (this is the key part) as John's pointed out:

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


来源:https://stackoverflow.com/questions/28653773/web-api-2-get-profile-data-during-oauth-registerexternal-facebook

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!