ASP.NET Web API 和使用 Facebook 登录的身份 [英] ASP.NET Web API and Identity with Facebook login
问题描述
在 ASP.NET Identity 的 Facebook 身份验证流程中,Facebook OAuth 对话框将代码而不是访问令牌附加到 redirect_url
以便服务器可以通过例如:
http://localhost:49164/signin-facebook?code=...&state=...
我的问题是我的客户端是一个使用 Facebook SDK 的移动应用程序,它立即给了我一个访问令牌.Facebook 说使用 SDK 总是会给你一个访问令牌,所以我可以直接把它提供给 ASP.NET Web API 吗?
我知道这不是很安全,但它甚至可能吗?
我不知道您是否找到了解决方案,但我正在尝试做类似的事情,而且我仍在拼凑拼图的各个部分.我曾尝试将此作为评论而不是答案发布,因为我没有提供真正的解决方案,但它太长了.
显然所有 WebAPI Owin OAuth 选项都是基于浏览器的——也就是说,它们需要大量浏览器重定向请求,这些请求不适合本机移动应用程序(如我的情况所需要).我仍在调查和试验,但作为 孙红叶在他的博文评论中简要描述,使用Facebook SDK登录Facebook,可以直接通过API验证访问令牌通过对 /me
端点进行图形调用.
通过使用图形调用返回的信息,您可以检查用户是否已经注册.最后,我们需要让用户登录,可能使用 Owin 的 Authentication.SignIn
方法,返回一个不记名令牌,该令牌将用于所有后续 API 调用.
实际上,我弄错了.不记名令牌是在调用 /Token
端点时发出的,它在输入时接受如下内容:
grant_type=password&username=Alice&password=password123
这里的问题是我们没有密码——这是 OAuth 机制的重点——那么我们还能如何调用 /Token
端点?
更新:我终于找到了一个可行的解决方案,以下是我必须添加到现有类中才能使其工作的内容:
Startup.Auth.cs
公共部分类启动{///<总结>///这部分已被添加到有一个 API 端点来验证接受 Facebook 访问令牌的用户///</总结>静态启动(){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 };返回用户经理;};OAuthOptions = 新的 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;}公共静态 OAuthBearerAuthenticationOptions OAuthBearerOptions { get;私人订制;}公共静态 OAuthAuthorizationServerOptions OAuthOptions { get;私人订制;}public static Func>UserManagerFactory { 获取;放;}公共静态字符串 PublicClientId { 获取;私人订制;}//有关配置身份验证的详细信息,请访问 http://go.microsoft.com/fwlink/?LinkId=301864public void ConfigureAuth(IAppBuilder app){[初始样板代码]OAuthBearerAuthenticationExtensions.UseOAuthBearerAuthentication(app, OAuthBearerOptions);[更多样板代码]}}公共类 CustomBearerAuthenticationProvider : OAuthBearerAuthenticationProvider{公共覆盖任务 ValidateIdentity(OAuthValidateIdentityContext 上下文){var claim = context.Ticket.Identity.Claims;if (claims.Count() == 0 || claim.Any(claim => claim.Issuer != "Facebook" && claim.Issuer != "LOCAL_AUTHORITY" ))context.Rejected();return Task.FromResult
在 AccountController
中,我添加了以下操作:
[HttpPost][允许匿名][路线(Facebook登录")]公共异步任务FacebookLogin(字符串令牌){[验证输入的代码...]var tokenExpirationTimeSpan = TimeSpan.FromDays(14);ApplicationUser 用户 = null;//获取 fb 访问令牌并对/me 端点进行图形调用//检查用户是否已经注册//如果是,则检索用户//如果没有,注册它//最后登录用户:这是创建不记名令牌和验证用户的代码的关键部分var identity = new ClaimsIdentity(Startup.OAuthBearerOptions.AuthenticationType);identity.AddClaim(new Claim(ClaimTypes.Name, user.Id, null, "Facebook"));//此声明用于正确填充用户 IDidentity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id, null, "LOCAL_AUTHORITY"));AuthenticationTicket 票证 = 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(身份);//创建响应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);//返回确定返回 Ok(blob);}
就是这样!我发现与经典 /Token
端点响应的唯一区别是不记名令牌略短,到期和发布日期采用 UTC 而不是 GMT(至少在我的机器上).>
我希望这会有所帮助!
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 server can exchange this code for an access token via e.g.:
http://localhost:49164/signin-facebook?code=…&state=…
My problem is that my client is a mobile app which uses the Facebook SDK, and that straight away gives me an access token. Facebook says using the SDK always gives you an access token, so can I just give this directly to ASP.NET Web API?
I understand this is not very secure, but is it even possible?
I don't know if you ever found a solution, but I'm trying to do something similar and I'm still putting the pieces of the puzzle together. I had tried to post this as a comment instead of an answer, as I do not provide a real solution, but it's too long.
Apparently all of the WebAPI Owin OAuth options are browser based—that is, they require lots of browser redirect requests that do not fit a native mobile app (as required for my case). I'm still investigating and experimenting, but as briefly described by Hongye Sun in a comment to his blog post, to login with Facebook the access token received using the Facebook SDK can be verified directly via the API by making a graph call to the /me
endpoint.
By using the information returned by the graph call, you can then check if the user is already registered or not. At the end, we need to sign-in the user, maybe using Owin's Authentication.SignIn
method, returning a bearer token that will be used for all subsequent API calls.
EDIT: Actually, I got it wrong. The bearer token is issued on calling /Token
endpoint, which on input accepts something like:
grant_type=password&username=Alice&password=password123
The problem here is that we do not have a password—that's the whole point of the OAuth mechanism—so how else can we invoke the /Token
endpoint?
UPDATE: I finally found a working solution and the following 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);
}
}
And 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);
}
That's it! The only difference I found with the classic /Token
endpoint response is that the bearer token is slightly shorter and the expiration and issue dates are in UTC instead that in GMT (at least on my machine).
I hope this helps!
这篇关于ASP.NET Web API 和使用 Facebook 登录的身份的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!