WebApi ASP.NET Identity Facebook Login

In the facebook authentication flow for identifying asp.net, the facebook oauth dialog adds the code, not the access token, to redirect_url so that the server can exchange this code for the access token through http://localhost:49164/signin-facebook?code=...&state=...

My problem is that my client is a mobile application that uses facebook sdk and immediately gives me an access token. Facebook says using sdk always gives you an access token, so can I immediately show the web api access token?

I understand that this is not very safe, but is it possible?

+32
facebook asp.net-web-api facebook-android-sdk facebook-ios-sdk
Jan 13 '14 at 13:40
source share
3 answers

I don’t know if you finally found a solution, but I am trying to do something quite similar, and I am still collecting puzzle pieces. I tried to post this as a comment instead of an answer, since I am not proposing a real solution, but it is too long.

Obviously, all OAPA parameters for Owin WebAPI are browser-based, that is, they require a large number of browser redirection requests that are not suitable for the native mobile application (in my case). I'm still learning and experimenting, but as Hongye Sun briefly described in one of the comments on his blog post, http://blogs.msdn.com/b/webdev/archive/2013/09/20/understanding-security-features -in-spa-template.aspx? PageIndex = 2 # comments , in order to enter the access token obtained via the Facebook SDK from Facebook, the API can be checked directly by creating a graph call to the / me endpoint.

Using the information returned by the graph call, you can check whether the user is registered or not. In the end, we need to sign the user, possibly using the Authentication.SignIn Owin method, returning the carrier token that will be used for all subsequent API calls.

EDIT: Actually, I realized that the carrier token is issued when the "/ Token" endpoint is called, which at the input accepts something like grant_type=password&username=Alice&password=password123 The problem here is that we don’t have a password (that's all points to the OAuth mechanism), so how else can you call the "/ Token" endpoint?

UPDATE: I finally found a working solution, and here is what I had to add to the existing classes to make it work: Startup.Auth.cs

 public partial class Startup { /// <summary> /// This part has been added to have an API endpoint to authenticate users that accept a Facebook access token /// </summary> static Startup() { PublicClientId = "self"; //UserManagerFactory = () => new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())); UserManagerFactory = () => { var userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())); userManager.UserValidator = new UserValidator<ApplicationUser>(userManager) { AllowOnlyAlphanumericUserNames = false }; return userManager; }; OAuthOptions = new OAuthAuthorizationServerOptions { TokenEndpointPath = new PathString("/Token"), Provider = new ApplicationOAuthProvider(PublicClientId, UserManagerFactory), AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"), AccessTokenExpireTimeSpan = TimeSpan.FromDays(14), AllowInsecureHttp = true }; OAuthBearerOptions = new OAuthBearerAuthenticationOptions(); OAuthBearerOptions.AccessTokenFormat = OAuthOptions.AccessTokenFormat; OAuthBearerOptions.AccessTokenProvider = OAuthOptions.AccessTokenProvider; OAuthBearerOptions.AuthenticationMode = OAuthOptions.AuthenticationMode; OAuthBearerOptions.AuthenticationType = OAuthOptions.AuthenticationType; OAuthBearerOptions.Description = OAuthOptions.Description; OAuthBearerOptions.Provider = new CustomBearerAuthenticationProvider(); OAuthBearerOptions.SystemClock = OAuthOptions.SystemClock; } public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; } public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; } public static Func<UserManager<ApplicationUser>> UserManagerFactory { get; set; } public static string PublicClientId { get; private set; } // For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864 public void ConfigureAuth(IAppBuilder app) { [Initial boilerplate code] OAuthBearerAuthenticationExtensions.UseOAuthBearerAuthentication(app, OAuthBearerOptions); [More boilerplate code] } } public class CustomBearerAuthenticationProvider : OAuthBearerAuthenticationProvider { public override Task ValidateIdentity(OAuthValidateIdentityContext context) { var claims = context.Ticket.Identity.Claims; if (claims.Count() == 0 || claims.Any(claim => claim.Issuer != "Facebook" && claim.Issuer != "LOCAL_AUTHORITY" )) context.Rejected(); return Task.FromResult<object>(null); } } 

In AccountController, I added the following action

  [HttpPost] [AllowAnonymous] [Route("FacebookLogin")] public async Task<IHttpActionResult> FacebookLogin(string token) { [Code to validate input...] var tokenExpirationTimeSpan = TimeSpan.FromDays(14); ApplicationUser user = null; // Get the fb access token and make a graph call to the /me endpoint // Check if the user is already registered // If yes retrieve the user // If not, register it // Finally sign-in the user: this is the key part of the code that creates the bearer token and authenticate the user var identity = new ClaimsIdentity(Startup.OAuthBearerOptions.AuthenticationType); identity.AddClaim(new Claim(ClaimTypes.Name, user.Id, null, "Facebook")); // This claim is used to correctly populate user id identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id, null, "LOCAL_AUTHORITY")); AuthenticationTicket ticket = new AuthenticationTicket(identity, new AuthenticationProperties()); var currentUtc = new Microsoft.Owin.Infrastructure.SystemClock().UtcNow; ticket.Properties.IssuedUtc = currentUtc; ticket.Properties.ExpiresUtc = currentUtc.Add(tokenExpirationTimeSpan); var accesstoken = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket); Authentication.SignIn(identity); // Create the response 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()) ); var json = Newtonsoft.Json.JsonConvert.SerializeObject(blob); // Return OK return Ok(blob); } 

What is it. The only difference I found with the answer to the classic / Token endpoint is that the carrier token is a little shorter and the expiration and release dates are in UTC and not GMT (at least on my machine).

Hope this helps!

+25
Jan 22 '14 at
source share

Following a great solution from @ s0nica, I modified some codes to integrate with the currently existing ASP.NET MVC pattern. s0nica is suitable, but not fully compatible with the MVC (Non-WebApi) AccountController .

The advantage of my approach is to work both with ASP.NET MVC and WebApi vice versa.

The main differences are the name of the application. The following link is used as the FacebookAccessToken application name ( http://blogs.msdn.com/b/webdev/archive/2013/10/16/get-more-information-from-social-providers-used-in-the-vs -2013-project-templates.aspx ), my approach is compatible with the approach to this link. I recommend using it.

Please note that the codes below are a modified version of @ s0nica's answer. So, (1) going through this link, (2) and then going through the s0nica code, (3) and, finally, consider mine later.

File Startup.Auth.cs.

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

api /AccountController.cs

  // POST api/Account/FacebookLogin [HttpPost] [AllowAnonymous] [Route("FacebookLogin")] public async Task<IHttpActionResult> 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 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<FacebookUserViewModel> 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<FacebookUserViewModel>(content); } return fbUser; } 
+15
Jun 16 '14 at 10:18
source share

Yes, you can use an external access token to securely log in.

I highly recommend that you follow this guide , which shows how to authenticate tokens using Web API 2 from scratch (using Angular JS as the front-end). In particular, step 4 includes two methods that allow authentication using an external access token, for example. as returned from the native SDK:

 [AllowAnonymous, HttpGet] async Task<IHttpActionResult> ObtainLocalAccessToken(string provider, string externalAccessToken) [AllowAnonymous, HttpPost] async Task<IHttpActionResult> RegisterExternal(RegisterExternalBindingModel model) 

In a nutshell:

  • Use your own SDK to get an external access token.

  • Call ObtainLocalAccessToken("Facebook", "[fb-access-token]") to determine if the user already has an account (200 response), in which case a new local token will be created for you. It also verifies that the external access token is legal.

  • If the call in step 2 is not completed (answer 400), you need to register a new account by calling RegisterExternal , passing an external token. This tutorial has a good example (see associateController.js ).

+12
Aug 20 '14 at 13:44 on
source share



All Articles