Skip to content

Commit

Permalink
Add token storage best practices
Browse files Browse the repository at this point in the history
  • Loading branch information
gguillemas committed Dec 18, 2024
1 parent 978d1d6 commit c9d92cf
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,29 @@ In the later scenario, some developers may choose to still authenticate each use
WebSocket connections offer an additional degree of isolation between users that may become relevant in the event where some session information for previous users who were using the same connection was not properly cleared. Additionally, even if successfully isolated from the security perspective, some resources associated with users are freed by SurrealDB only when the connection is terminated. Sharing the same WebSockets connection between several users may cause these unused resources to grow indefinitely.
## Token Storage
In some instances, applications may need to store some of the authentication tokens issued by SurrealDB. Even when token expiration has been configured to be as low as possible, tokens may potentially be stolen as a result of attacks against the application. To mitigate this risk, it is important to take steps to protect tokens in storage from being stolen as a result of these attacks. This is specially relevant in web applications, which usually expose additional attack vectors compared to other client applications.
The best way to protect tokens against stealing is to not store them at all. If your use case supports it, use the token in memory to [`authenticate`](/docs/sdk/javascript/methods/authenticate) a persistent session using the WebSocket protocol and destroy the token from memory after the session is established. When the session expires, ask your users to sign in again with their credentials and establish a new authenticated session with SurrealDB. Your use case may even support not using a token at all by directly authenticating the session with user credentials using [`signin`](/docs/sdk/javascript/methods/signin).
However, if you must store the authentication token (e.g. you want authentication to persist across browser tabs or restarts), our recommendation for most use cases is that you store tokens using browser storage primitives such as local storage and that you take steps to protect your web application from script injection attacks by taking measures including the following:
- Encode or at least sanitize all [untrusted input](/docs/surrealdb/reference-guide/security-best-practices#content-safety) before showing it on the page.
- Implement a [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) to prevent unauthorized scripts from executing.
- Implement [Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) to verify authorized external scripts.
- Use modern frontend frameworks that are designed to prevent content injection.
Understand that an attacker who is ultimately able to inject scripts into your web application or compromise the devices of your users will still be able to steal their tokens. These recomendations are intended to prevent this script injection from taking place. There is very little you can do to protect your users if you application is vulnerable to script injection attacks regardless of storage method. The impact of this actually happening can be mitigated by ensuring that token expiration is short to minimize the chance of an attacker capturing a valid token and reduce the window of oportunity to exploit it otherwise.
### Why not cookies?
SurrealDB does not support authenticating via cookies. Although cookies with the `secure` and `httpOnly` flags are often cited as the superior choice for token storage, this is [not always the case](https://portswigger.net/research/web-storage-the-lesser-evil-for-session-tokens). This is specially not true in the case of generic backend services such as SurrealDB, where protecting against [Cross-Site Request Forgery (CRSF)](https://owasp.org/www-community/attacks/csrf) attacks is not trivial without additional control of the frontend application. These attacks are possible because of how cookies work and would allow attackers to force users to make unauthorized requests to SurrealDB using their own valid cookies. Additionally, cookies are limited to a 4KB size, making them unsuitable for storing certain JWT payloads.
The proposed benefits of using cookies would be that [Cross-Site Scripting (XSS)](https://owasp.org/www-community/attacks/xss/) attacks could not be used to directly read the contents of the token as long as cookies were configure with the `httpOnly` flag. Although this is true, XSS attacks could still be used to take control of the browser session and impersonate the user using their own cookies to perform any authenticated actions that the token could be used for. This is essentially as bad as the token being stolen because an attacker is not interested in the token itself but rather in what the token can be used for. Most modern attacks against cookies with `httpOnly` will lead to essentially the same results as those against cookies without it.
In our opinion, the CSRF attacks made possible by cookies would be an unmitigated threat to applications built on SurrealDB. Additionally, XSS attacks are still a threat when using cookies with the `httpOnly` flag. On the other hand, significant advances have been made by modern browsers and frontend frameworks to prevent XSS attacks, whereas CSRF attacks are not possible to mitigate without the frontend and backend services working together in a way that would not be trivial to implement between SurrealDB and self-developed frontend applications.
## Vulnerabilities
When SurrealDB is part of your service or application, vulnerabilities that affect SurrealDB may also impact your environment. Due to this fact, we highly recommend that you track [vulnerabilities published for SurrealDB](https://github.com/surrealdb/surrealdb/security/advisories) so that you become aware of any updates that address vulnerabilities that you may be affected by. This can be done most effectively by leveraging automation tools that will consume the [Github Advisory Database](https://github.com/advisories?query=surrealdb). These automations will usually also warn of vulnerabilities in dependencies used by SurrealDB, which may also have an impact in your environment. Keeping up to date with the latest releases of SurrealDB is, in general, a good practice.
Expand Down
6 changes: 3 additions & 3 deletions src/content/doc-surrealql/statements/define/access/record.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,11 @@ ALGORITHM HS512 KEY "secret";
Defining a record access method `WITH REFRESH` will result in an additional [bearer key](/docs/surrealql/statements/define/access/bearer) for the record user being returned after successful authentication with the access method. This bearer key is intended to be used as a "refresh token", which is a concept commonly found in standards such as [OAuth 2.0](https://datatracker.ietf.org/doc/html/rfc6749#section-1.5).

Refresh tokens offer the possibility of issuing stateful tokens to record users when they successfully authenticate to SurrealDB. Unlike authentication tokens (i.e. JWT), refresh tokens (i.e. bearer keys) feature randomly generated opaque strings that contain no authentication information by themselves, but rather a pointer to an access grant that is stored in the datastore. Also unlike authentication tokens, bearer keys such as refresh tokens can be [audited](/docs/surrealql/statements/access/#show) and [revoked](/docs/surrealql/statements/access/#revoke) using the [`ACCESS`](/docs/surrealql/statements/access) statement. Refresh tokens are automatically revoked and replaced by a new refresh token whenever used to obtain an authentication token, reducing the time window for exploiting a compromised refresh token. These additional security guarantees allow refresh tokens to be longer-lived than authentication tokens, which in turn encourages making the original authentication tokens as short-lived as technically possible.
Unlike authentication tokens (i.e. JWT), refresh tokens (i.e. bearer keys) feature randomly generated opaque strings that contain no authentication information by themselves, but rather a pointer to an access grant that is stored in the datastore. Also unlike authentication tokens, bearer keys such as refresh tokens can be [audited](/docs/surrealql/statements/access/#show) and [revoked](/docs/surrealql/statements/access/#revoke) using the [`ACCESS`](/docs/surrealql/statements/access) statement. Refresh tokens are automatically revoked and replaced by a new refresh token whenever used to obtain an authentication token, reducing the time window for exploiting a compromised refresh token. These additional security guarantees allow refresh tokens to be longer-lived than authentication tokens, which in turn encourages making the original authentication tokens as short-lived as technically possible.

By default, refresh tokens will expire after 30 days. However, their duration can be increased using the `DURATION FOR GRANT` clause, which will accept any duration. This duration can be `NONE`, which will cause the refresh tokens to never expire. It is strongly recommended to set some expiration for refresh tokens to minimize the potential impact of credential stealing attacks.
By default, refresh tokens will expire after 30 days. However, their duration can be configured using the `DURATION FOR GRANT` clause, which will accept any duration. If set to `NONE`, refresh tokens will never expire. It is strongly recommended to set some expiration for refresh tokens to minimize the potential impact of credential stealing attacks.

Because refresh tokens can be used to indefinitely keep a user authenticated with SurrealDB as long as they are exchanged for a new fresh token before they expire, special care should be taken when storing them and suitably protecting applications using them from attacks.
Because refresh tokens can be used to indefinitely keep a user authenticated with SurrealDB as long as they are exchanged for a new fresh token before they expire, [special care](/docs/surrealdb/reference-guide/security-best-practices#token-storage) should be taken when storing and applications using them should be suitably protected from attacks.

Like other bearer keys, all refresh tokens are stored in the datastore even after they are expired or revoked. This means that using refresh tokens will have a space cost in addition to the performance cost of retrieving and verifying them against the datastore. Refresh tokens are intended to be used only to obtain a new authentication token after the existing one expires and applications should only use them when necessary, such as after receiving a [token expiration error](/docs/surrealdb/security/troubleshooting#token-expired-error). For certain high volume applications, you may want to regularly [purge](/docs/surrealql/statements/access/#purge) expired refresh tokens to minimize the space used by inactive refresh tokens.

Expand Down

0 comments on commit c9d92cf

Please sign in to comment.