在ASP.NET WebApi 2

时间:2015-11-16 01:52:38

标签: c# asp.net facebook asp.net-web-api login

我正在尝试构建一个API(使用ASP.NET WebApi),该API将由学校项目的本机移动应用程序使用。 (我不关心/开发移动应用程序,这个责任落在另一个成员身上) 我正处于需要实现基于令牌的Facebook登录的地步。有很多教程可用于如何为基于浏览器的应用程序实现此功能(这是非常直接的,大部分都是内置的),但我不认为我会遵循如何使用本机应用程序。我不明白重定向是如何工作的?

根据这个link,我的服务器无需专门处理任何事情。我不认为我明白这是如何工作的?如何处理来自Facebook的令牌?

此外,我应该实现令牌处理的哪个部分,我无法找到WebApi外部登录身份验证的良好文档。

无论如何,如果有人能指出我发生的令牌交换的确切流程以及ASP.NET默认实现的内容,那将是非常有用的。

此外,对我来说最大的困惑是我不明白Facebook将返回的令牌将如何处理。

  1. 我认为令牌将返回给客户端(移动应用),如何在我的服务器上访问它?
  2. 如何从facebook的令牌创建本地令牌? 这一切都是由ASP.NET内部/自动魔术完成的吗?
  3. 如果这是我应该弄明白的话,我很抱歉。我做了很多研究,发现自己陷入(相关和无关)信息中。我认为我甚至不知道如何搜索我需要的信息。

    我读过一些链接:

    Claims And Token Based Authentication (ASP.NET Web API)

    Token Based Authentication using ASP.NET Web API 2, Owin, and Identity

    ASP.NET Web API 2 external logins with Facebook and Google in AngularJS app

2 个答案:

答案 0 :(得分:11)

对于我正在处理的应用程序,我必须做同样的事情。我也很难找到有关它的信息。似乎我发现的一切都接近我所需要的,但不完全是解决方案。我最终从一堆不同的博客文章,文章等中汲取了点点滴滴,并将它们放在一起以使其发挥作用。

我记得你发布的两个链接"声明和基于令牌的身份验证"和#34; ASP.NET Web API 2在AngularJS应用程序中使用Facebook和Google进行外部登录"作为那些有用信息的人。

我不能给你一个全面的答案,因为我不记得我必须做的一切,我甚至不理解我当时所做的一切,但我可​​以给你一般的想法。你走在正确的轨道上。

基本上我最终使用Facebook授予的令牌来确认他们已登录到他们的Facebook帐户,根据他们的Facebook用户ID创建用户,并授予他们可用于访问我的API的我自己的持票人令牌。

流程看起来像这样:

  1. 客户端通过Facebook以任何方式验证(我们使用oauth.io
    • Facebook为他们返回一个令牌
  2. 客户端将令牌信息发送到我的WebApi控制器的注册端点
    • 使用Facebook的Graph API验证令牌,该API返回用户信息
    • 通过ASP.NET身份在数据库中创建用户,其Facebook用户ID为密钥
  3. 客户端将令牌信息发送到WebApi控制器的身份验证端点
    • 使用Facebook的Graph API验证令牌,该API返回用户信息
    • 用户信息用于在数据库中查找用户,确认他们之前已注册
    • ASP.NET Identity用于为该用户生成新令牌
    • 该令牌将返回给客户
  4. 客户端在所有未来的HTTP请求中都包含一个Authorization标头,其中包含我的服务授予的新令牌(例如"授权:承载 TOKEN ")
    • 如果WebApi端点具有[Authorize]属性,则ASP.NET Identity将自动验证持有者令牌,并在无效时拒绝访问
  5. 最终有很多自定义代码用于实现ASP.NET身份的OAuth内容,而您包含的那些链接会向您展示其中的一些内容。希望这些信息对你有所帮助,抱歉,我无法提供更多帮助。

答案 1 :(得分:0)

我关注了this article。流程基本上就是这个

  • 服务器具有与Web登录相同的facebook密钥
  • 该应用会要求提供可用的社交登录名并显示按钮(我想您可以对此进行硬编码)
  • 按下按钮后,应用程序会打开一个浏览器并将URL设置为与指定的社交登录相关的URL。然后,ASP.NET会将浏览器重定向到Facebook / google /具有相应质询的所有版本
  • 该用户可能已登录或未登录,并且可能已授予您的应用权限。在他授予权限后,facebook重定向回提供的回调URL
  • 此时,您可以从SignInManager获取外部登录信息,并检查用户是否已经存在以及是否应该创建新帐户
  • 最后,将生成令牌,并将浏览器重定向到放置令牌的URL。该应用程序从URL获取令牌,然后关闭浏览器。使用令牌进行API请求。

老实说,我不知道这种方法是否合法...

操作按钮的代码应重定向到:

public async Task<IEnumerable<ExternalLoginDto>> GetExternalLogins(string returnUrl, bool generateState = false)
{
    IEnumerable<AuthenticationScheme> loginProviders = await SignInManager.GetExternalAuthenticationSchemesAsync();
    var logins = new List<ExternalLoginDto>();

    string state;

    if (generateState)
    {
        const int strengthInBits = 256;
        state = RandomOAuthStateGenerator.Generate(strengthInBits);
    }
    else
    {
        state = null;
    }

    foreach (AuthenticationScheme authenticationScheme in loginProviders)
    {
        var routeValues = new
        {
            provider = authenticationScheme.Name,
            response_type = "token",
            client_id = Configuration["Jwt:Issuer"],
            redirect_uri = $"{Request.Scheme}//{Request.Host}{returnUrl}",
            state = state
        };

        var login = new ExternalLoginDto
        {
            Name = authenticationScheme.DisplayName,
            Url = Url.RouteUrl("ExternalLogin", routeValues),
            State = state
        };

        logins.Add(login);
    }

    return logins;
}

回调操作的代码:

[Authorize(AuthenticationSchemes = "Identity.External")]
[Route("ExternalLogin", Name = "ExternalLogin")]
public async Task<IActionResult> GetExternalLogin(string provider, string state = null, string client_id = null, string error = null)
{
    if (error != null)
    {
        ThrowBadRequest(error);
    }

    if (!User.Identity.IsAuthenticated)
    {
        return new ChallengeResult(provider);
    }

    string providerKey = User.FindFirstValue(ClaimTypes.NameIdentifier);

    var externalLoginInfo = new ExternalLoginInfo(User, User.Identity.AuthenticationType, providerKey, User.Identity.AuthenticationType);

    if (externalLoginInfo.LoginProvider != provider)
    {
        await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
        return new ChallengeResult(provider);
    }

    var userLoginInfo = new UserLoginInfo(externalLoginInfo.LoginProvider, externalLoginInfo.ProviderKey, externalLoginInfo.ProviderDisplayName);
    User user = await UserManager.FindByLoginAsync(externalLoginInfo.LoginProvider, externalLoginInfo.ProviderKey);

    if (client_id != Configuration["Jwt:Issuer"])
    {
        return Redirect($"/#error=invalid_client_id_{client_id}");
    }

    if (user != null)
    {
        return await LoginWithLocalUser(user, state);
    }
    else
    {
        string email = null;
        string firstName = null;
        string lastName = null;

        IEnumerable<Claim> claims = externalLoginInfo.Principal.Claims;
        if (externalLoginInfo.LoginProvider == "Google")
        {
            email = claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value;
            firstName = claims.FirstOrDefault(c => c.Type == ClaimTypes.GivenName)?.Value;
            lastName = claims.FirstOrDefault(c => c.Type == ClaimTypes.Surname)?.Value;
        }
        else if (externalLoginInfo.LoginProvider == "Facebook")
        {
            email = claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value;

            string[] nameParts = claims.First(c => c.Type == ClaimTypes.Name)?.Value.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            firstName = nameParts?.First();
            lastName = nameParts?.Last();
        }

        //some fallback just in case
        firstName ??= externalLoginInfo.Principal.Identity.Name;
        lastName ??= externalLoginInfo.Principal.Identity.Name;

        user = new User
        {
            UserName = email,
            Email = email,
            FirstName = firstName,
            LastName = lastName,
            EmailConfirmed = true //if the user logs in with Facebook consider the e-mail confirmed
        };

        IdentityResult userCreationResult = await UserManager.CreateAsync(user);
        if (userCreationResult.Succeeded)
        {
            userCreationResult = await UserManager.AddLoginAsync(user, userLoginInfo);
            if (userCreationResult.Succeeded)
            {
                return await LoginWithLocalUser(user, state);
            }
        }

        string identityErrrors = String.Join(" ", userCreationResult.Errors.Select(ie => ie.Description));
        Logger.LogWarning($"Error registering user with external login. Email:{email}, Errors:" + Environment.NewLine + identityErrrors);
        return Redirect($"/#error={identityErrrors}");
    }
}

private async Task<RedirectResult> LoginWithLocalUser(User user, string state)
{
    await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);

    DateTime expirationDate = DateTime.UtcNow.AddDays(365);

    string token = user.GenerateJwtToken(Configuration["Jwt:Key"], Configuration["Jwt:Issuer"], expirationDate);
    return Redirect($"/#access_token={token}&token_type=bearer&expires_in={(int)(expirationDate - DateTime.UtcNow).TotalSeconds}&state={state}");
}