我已经发现了一些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中允许的服务中删除我的应用程序?
希望这很清楚,我会犹豫要求精确度 .
如果你有勇气阅读所有内容(即使你没有!),谢谢!