我已经发现了一些ASP.NET Core几天,并想尝试通过LinkedIn实现身份验证 .
我在网上找到的大多数教程都使用了MVC,这是一个无缝的过程,但我想做一个纯API .
我've got something working, but I am not sure this is secure (I'以一种可能不安全的方式打了一些问题 .


应用程序的上下文:

  • 我正在使用基于JWT Bearer令牌的授权开发Web API,这意味着我没有任何基于cookie的身份验证

  • SPA(React app)将与API进行交互

  • 我正在尝试在服务器上实现大多数外部登录逻辑,以简化SPA的开发

当我配置身份时,我使用 services.AddIdentityCore<ApplicationUser>() 而不是 AddIdentity<> 来避免添加cookie身份验证方案,然后使用 service.AddAuthentication(...).AddJwtBearer(...) .
我使用电子邮件/密码进行注册/登录,返回JWT令牌并使用它们来保护对API方法的访问,这很有效 .
到现在为止还挺好 .

我遇到的问题来自 .AddLinkedIn() (来自aspnet-contrib providers) .
我添加了路径 /login/external?provider=LinkedIn ,其唯一的角色是(?)

public IActionResult ExternalLogin(string provider)
{
    return Challenge(provider);
}

将用户重定向到提供程序登录页面 . 这是我做的第一件事,我认为可能是一个肮脏/不安全的修复,但它的工作原理:我在LinkedIn登录页面上重定向,然后在我的回调页面上(在这种情况下: /login/linkedin?code= ) .

's where something I don' t理解发生: AspNet.Security.OAuth.LinkedIn.LinkedInAuthenticationHandler 被调用,可能基于回调路径,并验证一切 . 它也尝试登录,但这不起作用,因为我没有配置SignIn方案(JWT Bearer只允许Authenticate和Challenge方案) .
更糟糕的是,当我在第一次尝试时使用 AddIdentity<> 时,我得到了一个回调循环:

info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/1.1 GET https://localhost:5001/login/external?provider=LinkedIn  
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
      Route matched with {action = "ExternalLogin", controller = "Login"}. Executing action RestAPI.Controllers.LoginController.ExternalLogin (RestAPI)
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
      Executing action method RestAPI.Controllers.LoginController.ExternalLogin (RestAPI) with arguments (LinkedIn) - Validation state: Valid
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
      Executed action method RestAPI.Controllers.LoginController.ExternalLogin (RestAPI), returned result Microsoft.AspNetCore.Mvc.ChallengeResult in 0.2266ms.
info: Microsoft.AspNetCore.Mvc.ChallengeResult[1]
      Executing ChallengeResult with authentication schemes (LinkedIn).
info: AspNet.Security.OAuth.LinkedIn.LinkedInAuthenticationHandler[12]
      AuthenticationScheme: LinkedIn was challenged.
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
      Executed action RestAPI.Controllers.LoginController.ExternalLogin (RestAPI) in 248.7148ms
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
      Request finished in 805.406ms 302 
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/1.1 GET https://localhost:5001/login/linkedin?code=xxx&state=xxx  
info: Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler[10]
      AuthenticationScheme: Identity.External signed in.
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
      Request finished in 1142.4957ms 302 
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/1.1 GET https://localhost:5001/login/external?provider=LinkedIn  
...
...
...
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/1.1 GET https://localhost:5001/login/external?provider=LinkedIn  
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
      Route matched with {action = "ExternalLogin", controller = "Login"}. Executing action RestAPI.Controllers.LoginController.ExternalLogin (RestAPI)
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
      Executing action method RestAPI.Controllers.LoginController.ExternalLogin (RestAPI) with arguments (LinkedIn) - Validation state: Valid
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
      Executed action method RestAPI.Controllers.LoginController.ExternalLogin (RestAPI), returned result Microsoft.AspNetCore.Mvc.ChallengeResult in 0.0104ms.
info: Microsoft.AspNetCore.Mvc.ChallengeResult[1]
      Executing ChallengeResult with authentication schemes (LinkedIn).
info: AspNet.Security.OAuth.LinkedIn.LinkedInAuthenticationHandler[12]
      AuthenticationScheme: LinkedIn was challenged.
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
      Executed action RestAPI.Controllers.LoginController.ExternalLogin (RestAPI) in 2.5893ms
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
      Request finished in 4.3076ms 302 
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/1.1 GET https://localhost:5001/login/linkedin?code=xxx&state=xxx  
info: Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler[10]
      AuthenticationScheme: Identity.External signed in.
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
      Request finished in 716.1161ms 302 
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/1.1 GET https://localhost:5001/login/linkedin?code=xxx&state=xxx  
warn: AspNet.Security.OAuth.LinkedIn.LinkedInAuthenticationHandler[15]
      '.AspNetCore.Correlation.LinkedIn.OH3a8tiReVSVrkJ9eEUA0eA-W6UDfSJRoO7GyoLLNCo' cookie not found.
info: AspNet.Security.OAuth.LinkedIn.LinkedInAuthenticationHandler[4]
      Error from RemoteAuthentication: Correlation failed..
fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
      An unhandled exception has occurred while executing the request.
System.Exception: An error was encountered while handling the remote login. ---> System.Exception: Correlation failed.
   --- End of inner exception stack trace ---
   at Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler`1.HandleRequestAsync()
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.MigrationsEndPointMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
      Request finished in 245.3085ms 500 text/html; charset=utf-8

CookieAuthenticationHandler 将用户重定向到用户原始URI,在那里他被挑战重定向(我想,仍然不确定为什么 . 我还试图检查用户是否在 ExternalLogin 动作中进行身份验证,以避免在用户时向用户提出质疑继续这个页面,但似乎并非如此) .

为了解决这两个问题,我在收到新票证时通过在上下文中请求 SkipHandler 来禁用AuthenticationHandler中的SignIn阶段(当AuthenticationHandler验证LinkedIn的验证成功时完成):

.AddLinkedIn(options =>
{
    options.ClientId = Configuration["authentication:linked-in:client-id"];
    options.ClientSecret = Configuration["authentication:linked-in:client-secret"];
    options.CallbackPath = new Microsoft.AspNetCore.Http.PathString("/login/linkedin");
    options.Events.OnTicketReceived = context =>
    {
        context.HttpContext.User = context.Principal;
        context.SkipHandler();
        return Task.CompletedTask;
    };
});

我还将上下文中的 Principal 声明附加到 HttpContext.User ,因为我无法访问 /login/linkedin 的操作中的声明,否则我需要访问用户的LinkedIn ID .

然后 /login/linkedin 操作是由登录提供者使用 UserManager.FindByLoginAsync 方法查找用户的问题,如果我找不到用户,创建它,最终生成JWT,就像我为标准(带密码)用户做的那样 .
我最终将用户重定向到SPA的URI(类似于: /external-login-callback#auth_token=XXX&refresh_token=XXX ),以便SPA可以将令牌存储在本地存储中并使用它来验证后续请求 .

与此方法相关的第二个问题是,我似乎没有存储在任何地方,或者我可能不知道在哪里查看),因此我无法为此外部登录提供程序创建 IdentityUserToken (尽管这似乎不是是一个问题) .


所以最后,我确实有一些工作,我没有看到任何问题 .
但是,我是Web API的新手,我真的不确定这是一个有效/安全的方法 .


我的问题是:

  • 首先,可以通过API进行外部身份验证吗?或者它应该只是SPA的角色将用户重定向到正确的URL?
    我见过很多人说它不是要求的API,而是它应该是SPA .

  • 我是否泄漏任何敏感信息?
    攻击者是否有可能伪造用户请求并访问我的应用程序,因为这样做的方式?

  • 如何从LinkedIn获得与用户相关的 access_token
    鉴于 LinkedInAuthenticationHandler 已经向LinkedIn API请求获取用户ID /电子邮件,该令牌会有什么用处?
    是否需要存储它以验证用户是否未从LinkedIn中允许的服务中删除我的应用程序?


希望这很清楚,我会犹豫要求精确度 .
如果你有勇气阅读所有内容(即使你没有!),谢谢!