ASP.NET Web API 和使用 Facebook 登录的身份 [英] ASP.NET Web API and Identity with Facebook login

查看:24
本文介绍了ASP.NET Web API 和使用 Facebook 登录的身份的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆