google - WebApi ASP.NET Identity Inicio de sesión de Facebook
login with facebook asp (3)
No sé si finalmente encontraste una solución, pero estoy tratando de hacer algo muy similar y todavía estoy juntando las piezas del rompecabezas. Intenté publicar esto como un comentario en lugar de una respuesta, ya que no ofrezco una solución real, pero es demasiado larga.
Aparentemente, todas las opciones de OAP de WebAPI Owin están basadas en el navegador, es decir, requieren muchas solicitudes de redirecciones del navegador que no se ajustan a una aplicación móvil nativa (mi caso). Todavía estoy investigando y experimentando, pero brevemente descrito por Hongye Sun en uno de los comentarios a su publicación en el blog, http://blogs.msdn.com/b/webdev/archive/2013/09/20/understanding-security-features-in-spa-template.aspx?PageIndex=2#comments , para iniciar sesión con Facebook, el token de acceso recibido mediante Facebook SDK puede ser verificado directamente por la API realizando una llamada de gráficos al / me endpoint.
Al usar la información devuelta por la llamada de gráfico, puede verificar si el usuario ya está registrado o no. Al final, necesitamos iniciar sesión en el usuario, tal vez usando el método Authentication.SignIn Owin, que devuelve un token de portador que se utilizará para todas las llamadas API posteriores.
EDITAR: En realidad lo entendí mal, el token portador se emite al llamar al punto final "/ Token", que en la entrada acepta algo como grant_type=password&username=Alice&password=password123
El problema aquí es que no tenemos una contraseña (ese es el punto del mecanismo OAuth), entonces, ¿de qué otra manera podemos invocar el punto final "/ Token"?
ACTUALIZACIÓN: finalmente encontré una solución de trabajo y lo siguiente fue lo que tuve que agregar a las clases existentes para que funcione: 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);
}
}
En AccountController, agregué la siguiente acción
[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);
}
Eso es. La única diferencia que encontré con la respuesta de punto final clásico / Token es que el token de portador es un poco más corto y las fechas de vencimiento y emisión están en UTC en lugar de en GMT (al menos en mi máquina).
¡Espero que esto ayude!
En el flujo de autenticación de Facebook para la identidad de asp.net, el diálogo de Facebook oauth agrega un código en lugar de token de acceso a redirect_url para que el servidor pueda intercambiar este código por un token de acceso a través de http://localhost:49164/signin-facebook?code=...&state=...
Mi problema es que mi cliente es una aplicación móvil que usa el sdk de Facebook y eso inmediatamente me da un token de acceso. Facebook dice que usar el SDK siempre te da un token de acceso, así que ¿puedo darle de inmediato la API del token de acceso?
Entiendo que esto no es muy seguro, pero ¿es posible?
Sí, puede usar un token de acceso externo para iniciar sesión de forma segura.
Le recomiendo que siga este tutorial , que le muestra cómo hacer la autenticación basada en tokens con Web API 2 desde cero (usando Angular JS como front-end). En particular, el paso 4 incluye dos métodos que le permiten autenticarse utilizando un token de acceso externo, por ejemplo, como se devuelve desde un SDK nativo:
[AllowAnonymous, HttpGet]
async Task<IHttpActionResult> ObtainLocalAccessToken(string provider, string externalAccessToken)
[AllowAnonymous, HttpPost]
async Task<IHttpActionResult> RegisterExternal(RegisterExternalBindingModel model)
En una palabra:
Use SDK nativo para obtener token de acceso externo.
Llame a
ObtainLocalAccessToken("Facebook", "[fb-access-token]")
para determinar si el usuario ya tiene una cuenta (200 respuestas), en cuyo caso se generará un nuevo token local para usted. También verifica que el token de acceso externo sea legítimo.Si la llamada en el paso 2 falló (400 respuestas), necesita registrar una nueva cuenta llamando a
RegisterExternal
, pasando el token externo. El tutorial anterior tiene un buen ejemplo de esto (ver associateController.js ).
Seguido de la gran solución de @ s0nica, modifiqué algunos códigos para integrar con la plantilla MVC ASP.NET actualmente implementada. El enfoque de s0nica es bueno, pero no es totalmente compatible con MVC (Non-WebApi) AccountController
.
El beneficio de mi enfoque es trabajar con ASP.NET MVC y WebApi, viceversa.
Las principales diferencias es el nombre de la reclamación. Como nombre de reclamo se utiliza FacebookAccessToken
, seguido del enlace ( http://blogs.msdn.com/b/webdev/archive/2013/10/16/get-more-information-from-social-providers-used-in-the-vs-2013-project-templates.aspx ), mi enfoque es compatible con el enfoque de un enlace dado. Recomiendo usar con eso.
Tenga en cuenta que los códigos a continuación son una versión modificada de la respuesta de @ s0nica. Por lo tanto, (1) el enlace dado paso a paso, (2) y luego el código de s0nica, (3) y finalmente considerar el mío después.
Startup.Auth.cs archivo.
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 (clase interna de 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; }
}
Método VerifyFacebookAccessToken (en 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;
}