Laravel 5.6 - 用于自消费 API 的 Passport JWT httponly cookie SPA 身份验证?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/53678019/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Laravel 5.6 - Passport JWT httponly cookie SPA authentication for self consuming API?
提问by Wonka
NOTE: I had 4 bounties on this question, but non of the upvoted answers below are the answer needed for this question. Everything needed is in Update 3 below, just looking for Laravel code to implement.
注意:我在这个问题上有 4 个赏金,但下面的赞成答案中没有一个是这个问题所需的答案。所需的一切都在下面的更新 3 中,只需寻找 Laravel 代码即可实现。
UPDATE 3: This flow chart is exactlythe flow I am trying to accomplish, everything below is the original question with some older updates. This flow chart sums up everything needed.
更新 3:这个流程图正是我想要完成的流程,下面的所有内容都是带有一些较旧更新的原始问题。此流程图总结了所需的一切。
The green parts in the flow chart below are the parts that I know how to do. The red parts along with their side notes is what I am looking for help accomplishing using Laravel code.
下面流程图中的绿色部分是我知道怎么做的部分。红色部分及其旁注是我正在寻找使用 Laravel 代码完成的帮助。
I have done a lot of research but the information always ended up short and not complete when it comes to using Laravel with a JWT httponly cookie for a self consuming API (most tutorials online only show JWT being stored in local storage which is not very secure). It looks like httponly cookie containing a JWT by Passport should be used to identify the user on the Javascript side when sent with every request to the server to validate that the user is who they say they are.
我已经做了很多研究,但是在将 Laravel 与 JWT httponly cookie 用于自消费 API 时,信息总是很短而且不完整(大多数在线教程只显示 JWT 存储在本地存储中,这不是很安全)。看起来像包含 Passport 的 JWT 的 httponly cookie 应该用于在 Javascript 端识别用户,当与每个请求一起发送到服务器以验证用户是他们所说的人时。
There are also some additional things that are needed to have a complete picture of how to make this setup work which I haven't come across in a single tutorial which covers this:
还需要一些额外的东西来全面了解如何使此设置工作,我在涵盖此内容的单个教程中没有遇到过这些内容:
- Laravel Passport (not tymon auth) to generate encrypted JWT and send it as httponly cookie as response after login from JS side. What middleware to use? If refresh tokens add more security, how to implement?
- JavaScript (axios for example) api pseudo code that makes call to auth endpoint, how is httponly cookie passed to backend, and how does backend verify token is valid.
- If single account is logged in from multiple devices, then a device is stolen, how to revoke access from all the authed user devices (assuming user does Change Password from a logged in device they have control over)?
- What would Login/Register, Logout, Change Password, Forgot Password controller methods typically look like to handle the creation/validating/revoking of tokens?
- CSRF token integration.
- Laravel Passport(不是 tymon auth)生成加密的 JWT,并在从 JS 端登录后将其作为 httponly cookie 作为响应发送。使用什么中间件?如果刷新令牌增加了更多的安全性,如何实现?
- 调用 auth 端点的 JavaScript(例如 axios)api 伪代码,httponly cookie 如何传递给后端,以及后端如何验证令牌是否有效。
- 如果单个帐户从多个设备登录,则设备被盗,如何撤销所有已授权用户设备的访问权限(假设用户从他们控制的登录设备更改密码)?
- 登录/注册、注销、更改密码、忘记密码控制器方法通常会如何处理令牌的创建/验证/撤销?
- CSRF 令牌集成。
I hope an answer to this question serves as an easy to follow guide for future readers and those struggling at the moment to find an answer covering the above points on a self consuming API.
我希望这个问题的答案可以为未来的读者和那些目前正在努力寻找涵盖上述自消费 API 要点的答案的读者提供一个易于遵循的指南。
UPDATE 1:
更新 1:
- Please note I tried the
CreateFreshApiToken
before, but that didn't work when it comes to revoking tokens of the user (for points 3 and 4 above). This is based on this commentby a core laravel developer, when talking about theCreateFreshApiToken
middleware:
- 请注意,我
CreateFreshApiToken
之前尝试过,但是在撤销用户的令牌时不起作用(对于上面的第 3 点和第 4 点)。这是基于此评论的核心laravel开发商,谈论时CreateFreshApiToken
中间件:
JWT tokens created by this middleware aren't stored anywhere. They can't be revoked or "not exist". They simply provide a way for your api calls to be authed through the laravel_token cookie. It isn't related to access tokens. Also: you normally wouldn't use tokens issued by clients on the same app which issues them. You'd use them in a first or third party app. Either use the middleware or the client issued tokens but not both at the same time.
此中间件创建的 JWT 令牌不会存储在任何地方。它们不能被撤销或“不存在”。它们只是为您的 api 调用提供了一种通过 laravel_token cookie 进行身份验证的方法。它与访问令牌无关。另外:您通常不会在发布它们的同一应用程序上使用客户端发布的令牌。您会在第一方或第三方应用程序中使用它们。使用中间件或客户端颁发的令牌,但不能同时使用。
So it seems to be able to cater to points 3 and 4 to revoke tokens, it's not possible to do so if using the CreateFreshApiToken
middleware.
所以它似乎能够迎合第3点和第4点来撤销令牌,如果使用CreateFreshApiToken
中间件就不可能这样做。
- On the client side, it seems
Authorization: Bearer <token>
is not the way to go when dealing with the secure httpOnly cookie. I think the request/response are supposed to include the secure httpOnly cookie as a request/response header, like this based on the laravel docs:
- 在客户端,这似乎
Authorization: Bearer <token>
不是处理安全 httpOnly cookie 的方法。我认为请求/响应应该包含安全的 httpOnly cookie 作为请求/响应标头,就像这样基于 laravel 文档:
When using this method of authentication, the default Laravel JavaScript scaffolding instructs Axios to always send the X-CSRF-TOKEN and X-Requested-With headers.
使用这种身份验证方法时,默认的 Laravel JavaScript 脚手架会指示 Axios 始终发送 X-CSRF-TOKEN 和 X-Requested-With 标头。
headerswindow.axios.defaults.headers.common = {
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-TOKEN': (csrf_token goes here)
};
This is also the reason I am looking for a solution which covers all the points above. Apologies, I am using Laravel 5.6 not 5.5.
这也是我正在寻找涵盖上述所有要点的解决方案的原因。抱歉,我使用的是 Laravel 5.6 而不是 5.5。
UPDATE 2:
更新 2:
It seems the Password Grant/Refresh Token Grantcombo is the way to go. Looking for an easy to follow implementation guide using Password Grant/Refresh Token Grantcombo.
似乎密码授予/刷新令牌授予组合是要走的路。寻找使用密码授予/刷新令牌授予组合的易于遵循的实施指南。
Password Grant:This grant is suitable when dealing with the client that we trust, like a mobile app for our own website. In this case, the client sends the user's login credentials to the authorization server and the server directly issues the access token.
Refresh Token Grant:When the server issues an access token, it also sets an expiry for the access token. Refresh token grant is used when we want to refresh the access token once it is expired. In this case, authorization server will send a refresh token while issuing the access token, which can be used to request a new access token.
密码授予:此授予适用于与我们信任的客户打交道,例如我们自己网站的移动应用程序。在这种情况下,客户端将用户的登录凭据发送到授权服务器,服务器直接颁发访问令牌。
刷新令牌授予:当服务器发出访问令牌时,它还会为访问令牌设置一个到期时间。当我们想在访问令牌过期后刷新它时,会使用刷新令牌授权。在这种情况下,授权服务器将在发出访问令牌的同时发送刷新令牌,该令牌可用于请求新的访问令牌。
I am looking for an easy to implement, straight forward, holistic answer using the Password Grant/Refresh Token Grantcombo that covers all the parts of the above original 5 points with httpOnly secure cookie, creating/revoking/refreshing tokens, login cookie creation, logout cookie revoking, controller methods, CSRF, etc.
我正在寻找使用密码授予/刷新令牌授予组合的易于实施、直接、全面的答案,该组合涵盖了上述原始 5 点的所有部分,包括 httpOnly 安全 cookie、创建/撤销/刷新令牌、登录 cookie 创建,注销 cookie 撤销、控制器方法、CSRF 等。
回答by Mirceac21
Laravel Passport JWT
Laravel 护照 JWT
To use this feature you need to disable cookie serialization. Laravel 5.5 has an issue with serialization / unserialization of cookie values. You can read more about this here (https://laravel.com/docs/5.5/upgrade)
Make sure that
you have
<meta name="csrf-token" content="{{ csrf_token() }}">
in your blade template headaxios is set to use csrf_token on each request.
要使用此功能,您需要禁用 cookie 序列化。Laravel 5.5 在 cookie 值的序列化/反序列化方面存在问题。您可以在此处阅读更多相关信息 ( https://laravel.com/docs/5.5/upgrade)
确保
你有
<meta name="csrf-token" content="{{ csrf_token() }}">
你的刀片模板头axios 设置为在每个请求上使用 csrf_token。
You should have something like this in resources/assets/js/bootstrap.js
你应该有这样的东西 resources/assets/js/bootstrap.js
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
let token = document.head.querySelector('meta[name="csrf-token"]');
if (token) {
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}
- Setup auth routes explained here (https://laravel.com/docs/5.5/authentication)
- Setup passport explained here (https://laravel.com/docs/5.5/passport).
- 此处解释了设置身份验证路由(https://laravel.com/docs/5.5/authentication)
- 此处解释了设置通行证(https://laravel.com/docs/5.5/passport)。
Important parts are:
重要的部分是:
- add the
Laravel\Passport\HasApiTokens
trait to yourUser
model - set the
driver
option of theapi
authentication guard topassport
in yourconfig/auth.php
- add the
\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
middleware to yourweb
middleware group inapp/Http/Kernel.php
- 将
Laravel\Passport\HasApiTokens
特征添加到您的User
模型中 - 将身份验证保护的
driver
选项设置为您的api
passport
config/auth.php
- 将
\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
中间件添加到您的web
中间件组中app/Http/Kernel.php
Note you probably can skip migrations and creating clients.
请注意,您可能可以跳过迁移和创建客户端。
- Make a POST request to
/login
passing your credentials. You can make an AJAX request or normal form submit.
- 发出 POST 请求以
/login
传递您的凭据。您可以进行 AJAX 请求或普通表单提交。
If the login request is AJAX (using axios) the response data will be the HTML but what are you interested at is the status code.
如果登录请求是 AJAX(使用 axios),则响应数据将是 HTML,但您感兴趣的是状态代码。
axios.get(
'/login,
{
email: '[email protected]',
password: 'secret',
},
{
headers: {
'Accept': 'application/json', // set this header to get json validation errors.
},
},
).then(response => {
if (response.status === 200) {
// the cookie was set in browser
// the response.data will be HTML string but I don't think you are interested in that
}
// do something in this case
}).catch(error => {
if (error.response.status === 422) {
// error.response.data is an object containing validation errors
}
// do something in this case
});
On login, the server finds the user by credentials provided, generates a token based on user info (id, email ...) (this token is not saved anywhere) then the server returns a response with an encrypted cookie that contains the generated token.
登录时,服务器通过提供的凭据找到用户,根据用户信息(ID、电子邮件...)生成令牌(此令牌未保存在任何地方)然后服务器返回带有加密 cookie 的响应,其中包含生成的令牌.
- Make an API call to a protected route.
- 对受保护的路由进行 API 调用。
Assuming that you have a protected route
假设你有一个受保护的路由
Route::get('protected', 'SomeController@protected')->middleware('auth:api');
Route::get('protected', 'SomeController@protected')->middleware('auth:api');
You can make an ajax call using axios as normal. The cookies are automatically set.
您可以照常使用 axios 进行 ajax 调用。cookie 是自动设置的。
axios.get('/api/protected')
.then(response => {
// do something with the response
}).catch(error => {
// do something with this case of error
});
When the server receives the call decrypts the request laravel_cookie
and get user information (ex: id, email ...)
Then with that user info does a database lookup to check if the user exists.
If the user is found then the user is authorized to access the requested resource.
Else a 401 is returned.
当服务器收到调用解密请求laravel_cookie
并获取用户信息(例如:id、电子邮件...)然后使用该用户信息进行数据库查找以检查用户是否存在。如果找到该用户,则该用户被授权访问所请求的资源。否则返回 401。
Invalidating the JWT token. As you mention the comment there's no need to worry about this since this token is not saved anywhere on the server.
使 JWT 令牌无效。当您提到评论时,无需担心这一点,因为此令牌未保存在服务器上的任何位置。
Update
更新
Regarding point 3 Laravel 5.6 Auth has a new method logoutOtherDevices
. You can learn more from here (https://laracasts.com/series/whats-new-in-laravel-5-6/episodes/7)
since the documentation is very light.
关于第 3 点,Laravel 5.6 Auth 有一个新方法logoutOtherDevices
。您可以从这里 ( https://laracasts.com/series/whats-new-in-laravel-5-6/episodes/7)了解更多信息,因为文档非常简单。
If you can't update your Laravel version you can check it out how is done in 5.6 and build your own implementation for 5.5
如果您无法更新 Laravel 版本,您可以查看 5.6 中是如何完成的,并为 5.5 构建您自己的实现
Point 4 from your question. Take a look at controllers found in app/Http/Controllers/Auth
.
你的问题的第4点。看看在app/Http/Controllers/Auth
.
Regarding access_tokens and refresh_tokens this is a totally different and more complex approach. You can find lots of tutorials online explaining how to do it.
关于 access_tokens 和 refresh_tokens,这是一种完全不同且更复杂的方法。你可以在网上找到很多教程来解释如何做到这一点。
I hope it helps.
我希望它有帮助。
PS. Have a Happy New Year!! :)
附注。祝新年快乐!!:)
回答by Saptarshi Basu
I'll try to answer this in a generic way so that the answer is applicable across frameworks, implementations and languages because the answers to all the questions can be derived from the general protocol or algorithm specifications.
我将尝试以通用方式回答这个问题,以便答案适用于框架、实现和语言,因为所有问题的答案都可以从通用协议或算法规范中得出。
Which OAuth 2.0 grant type should I use?
我应该使用哪种 OAuth 2.0 授权类型?
This is the first thing to be decided. When it comes to SPA, the two possible options are:
这是首先要决定的。谈到 SPA,有两种可能的选择:
- Authorization code grant (recommended, provided the client secret is stored on the server side)
- Resource owner password credential grant
- 授权码授予(推荐,前提是客户端密钥存储在服务器端)
- 资源所有者密码凭据授予
The reasons I don't mention Implicit grant type as an option are:
我没有提到隐式授权类型作为选项的原因是:
- The client authentication step by providing client secret and authorization code is missing. So less security
- The access token is sent back as a URL fragment (so that the token doesn't go to the server) which will continue to stay in browser history
- If XSS attack occurs, the malicious script can very well send the token to a remote server in control of an attacker
- 缺少提供客户端密码和授权代码的客户端身份验证步骤。所以安全性较低
- 访问令牌作为 URL 片段发送回(这样令牌不会转到服务器),它将继续保留在浏览器历史记录中
- 如果发生 XSS 攻击,恶意脚本可以很好地将令牌发送到攻击者控制的远程服务器
(Client Credentials grant type is kept out of scope of this discussion as it is used when the client is not acting on behalf of a user. For e.g. a batch job)
(客户端凭据授予类型不在本讨论的范围内,因为它在客户端不代表用户行事时使用。例如批处理作业)
In case of Authorization Code grant type, the authorization server is usually a different server from the resource server. It is better to keep the authorization server separate and use it as a common authorization server for all SPA within the organization. This is always the recommended solution.
在授权代码授予类型的情况下,授权服务器通常是与资源服务器不同的服务器。最好将授权服务器分开,并将其用作组织内所有 SPA 的通用授权服务器。这始终是推荐的解决方案。
Here (in the authorization code grant type) the flow looks like below:
此处(在授权代码授予类型中)流程如下所示:
- the user clicks on the login button on the SPA landing page
- the user is redirected to the authorization server login page. The client id is provided in the URL query parameter
- The user enters his / her credentials and clicks on the login button. The username and password will be sent to the authorization server using HTTP POST. The credentials should be sent in the request body or header and NOT in the URL (as URLs are logged in browser history and application server). Also, the proper caching HTTP headers should be set, so that the credentials are not cached:
Cache-Control: no-cache, no-store
,Pragma: no-cache
,Expires: 0
- The authorization server authenticates the user against a user database (say, LDAP server) where the username and the hash of the user password (hashing algorithms like Argon2, PBKDF2, Bcrypt or Scrypt) is stored with a random salt
- On successful authentication, the authorization server would retrieve from its database the redirect URL against the provided client id in the URL query parameter. The redirect URL is the resource server URL
- The user will then be redirected to a resource server endpoint with a authorization code in the URL query parameter
- The resource server will then do an HTTP POST request to the authorization server for access token. The authorization code, client id, client secret should go in the request body. (Appropriate caching headers as above should be used)
- The authorization server would return the access token and the refresh token in response body or header (with the appropriate caching header as mentioned above)
- The resource server will now redirect the user (HTTP response code 302) to the SPA URL by setting appropriate cookies (to be explained in detail below)
- 用户点击 SPA 登陆页面上的登录按钮
- 用户被重定向到授权服务器登录页面。在 URL 查询参数中提供了客户端 ID
- 用户输入他/她的凭据并单击登录按钮。用户名和密码将使用 HTTP POST 发送到授权服务器。凭据应在请求正文或标头中发送,而不是在 URL 中发送(因为 URL 记录在浏览器历史记录和应用程序服务器中)。此外,应设置正确的缓存 HTTP 标头,以便不缓存凭据:
Cache-Control: no-cache, no-store
,Pragma: no-cache
,Expires: 0
- 授权服务器根据用户数据库(例如 LDAP 服务器)对用户进行身份验证,其中用户名和用户密码的散列(散列算法,如 Argon2、PBKDF2、Bcrypt 或 Scrypt)与随机盐一起存储
- 成功验证后,授权服务器将从其数据库中根据 URL 查询参数中提供的客户端 ID 检索重定向 URL。重定向 URL 是资源服务器 URL
- 然后,用户将被重定向到资源服务器端点,在 URL 查询参数中带有授权代码
- 然后,资源服务器将向授权服务器发出 HTTP POST 请求以获取访问令牌。授权代码、客户端 ID、客户端密码应包含在请求正文中。(应使用上述适当的缓存标头)
- 授权服务器将在响应正文或标头中返回访问令牌和刷新令牌(具有上述适当的缓存标头)
- 资源服务器现在将通过设置适当的 cookie(将在下面详细解释)将用户(HTTP 响应代码 302)重定向到 SPA URL
On the other hand, for resource owner password credential grant type, the authorization server and the resource server are same. It is easier to implement and can also be used if it suits the requirement and implementation timelines.
另一方面,对于资源所有者密码凭证授予类型,授权服务器和资源服务器是相同的。它更容易实施,如果它适合要求和实施时间表,也可以使用。
Also refer to my answer on this herefor further details on Resource Owner grant type.
另请参阅我的答案在这这里对资源所有者授予类型的进一步细节。
It may be important to note here that in a SPA, all the protected routes should be enabled only after calling an appropriate service to ensure that valid tokens are present in the request. Similarly the protected APIs should also have appropriate filters to validate the access tokens.
这里需要注意的是,在 SPA 中,只有在调用适当的服务后才应启用所有受保护的路由,以确保请求中存在有效的令牌。同样,受保护的 API 也应该有适当的过滤器来验证访问令牌。
Why shouldn't I store the tokens in browser localstorage or sessionstorage?
为什么我不应该将令牌存储在浏览器本地存储或会话存储中?
Many SPAs do store access and / or refresh token in the browser localstorage or sessionstorage. The reason I think we shouldn't store the tokens in these browser storages are:
许多 SPA 确实在浏览器本地存储或会话存储中存储访问和/或刷新令牌。我认为我们不应该将令牌存储在这些浏览器存储中的原因是:
If XSS occurs, the malicious script can easily read the tokens from there and send them to a remote server. There on-wards the remote server or attacker would have no problem in impersonating the victim user.
localstorage and sessionstorage are not shared across sub-domains. So, if we have two SPA running on different sub-domains, we won't get the SSO functionality because the token stored by one app won't be available to the other app within the organization
如果发生 XSS,恶意脚本可以轻松地从那里读取令牌并将它们发送到远程服务器。在那里,远程服务器或攻击者在冒充受害用户方面没有问题。
localstorage 和 sessiontorage 不跨子域共享。因此,如果我们在不同的子域上运行两个 SPA,我们将无法获得 SSO 功能,因为组织内的另一个应用程序无法使用一个应用程序存储的令牌
If, however, the tokens are still stored in any of these browser storages, proper fingerprint must be included. Fingerprint is a cryptographically strong random string of bytes. The Base64 string of the raw string will then be stored in a HttpOnly
, Secure
, SameSite
cookie with name prefix __Secure-
. Proper values for Domain
and Path
attributes. A SHA256 hash of the string will also be passed in a claim of JWT. Thus Even if an XSS attack sends the JWT access token to an attacker controlled remote server, it cannot send the original string in cookie and as a result the server can reject the request based on the absence of the cookie. Also, XSS and script injection can be further mitigated by using an appropriate content-security-policy
response header.
但是,如果令牌仍然存储在这些浏览器存储中的任何一个中,则必须包含正确的指纹。指纹是加密强的随机字节串。原始字符串的 Base64 字符串将存储在名称为 prefix的HttpOnly
, Secure
, SameSite
cookie 中__Secure-
。Domain
和Path
属性的正确值。字符串的 SHA256 哈希值也将在 JWT 的声明中传递。因此,即使 XSS 攻击将 JWT 访问令牌发送到攻击者控制的远程服务器,它也无法发送 cookie 中的原始字符串,因此服务器可以基于 cookie 的缺失拒绝请求。此外,可以通过使用适当的content-security-policy
响应头进一步减轻 XSS 和脚本注入。
Note:
笔记:
SameSite=strict
ensures that the given cookie will not accompany the requests originated from a different site (AJAX or through following hyperlink). Simply put - any request originating from a site with the same "registrable domain" as the target site will be allowed. E.g. If "http://www.example.com" is the name of the site, the registrable domain is "example.com". For further details refer to Reference no. 3 in the last section below. Thus, it provides some protection against CSRF. However, this also means that if the URL is given is a forum, an authenticated user cannot follow the link. If that is a serious restriction for an application,SameSite=lax
can be used which will allow cross-site requests as long as the HTTP methods are safe viz. GET, HEAD, OPTIONS and TRACE. Since CSRF is based on unsafe methods like POST, PUT, DELETE,lax
still provides protection against CSRFTo allow a cookie to be passed in all requests to any sub-domain of "example.com", the domain attribute of the cookie should be set as "example.com"
SameSite=strict
确保给定的 cookie 不会伴随来自不同站点(AJAX 或通过以下超链接)发起的请求。简单地说 - 任何来自与目标站点具有相同“可注册域”的站点的请求都将被允许。例如,如果“ http://www.example.com”是站点名称,则可注册域为“example.com”。有关更多详细信息,请参阅参考编号。3 在下面的最后一节。因此,它提供了一些针对 CSRF 的保护。但是,这也意味着如果给出的 URL 是论坛,则经过身份验证的用户无法访问该链接。如果这是对应用程序的严重限制,SameSite=lax
只要 HTTP 方法是安全的,就可以使用它允许跨站点请求。获取、头部、选项和跟踪。由于 CSRF 基于不安全的方法,如 POST、PUT、DELETE,lax
仍然提供对 CSRF 的保护为了允许 cookie 在所有请求中传递到“example.com”的任何子域,cookie 的域属性应设置为“example.com”
Why should I store access token and / or refresh token in cookies?
为什么要在 cookie 中存储访问令牌和/或刷新令牌?
- When storing the tokens in cookies, we can set the cookie as
secure
andhttpOnly
. Thus if XSS occurs, the malicious script cannot read and send them to remote server. XSS can still impersonate the user from the users' browser, but if the browser is closed, the script can't do further damage.secure
flag ensures that the tokens cannot be sent over unsecured connections - SSL/TLS is mandatory - Setting the root domain in the cookie as
domain=example.com
, for example, ensures that the cookie is accessible across all sub-domains. Thus, different apps and servers within the organization can use the same tokens. Login is required only once
- 将令牌存储在 cookie 中时,我们可以将 cookie 设置为
secure
和httpOnly
。因此,如果发生 XSS,恶意脚本无法读取它们并将其发送到远程服务器。XSS 仍然可以从用户的浏览器中冒充用户,但是如果浏览器关闭,脚本就不能做进一步的破坏。secure
标志确保令牌不能通过不安全的连接发送 - SSL/TLS 是强制性的 domain=example.com
例如,将 cookie 中的根域设置为 可确保 cookie 可跨所有子域访问。因此,组织内的不同应用程序和服务器可以使用相同的令牌。只需登录一次
How do I validate the token?
如何验证令牌?
Tokens are usually JWT tokens. Usually the contents of the token are not secret. Hence they are usually not encrypted. If encryption is required (maybe because some sensitive information is also being passed within the token), there is a separate specification JWE. Even if encryption is not required, we need to ensure the integrity of the tokens. No one (user or the attacker) should be able to modify the tokens. If they do, the server should be able to detect that and deny all requests with the forged tokens. To ensure this integrity, the JWT tokens are digitally signed using an algorithm like HmacSHA256. In order to generate this signature, a secret key is required. The authorization server will own and protect the secret. Whenever the authorization server api is invoked to validate a token, the authorization server would recalculate the HMAC on the passed token. If it doesn't match with the input HMAC, it gives back a negative response. The JWT token are returned or stored in a Base64 encoded format.
令牌通常是 JWT 令牌。通常令牌的内容不是秘密的。因此它们通常不加密。如果需要加密(可能是因为一些敏感信息也在令牌中传递),则有一个单独的规范 JWE。即使不需要加密,我们也需要确保令牌的完整性。没有人(用户或攻击者)应该能够修改令牌。如果他们这样做,服务器应该能够检测到并拒绝所有带有伪造令牌的请求。为确保这种完整性,JWT 令牌使用 HmacSHA256 等算法进行数字签名。为了生成这个签名,需要一个秘密密钥。授权服务器将拥有并保护该机密。每当调用授权服务器 API 来验证令牌时,授权服务器将在传递的令牌上重新计算 HMAC。如果它与输入 HMAC 不匹配,它会返回否定响应。JWT 令牌以 Base64 编码格式返回或存储。
However, for every API call on the resource server, the authorization server is not involved to validate the token. The resource server can cache the tokens issued by the authorization server. The resource server can use an in-memory data grid (viz. Redis) or, if everything cannot be stored in RAM, an LSM based DB (viz Riak with Level DB) to store the tokens.
但是,对于资源服务器上的每个 API 调用,授权服务器不参与验证令牌。资源服务器可以缓存授权服务器发出的令牌。资源服务器可以使用内存数据网格(即 Redis),或者,如果所有内容都无法存储在 RAM 中,则可以使用基于 LSM 的数据库(即带有 Level DB 的 Riak)来存储令牌。
For every API call, the resource server would check its cache.
对于每个 API 调用,资源服务器都会检查其缓存。
If the access token is not present in the cache, APIs should return an appropriate response message and 401 response code such that the SPA can redirect the user to an appropriate page where the user would be requested to re-login
If the access token is valid but expired (Note, the JWT tokens usually contain the username and the expiry date among other things), APIs should return an appropriate response message and 401 response code such that the SPA can invoke an appropriate resource server API to renew the access token with the refresh token (with appropriate cache headers). The server would then invoke the authorization server with access token, refresh token and client secret and the authorization server can return the new access and refresh tokens which eventually flow down to the SPA (with appropriate cache headers). Then the client needs to retry the original request. All this will be handled by the system without user intervention. A separate cookie could be created for storing refresh token similar to access token but with appropriate value for
Path
attribute, so that the refresh token do not accompany every request, but available only in renewal requestsIf the refresh token is invalid or expired, APIs should return an appropriate response message and 401 response code such that the SPA can redirect the user to an appropriate page where the user would be requested to re-login
如果缓存中不存在访问令牌,API 应返回适当的响应消息和 401 响应代码,以便 SPA 可以将用户重定向到将请求用户重新登录的适当页面
如果访问令牌有效但已过期(注意,JWT 令牌通常包含用户名和到期日期等),API 应返回适当的响应消息和 401 响应代码,以便 SPA 可以调用适当的资源服务器 API 以使用刷新令牌(具有适当的缓存标头)更新访问令牌。然后,服务器将使用访问令牌、刷新令牌和客户端密钥调用授权服务器,授权服务器可以返回新的访问和刷新令牌,这些令牌最终会流向 SPA(具有适当的缓存标头)。然后客户端需要重试原始请求。所有这些都将由系统处理,无需用户干预。可以创建一个单独的 cookie 来存储类似于访问令牌但具有适当值的刷新令牌
Path
属性,以便刷新令牌不会伴随每个请求,而仅在更新请求中可用如果刷新令牌无效或已过期,API 应返回适当的响应消息和 401 响应代码,以便 SPA 可以将用户重定向到将要求用户重新登录的适当页面
Why do we need two tokens - access token and refresh token?
为什么我们需要两个令牌——访问令牌和刷新令牌?
Access token usually have a short validity period, say 30 minutes. Refresh token usually have a longer validity period, say 6 months. If the access token is somehow compromised, the attacker can impersonate the victim user only as long as the access token is valid. Since the attacker won't have the client secret, it cannot request the authorization server for a new access token. Attacker can however request the resource server for token renewal (as in the above setup, the renewal request is going through the resource server to avoid storing the client secret in browser), but given the other steps taken it is unlikely and moreover the server can take additional protection measures based on IP address.
If this short validity period of the access token helps the authorization server to revoke the issued tokens from the clients, if required. The authorization server can also maintain a cache of the issued tokens. The administrators of the system can then, if required, mark certain users' tokens as revoked. On access token expiry, when the resource server will go to the authorization server, the user will be forced to login again.
访问令牌的有效期通常很短,比如 30 分钟。刷新令牌通常具有更长的有效期,例如 6 个月。如果访问令牌以某种方式被泄露,攻击者只能在访问令牌有效的情况下冒充受害用户。由于攻击者没有客户端机密,因此无法向授权服务器请求新的访问令牌。然而,攻击者可以请求资源服务器进行令牌更新(如上述设置,更新请求通过资源服务器以避免将客户端密钥存储在浏览器中),但考虑到采取的其他步骤,这是不太可能的,而且服务器可以根据 IP 地址采取额外的保护措施。
如果需要,访问令牌的这个短有效期有助于授权服务器从客户端撤销已发布的令牌。授权服务器还可以维护已发布令牌的缓存。然后,如果需要,系统管理员可以将某些用户的令牌标记为已撤销。在访问令牌到期时,当资源服务器将转到授权服务器时,用户将被强制再次登录。
What about CSRF?
CSRF呢?
In order to protect the user from CSRF, we can follow the approach followed in frameworks like Angular (as explained in the Angular HttpClient documentationwhere the server has to send a non-HttpOnly cookie (in other words a readable cookie) containing a unique unpredictable value for that particular session. It should be a cryptographically strong random value. The client will then always read the cookie and send the value in a custom HTTP header (except GET & HEAD requests which are not supposed to have any state changing logic. Note CSRF cannot read anything from the target web app due to same origin policy) so that the server can verify the value from the header and the cookie. Since the cross domain forms cannot read the cookie or set a custom header, in case of CSRF requests, the custom header value will be missing and the server would be able to detect the attack
To protect the application from login CSRF, always check the
referer
header and accept requests only whenreferer
is a trusted domain. Ifreferer
header is absent or a non-whitelisted domain, simply reject the request. When using SSL/TLSreferrer
is usually present. Landing pages (that is mostly informational and not containing login form or any secured content may be little relaxed ?and allow requests with missingreferer
headerTRACE
HTTP method should be blocked in the server as this can be used to read thehttpOnly
cookieAlso, set the header
Strict-Transport-Security: max-age=<expire-time>; includeSubDomains
? to allow only secured connections to prevent any man-in-the-middle overwrite the CSRF cookies from a sub-domainAdditionally, the
SameSite
setting as mentioned above should be usedState Variable (Auth0 uses it) - The client will generate and pass with every request a cryptographically strong random nonce which the server will echo back along with its response allowing the client to validate the nonce. It's explained in Auth0 doc
为了保护用户免受 CSRF 的影响,我们可以遵循 Angular 等框架中遵循的方法(如 Angular HttpClient文档中所述服务器必须发送包含该特定会话的唯一不可预测值的非 HttpOnly cookie(换句话说,可读 cookie)。它应该是一个加密强随机值。然后客户端将始终读取 cookie 并在自定义 HTTP 标头中发送值(GET 和 HEAD 请求除外,它们不应该具有任何状态更改逻辑。注意,由于同源策略,CSRF 无法从目标 Web 应用程序读取任何内容)以便服务器可以验证标头和 cookie 中的值。由于跨域表单无法读取 cookie 或设置自定义标头,在 CSRF 请求的情况下,自定义标头值将丢失,服务器将能够检测到攻击
为了保护应用程序免受登录 CSRF 的影响,请始终检查
referer
标头并仅在referer
受信任域时才接受请求。如果referer
标头不存在或域未列入白名单,只需拒绝请求即可。使用 SSL/TLS 时referrer
通常会出现。登陆页面(主要是信息性的,不包含登录表单或任何安全内容可能有点放松?并允许缺少referer
标头的请求TRACE
应在服务器中阻止 HTTP 方法,因为这可用于读取httpOnly
cookie另外,设置标题
Strict-Transport-Security: max-age=<expire-time>; includeSubDomains
?只允许安全连接以防止任何中间人覆盖来自子域的 CSRF cookie此外,
SameSite
应使用上述设置状态变量(Auth0 使用它) - 客户端将生成并随每个请求传递一个加密的强随机随机数,服务器将与其响应一起回显,允许客户端验证随机数。它在Auth0 文档中进行了解释
Finally, SSL/TLS is mandatory for all communications - as on today, TLS versions below 1.1 are not acceptable for PCI/DSS compliance. Proper cipher suites should be used to ensure forward secrecy and authenticated encryption. Also, the access and refresh tokens should be blacklisted as soon as the user explicitly clicks on "Logout" to prevent any possibility of token misuse.
最后,SSL/TLS 对所有通信都是强制性的——就像今天一样,低于 1.1 的 TLS 版本对于 PCI/DSS 合规性是不可接受的。应使用适当的密码套件来确保前向保密和经过身份验证的加密。此外,一旦用户明确单击“注销”,访问和刷新令牌应被列入黑名单,以防止任何可能的令牌滥用。
References
参考
回答by Ismoil Shifoev
- Laravel Passport is an implementation of The PHP League's OAuth Server
- The password grant type can be used for username + password authentication
- Remember to hide your client credentials by making the auth request in a proxy
- Save the refresh token in a HttpOnly cookie to minimize the risk of XSS attacks
- Laravel Passport 是 PHP League 的 OAuth 服务器的一个实现
- 密码授权类型可用于用户名+密码认证
- 请记住通过在代理中发出身份验证请求来隐藏您的客户端凭据
- 将刷新令牌保存在 HttpOnly cookie 中以最小化 XSS 攻击的风险
More information you can see here
您可以在此处查看更多信息
http://esbenp.github.io/2017/03/19/modern-rest-api-laravel-part-4/
http://esbenp.github.io/2017/03/19/modern-rest-api-laravel-part-4/
回答by Wellwisher
I have also implemented Laravel passport in my project and I think I have covered most of the points which you have mentioned in your question.
我还在我的项目中实施了 Laravel 护照,我想我已经涵盖了你在问题中提到的大部分要点。
- I have used the password grant for generating an access token and refresh token. You can follow thesesteps to set up the passport and implement the passport grant. In your login method, you have to validate the user credentials and generate the tokens and attach the cookie(Attaching cookie to the response) to the response. If you need I can get you some examples.
- I have added two middleware for CORS(Handling the incoming request headers) and to check if the incoming access token is valid or not if not valid generate the access token from stored refresh token (Refreshing token). I can show you the example.
- After login, all the request from the client side should contain the Authorization header(
Authorization: Bearer <token>
).
- 我已经使用密码授权来生成访问令牌和刷新令牌。您可以按照以下步骤设置护照并实施护照授予。在您的登录方法中,您必须验证用户凭据并生成令牌并将 cookie(将 cookie 附加到响应)附加到响应。如果你需要,我可以给你一些例子。
- 我为 CORS(处理传入的请求标头)添加了两个中间件,并检查传入的访问令牌是否有效(如果无效)从存储的刷新令牌(刷新令牌)生成访问令牌。我可以给你看例子。
- 登录后,来自客户端的所有请求都应包含 Authorization header(
Authorization: Bearer <token>
)。
Let me know if you are clear with the above points.
如果您清楚以上几点,请告诉我。