ASP.NET Web API and Identity with Facebook login

前端 未结 3 2109
迷失自我
迷失自我 2020-11-29 16:42

In the Facebook authentication flow for ASP.NET Identity, the Facebook OAuth dialog appends a code rather than an access token to the redirect_url so that the s

3条回答
  •  执笔经年
    2020-11-29 17:16

    Following the great solution from @s0nica, I modified some code in order to integrate with the currently implemented ASP.NET MVC template. s0nica's approach is good, but it isn't fully compatible with the MVC (Non-WebApi) AccountController.

    The benefit of my approach is that it works with both ASP.NET MVC and ASP.NET Web API.

    The main differences is the claim name. As the claim name FacebookAccessToken is used on this MSDN blog, my approach is compatible with the approach of given in the link. I recommend using it with this.

    Note that below code is a modified version of @s0nica's answer. So, (1) walkthrough the above link, and then (2) walkthrough @s0nica's code, and finally (3) consider mine afterwards.

    Startup.Auth.cs file.

    public class CustomBearerAuthenticationProvider : OAuthBearerAuthenticationProvider
    {
        // This validates the identity based on the issuer of the claim.
        // The issuer is set in the API endpoint that logs the user in
        public override Task ValidateIdentity(OAuthValidateIdentityContext context)
        {
            var claims = context.Ticket.Identity.Claims;
            if (!claims.Any() || claims.Any(claim => claim.Type != "FacebookAccessToken")) // modify claim name
                context.Rejected();
            return Task.FromResult(null);
        }
    }
    
    
    

    api/AccountController.cs

    // POST api/Account/FacebookLogin
    [HttpPost]
    [AllowAnonymous]
    [Route("FacebookLogin")]
    public async Task FacebookLogin([FromBody] FacebookLoginModel model)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }
    
        if (string.IsNullOrEmpty(model.token))
        {
            return BadRequest("No access token");
        }
    
        var tokenExpirationTimeSpan = TimeSpan.FromDays(300);
        ApplicationUser user = null;
        string username;
        // Get the fb access token and make a graph call to the /me endpoint
        var fbUser = await VerifyFacebookAccessToken(model.token);
        if (fbUser == null)
        {
            return BadRequest("Invalid OAuth access token");
        }
    
        UserLoginInfo loginInfo = new UserLoginInfo("Facebook", model.userid);
        user = await UserManager.FindAsync(loginInfo);
    
        // If user not found, register him with username.
        if (user == null)
        {
            if (String.IsNullOrEmpty(model.username))
                return BadRequest("unregistered user");
    
            user = new ApplicationUser { UserName = model.username };
    
            var result = await UserManager.CreateAsync(user);
            if (result.Succeeded)
            {
                result = await UserManager.AddLoginAsync(user.Id, loginInfo);
                username = model.username;
                if (!result.Succeeded)
                    return BadRequest("cannot add facebook login");
            }
            else
            {
                return BadRequest("cannot create user");
            }
        }
        else
        {
            // existed user.
            username = user.UserName;
        }
    
        // common process: Facebook claims update, Login token generation
        user = await UserManager.FindByNameAsync(username);
    
        // Optional: make email address confirmed when user is logged in from Facebook.
        user.Email = fbUser.email;
        user.EmailConfirmed = true;
        await UserManager.UpdateAsync(user);
    
        // Sign-in the user using the OWIN flow
        var identity = new ClaimsIdentity(Startup.OAuthBearerOptions.AuthenticationType);
    
        var claims = await UserManager.GetClaimsAsync(user.Id);
        var newClaim = new Claim("FacebookAccessToken", model.token); // For compatibility with ASP.NET MVC AccountController
        var oldClaim = claims.FirstOrDefault(c => c.Type.Equals("FacebookAccessToken"));
        if (oldClaim == null)
        {
            var claimResult = await UserManager.AddClaimAsync(user.Id, newClaim);
            if (!claimResult.Succeeded)
                return BadRequest("cannot add claims");
        }
        else
        {
            await UserManager.RemoveClaimAsync(user.Id, oldClaim);
            await UserManager.AddClaimAsync(user.Id, newClaim);
        }
    
        AuthenticationProperties properties = ApplicationOAuthProvider.CreateProperties(user.UserName);
        var currentUtc = new Microsoft.Owin.Infrastructure.SystemClock().UtcNow;
        properties.IssuedUtc = currentUtc;
        properties.ExpiresUtc = currentUtc.Add(tokenExpirationTimeSpan);
        AuthenticationTicket ticket = new AuthenticationTicket(identity, properties);
        var accesstoken = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket);
        Request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accesstoken);
        Authentication.SignIn(identity);
    
        // Create the response building a JSON object that mimics exactly the one issued by the default /Token endpoint
        JObject blob = new JObject(
            new JProperty("userName", user.UserName),
            new JProperty("access_token", accesstoken),
            new JProperty("token_type", "bearer"),
            new JProperty("expires_in", tokenExpirationTimeSpan.TotalSeconds.ToString()),
            new JProperty(".issued", ticket.Properties.IssuedUtc.ToString()),
            new JProperty(".expires", ticket.Properties.ExpiresUtc.ToString()),
            new JProperty("model.token", model.token),
        );
        // Return OK
        return Ok(blob);
    }
    

    Facebook Login Model for Binding (inner class of api/AccountController.cs)

    public class FacebookLoginModel
    {
        public string token { get; set; }
        public string username { get; set; }
        public string userid { get; set; }
    }
    
    public class FacebookUserViewModel
    {
        public string id { get; set; }
        public string first_name { get; set; }
        public string last_name { get; set; }
        public string username { get; set; }
        public string email { get; set; }
    }
    

    VerifyFacebookAccessToken method (in api/AccountController.cs)

    private async Task VerifyFacebookAccessToken(string accessToken)
    {
        FacebookUserViewModel fbUser = null;
        var path = "https://graph.facebook.com/me?access_token=" + accessToken;
        var client = new HttpClient();
        var uri = new Uri(path);
        var response = await client.GetAsync(uri);
        if (response.IsSuccessStatusCode)
        {
            var content = await response.Content.ReadAsStringAsync();
            fbUser = Newtonsoft.Json.JsonConvert.DeserializeObject(content);
        }
        return fbUser;
    }
    

    提交回复
    热议问题