Identity framework ASP.NET WebApi Login Facebook

Nel stream di autenticazione di facebook per l’id quadro di asp.net, la finestra di dialogo di Facebook oauth aggiunge un codice piuttosto che un token di accesso a redirect_url in modo che il server possa scambiare questo codice per un token di accesso tramite http://localhost:49164/signin-facebook?code=...&state=...

Il mio problema è che il mio client è un’app mobile che usa il sdk di Facebook e che subito mi dà un token di accesso. Facebook dice che l’uso di sdk ti fornisce sempre un token di accesso, quindi posso immediatamente dare a web api il token di accesso?

Capisco che questo non è molto sicuro, ma è anche ansible?

Non so se hai finalmente trovato una soluzione, ma sto cercando di fare qualcosa di molto simile e sto ancora mettendo insieme i pezzi del puzzle. Avevo provato a postare questo come commento invece di una risposta, in quanto non fornisco una soluzione reale, ma è troppo lungo.

Apparentemente tutte le opzioni OAP di OAP WebAPI sono basate su browser, ovvero richiedono molte richieste di reindirizzamento del browser che non si adattano a un’app mobile nativa (il mio caso). Sto ancora indagando e sperimentando, ma come brevemente descritto da Hongye Sun in uno dei commenti al suo post sul blog, http://blogs.msdn.com/b/webdev/archive/2013/09/20/understanding-security -features-in-spa-template.aspx? PageIndex = 2 # commenti , per accedere con Facebook il token di accesso ricevuto utilizzando l’SDK di Facebook può essere verificato direttamente dall’API effettuando una chiamata di grafico all’endpoint / me.

Utilizzando le informazioni restituite dalla chiamata del grafico, è ansible verificare se l’utente è già registrato o meno. Alla fine abbiamo bisogno di accedere all’utente, magari usando il metodo Authentication.SignIn Owin, restituendo un token al portatore che verrà utilizzato per tutte le successive chiamate API.

EDIT: In realtà ho sbagliato, il token bearer viene emesso chiamando “endpoint” / Token, che su input accetta qualcosa come grant_type=password&username=Alice&password=password123 Il problema qui è che non abbiamo una password (questo è il punto intero del meccanismo OAuth), quindi in che altro modo possiamo richiamare l’endpoint “/ Token”?

AGGIORNAMENTO: ho finalmente trovato una soluzione funzionante e il seguente è quello che ho dovuto aggiungere alle classi esistenti per farlo funzionare: Startup.Auth.cs

 public partial class Startup { ///  /// This part has been added to have an API endpoint to authenticate users that accept a Facebook access token ///  static Startup() { PublicClientId = "self"; //UserManagerFactory = () => new UserManager(new UserStore(new ApplicationDbContext())); UserManagerFactory = () => { var userManager = new UserManager(new UserStore(new ApplicationDbContext())); userManager.UserValidator = new UserValidator(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> 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(null); } } 

In AccountController ho aggiunto la seguente azione

  [HttpPost] [AllowAnonymous] [Route("FacebookLogin")] public async Task 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); } 

Questo è tutto. L’unica differenza che ho riscontrato con la risposta dell’endpoint classico / Token è che il token bearer è leggermente più corto e le date di scadenza e di emissione sono in UTC anziché in GMT (almeno sulla mia macchina).

Spero che aiuti!

Seguito da un’ottima soluzione di @ s0nica, ho modificato alcuni codici per integrarmi con il modello MVC ASP.NET attualmente implementato. L’approccio s0nica è buono ma non completamente compatibile con AccountController MVC (Non-WebApi).

Il vantaggio del mio approccio è di lavorare con ASP.NET MVC e WebApi viceversa.

Le principali differenze sono il nome della richiesta. Come il nome della rivendicazione FacebookAccessToken viene utilizzato come seguito dal link ( http://blogs.msdn.com/b/webdev/archive/2013/10/16/get-more-information-from-social-providers-used-in- the-vs-2013-project-templates.aspx ), il mio approccio è compatibile con l’approccio del collegamento dato. Raccomando di usarlo.

Nota che i seguenti codici sono versione modificata della risposta di @ s0nica. Quindi, (1) descrizione dettagliata del collegamento, (2) e quindi descrizione del codice s0nica, (3) e infine considerare il mio in seguito.

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 (class interna di 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; } } 

Metodo VerifyFacebookAccessToken (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; } 

Sì, puoi utilizzare un token di accesso esterno per accedere in modo sicuro.

Consiglio vivamente di seguire questo tutorial , che mostra come eseguire l’autenticazione basata su token con Web API 2 da zero (utilizzando Angular JS come front-end). In particolare, il passaggio 4 include due metodi che consentono di autenticare utilizzando un token di accesso esterno, ad esempio come restituito da un SDK nativo:

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

In poche parole:

  1. Utilizza l’SDK nativo per ottenere un token di accesso esterno.

  2. Chiama ObtainLocalAccessToken("Facebook", "[fb-access-token]") per determinare se l’utente ha già un account (200 risposte), nel qual caso verrà generato un nuovo token locale . Verifica inoltre che il token di accesso esterno sia legittimo.

  3. Se la chiamata al passaggio 2 non è riuscita (risposta 400), è necessario registrare un nuovo account chiamando RegisterExternal , passando il token esterno. Il tutorial sopra ha un buon esempio di questo (vedi associatedController.js ).