¿Cómo acceder a la información privada de Facebook utilizando ASP.NET Identity (OWIN)?

Estoy desarrollando un sitio web en ASP.NET MVC 5 (usando la versión RC1 actualmente). El sitio usará Facebook para la autenticación del usuario y para recuperar datos del perfil inicial.

Para el sistema de autenticación estoy usando el nuevo motor de identidad ASP.NET basado en OWIN ( http://blogs.msdn.com/b/webdev/archive/2013/07/03/understanding-owin-forms-authentication-in-mvc -5.aspx ), ya que simplifica enormemente el proceso de autenticación con proveedores externos.

El problema es que una vez que un usuario inicia sesión por primera vez, quiero obtener su dirección de correo electrónico del perfil de Facebook, pero esta información no está incluida en los reclamos generados. Así que he pensado en estas alternativas para obtener la dirección:

  1. Indique al motor de Identidad de ASP.NET que incluya la dirección de correo electrónico en el conjunto de datos recuperados de Facebook y luego convertidos en reclamos. No sé si esto es posible en absoluto.

  2. Utilice la API de gráficos de Facebook ( https://developers.facebook.com/docs/getting-started/graphapi ) para recuperar la dirección de correo electrónico mediante el ID de usuario de Facebook (que se incluye en los datos de reclamaciones). Pero esto no funcionará si el usuario ha configurado su dirección de correo electrónico como privada.

  3. Use la API de gráficos de Facebook, pero especifique “yo” en lugar de la ID de usuario de Facebook ( https://developers.facebook.com/docs/reference/api/user ). Pero se requiere un token de acceso, y no sé cómo (o si es posible) recuperar el token de acceso que utiliza ASP.NET para obtener los datos del usuario.

Entonces la pregunta es:

  1. ¿Cómo puedo instruir al motor de identidad de ASP.NET para recuperar información adicional de Facebook e incluirla en los datos de reclamaciones?

  2. O, alternativamente, ¿cómo puedo recuperar el token de acceso generado para poder preguntarle a Facebook?

¡Gracias!

Nota: para el sistema de autenticación mi aplicación usa código basado en el proyecto de muestra vinculado en esta respuesta SO: https://stackoverflow.com/a/18423474/4574

Para recuperar información adicional de Facebook, puede especificar los ámbitos que desea incluir cuando configure las opciones de autenticación de Facebook. Obtener la información adicional que se recupera puede lograrse implementando el método OnAuthenticated del proveedor como este:

var facebookOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions() { Provider = new FacebookAuthenticationProvider() { OnAuthenticated = (context) => { // All data from facebook in this object. var rawUserObjectFromFacebookAsJson = context.User; // Only some of the basic details from facebook // like id, username, email etc are added as claims. // But you can retrieve any other details from this // raw Json object from facebook and add it as claims here. // Subsequently adding a claim here will also send this claim // as part of the cookie set on the browser so you can retrieve // on every successive request. context.Identity.AddClaim(...); return Task.FromResult(0); } } }; //Way to specify additional scopes facebookOptions.Scope.Add("..."); app.UseFacebookAuthentication(facebookOptions); 

Según el código aquí, veo que el correo electrónico ya se recuperó y se agregó como reclamo aquí si Facebook lo envió. ¿No eres capaz de verlo?

Cree un nuevo objeto Microsoft.Owin.Security.Facebook.AuthenticationOptions en Startup.ConfigureAuth (StartupAuth.cs), pasándole el FacebookAppId, FacebookAppSecret y un nuevo AuthenticationProvider. Utilizará una expresión lambda para pasar el método OnAuthenticated a algún código para agregar Reclamos a la identidad que contenga los valores que extrae de context.Identity. Esto incluirá access_token por defecto. Debe agregar un correo electrónico al scope. Otras propiedades de usuario están disponibles desde context.User (ver el enlace en la parte inferior, por ejemplo).

StartUp.Auth.cs

 // Facebook : Create New App // https://dev.twitter.com/apps if (ConfigurationManager.AppSettings.Get("FacebookAppId").Length > 0) { var facebookOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions() { AppId = ConfigurationManager.AppSettings.Get("FacebookAppId"), AppSecret = ConfigurationManager.AppSettings.Get("FacebookAppSecret"), Provider = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationProvider() { OnAuthenticated = (context) => { context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:access_token", context.AccessToken, XmlSchemaString, "Facebook")); context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:email", context.Email, XmlSchemaString, "Facebook")); return Task.FromResult(0); } } }; facebookOptions.Scope.Add("email"); app.UseFacebookAuthentication(facebookOptions); } 

En AccountController, extraigo ClaimsIdentity del AuthenticationManager utilizando la cookie externa. Luego lo agrego a la identidad creada usando la cookie de la aplicación. Ignore cualquier reclamo que comience con “… schemas.xmlsoap.org/ws/2005/05/identity/claims” ya que pareció romper el inicio de sesión.

AccountController.cs

 private async Task SignInAsync(CustomUser user, bool isPersistent) { AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie); var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie); // Extracted the part that has been changed in SignInAsync for clarity. await SetExternalProperties(identity); AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity); } private async Task SetExternalProperties(ClaimsIdentity identity) { // get external claims captured in Startup.ConfigureAuth ClaimsIdentity ext = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie); if (ext != null) { var ignoreClaim = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims"; // add external claims to identity foreach (var c in ext.Claims) { if (!c.Type.StartsWith(ignoreClaim)) if (!identity.HasClaim(c.Type, c.Value)) identity.AddClaim(c); } } } 

Y, por último, quiero mostrar los valores que no sean de la AUTORIDAD LOCAL. Creé una vista parcial _ExternalUserPropertiesListPartial que aparece en la página / Account / Manage . Obtengo los reclamos que guardé previamente de AuthenticationManager.User.Claims y luego los paso a la vista.

AccountController.cs

 [ChildActionOnly] public ActionResult ExternalUserPropertiesList() { var extList = GetExternalProperties(); return (ActionResult)PartialView("_ExternalUserPropertiesListPartial", extList); } private List GetExternalProperties() { var claimlist = from claims in AuthenticationManager.User.Claims where claims.Issuer != "LOCAL AUTHORITY" select new ExtPropertyViewModel { Issuer = claims.Issuer, Type = claims.Type, Value = claims.Value }; return claimlist.ToList(); } 

Y solo para ser completo, la vista:

_ExternalUserPropertiesListPartial.cshtml

 @model IEnumerable @if (Model != null) { External User Properties  @foreach (var claim in Model) {  } 
@claim.Issuer @claim.Type @claim.Value
}

El ejemplo de trabajo y el código completo están en GitHub: https://github.com/johndpalm/IdentityUserPropertiesSample

Y cualquier comentario, corrección o mejora sería apreciado.

Me funcionó con esto en Startup.Auth .

 var facebookOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions() { AppId = "*", AppSecret = "**" }; facebookOptions.Scope.Add("email"); app.UseFacebookAuthentication(facebookOptions); 

Y luego en el método ExternalLoginCallback o ExternalLoginConfirmation obtienes el correo electrónico con:

 ClaimsIdentity ext = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie); var email = ext.Claims.First(x => x.Type.Contains("emailaddress")).Value; 

Necesita crear una instancia de FacebookAuthenticationOptions y configurar el Provider . El Provider contiene un evento llamado OnAuthenticated que se activa cuando inicia sesión.

 var facebookOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions { Provider = new FacebookAuthenticationProvider() { OnAuthenticated = (context) => { context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:access_token", context.AccessToken, ClaimValueTypes.String, "Facebook")); return Task.FromResult(0); } }, // You can store these on AppSettings AppId = ConfigurationManager.AppSettings["facebook:AppId"], AppSecret = ConfigurationManager.AppSettings["facebook:AppSecret"] }; app.UseFacebookAuthentication(facebookOptions); 

En el código anterior, estoy accediendo al access_token por context.AccessToken y lo access_token a las Claims del usuario que inició sesión actualmente.

Para acceder a este valor más tarde, debe hacer esto:

 var owinContext = HttpContext.GetOwinContext(); var authentication = owinContext.Authentication; var user = autentication.User; var claim = (user.Identity as ClaimsIdentity).FindFirst("urn:facebook:access_token"); string accessToken; if (claim != null) accessToken = claim.Value; 

Para simplificar todo esto, puede crear un BaseController y hacer que todos sus Controllers hereden de él.

El código de BaseController sería:

 public class BaseController : Controller { public IOwinContext CurrentOwinContext { get { return HttpContext.GetOwinContext(); } } public IAuthenticationManager Authentication { get { return CurrentOwinContext.Authentication; } } public new ClaimsPrincipal User { get { return Authentication.User; } } public ClaimsIdentity Identity { get { return Authentication.User.Identity as ClaimsIdentity; } } public string FacebookAccessToken { get { var claim = Identity.FindFirst("urn:facebook:access_token"); if (claim == null) return null; return claim.Value; } } } 

Luego, para obtener el token de acceso en su código, solo necesita acceder a la propiedad FacebookAccessToken .

 string accessToken = FacebookAccessToken; 

Es posible recuperar algunos otros valores como

 context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:username", context.User.Value("username"), ClaimValueTypes.String, "Facebook")); context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:name", context.User.Value("name"), ClaimValueTypes.String, "Facebook")); 

Tenga en cuenta que no todos los campos estarán disponibles, para recibir el correo electrónico debe requerir el correo electrónico del Scope .

 facebookOptions.Scope.Add("email"); 

A continuación, acceda al evento OnAuthenticated como

 context.User.Value("email"); 

Aquí hay algunos pasos que te ayudarán. Estoy en el proceso de escribir una publicación de blog, pero llevará un tiempo … – Agregar ámbitos en proveedor de Fb y agregar los datos devueltos de FB como reclamos

 app.UseFacebookAuthentication(new FacebookAuthenticationOptions() { AppId = "", AppSecret = "", //Scope = "email,user_about_me,user_hometown,friends_about_me,friends_photos", Provider = new FacebookAuthenticationProvider() { OnAuthenticated = async context => { foreach (var x in context.User) { context.Identity.AddClaim(new System.Security.Claims.Claim(x.Key, x.Value.ToString())); } //Get the access token from FB and store it in the database and use FacebookC# SDK to get more information about the user context.Identity.AddClaim(new System.Security.Claims.Claim("FacebookAccessToken", context.AccessToken)); } }, SignInAsAuthenticationType = "External", }); 
  • Utilice el token de acceso y llame a Facebook C # SDK para obtener la lista de amigos para el usuario

      var claimsIdentity = HttpContext.User.Identity as ClaimsIdentity; var access_token = claimsIdentity.FindAll("FacebookAccessToken").First().Value; var fb = new FacebookClient(access_token); dynamic myInfo = fb.Get("/me/friends"); var friendsList = new List(); foreach (dynamic friend in myInfo.data) { friendsList.Add(new FacebookViewModel() { Name = friend.name, ImageURL = @"https://graph.facebook.com/" + friend.id + "/picture?type=large" }); //Response.Write("Name: " + friend.name + "
    Facebook id: " + friend.id + "

    "); }

mi pareja tiene todas las respuestas … si todavía quieres preguntarte a Facebook, tiene sentido echarle un vistazo al paquete de Facebook ya existente. Proporciona una gran funcionalidad que ya se implementó, por lo que no tiene que volver a implementarla usted mismo … algún ejemplo de cómo usarlo dentro de la aplicación ASP.NET MVC que puede encontrar aquí .