1

Where to store JWT refresh tokens? My idea was to encrypt the refresh token with crypto-js AES and salt, keeping it in an environment variable (.env). Then, the refresh token would be stored in either local storage or cookies. I am still deciding between these options.

  • What is your advice?
  • Is it feasible?
  • Is it sufficient?
1
  • Welcome to the community. So you want to encrypt it via the browser and then store it somehow in the backend? Prove me wrong, but isn't that the worst you can do? Commented Jul 9, 2023 at 12:44

1 Answer 1

4

Your question isn't clear whether you mean in the client (the thing that first receives and then uses the refresh token) or the server (where the token is generated and then consumed), nor as to whether you're using a browser or non-browser client, so I'll answer for each case.

On the server:

Refresh tokens need to be long-lived and revocable, so they need to be stored in persistent storage server-side. In practice this is going to be a database table or other persistent key-value store of some sort (the keys are the tokens; values will be things like user identity / session access scopes, expiration date if any, etc.). Environment variables aren't a good choice, you need too many of them, and they aren't designed for persistent storage or communication among processes (other than parent-to-child).

Hashing refresh tokens before storing (or retrieving) is recommended both to prevent a compromise of this database from leaking valid tokens and to prevent string comparison timing attacks; assuming the refresh tokens are cryptographically secure random strings (as they should be!), a single unsalted round of a fast secure hash like the SHA2 or SHA3 families would be fine. There's no good reason to additionally encrypt them, and you certainly shouldn't encrypt them instead of hashing, since reversible encryption (like AES) is unneeded here and the risk of key exposure makes it inherently less secure than hashing here.

In a distributed / microservice architecture, the handling refresh tokens is part of the auth service (as it needs to issue refresh tokens in response to logging in, and access/session tokens in response to refresh requests) but doesn't need any interaction with other services.

On the client:

Browser clients

As with most sensitive token types, there are arguments for either using cookies or local storage. Cookies are low-effort to implement, automatically time out (not that you should rely on this to perform a security function!), and the risk of CSRF is lesser with refresh than with other tokens (it allows the attacker to extend a session the user might expect to have timed out, which is bad and you probably should have some anti-CSRF mechanism, but it's not really a problem unless some attacker can also do something undesirable with that extended session). The network utilization cost can even be mitigated by setting the cookie's path property if you want.

However, storing refresh tokens in local storage and then submitting them using non-cookie headers (e.g. Authorization as a Bearer token, or a dedicated custom header) or putting the token in the request body is also fine. Its main security weakness is that XSS could steal the token, but even with an HttpOnly-flagged cookie, an attacker with XSS could still extend the session repeatedly until a user explicitly logged off or the attacker lost access to the victim's browser for long enough that the token expired, and also many XSS attacks can be fast enough that the session won't need refreshing.

There is no point to encrypting the refresh token in the client unless you have some way to generate a key that isn't stored by the browser. Browser storage such as cookies and local storage is already stored in a per-user (or per-app-sandbox) private location on all modern browsers, and additionally encrypted in most of them (though on desktop Linux and some other platforms, there's no guarantee of a platform-provided option for secure key storage so sometimes browsers can't meaningfully encrypt unless the user sets a master password). Adding your own in-browser encryption adds no security against XSS (what a legit script can do, so can an injected one), cross-site attacks (same-origin policy already handles this), or offline attacks (unless the encryption key is not stored, which removes most advantages of having a refresh token vs. just re-authenticating).

Non-browser clients

If you want refresh tokens to outlive the process (e.g. because the process may be restarted and you don't want to force re-authentication of the client) then the tokens need to be stored outside of the process, such as in a file, database, keychain, registry hive, etc. Process environment variables are ephemeral and thus unsuited to that use case. A keychain or other automatically-encrypted data store is ideal, if available, but anywhere with access control that prevents unauthorized users/services from reading it would be fine. Encrypting the token before storing it is useful if you want to prevent offline attacks (see e.g. the way that modern browsers encrypt site data such as cookies) that bypass OS access control, but fundamentally this just moves the problem a little; you now have to figure out how to get / where to store the key. This is why I suggest using a keychain functionality (or something like Windows' DPAPI) if available, as that takes care of key management for you.

Even if you want the tokens to be lost if the client process ends, an environment variable wouldn't be my first choice simply because setting and accessing them is usually more complicated than using any other process-global variable, but it's not inherently a bad one security-wise. There's not much point in encrypting them if you're storing them only in RAM, unless you're passing the variable (but not the key) to untrusted processes for some reason (just don't do that, OK?). In most operating systems, it's no easier to read environment variables out of another process than to read other global variables out of memory directly; an attacker with the ability to do either has already won. Using environment variables does mean that any child processes would by default receive the token, which might be good or bad depending on how much you trust the child processes and why you're spawning them at all.

5
  • Is there any other way to secure refresh token on client side? Commented Jul 9, 2023 at 12:00
  • It would help to add more info (possibly edit the question) about the client type (e.g. browser, mobile app, PC app, server for something else, etc.) and platform (e.g. desktop Windows, MacOS, Linux server, Android, etc.). Also, what attackers you're worried about (malicious user of the client, malicious other-app-or-user on the same machine as the client, network attacker, device thief, etc.). There are many places to store secrets, but realistically only a few will be viable, and I need to know more about what you're building to give more precise advice.
    – CBHacking
    Commented Jul 9, 2023 at 13:59
  • Great answer. Why would you choose not to salt hash when storing the refresh token?
    – Dave New
    Commented Nov 10, 2023 at 13:18
  • 1
    @DaveNew Simply put, there's no need to. Salting exists for passwords because password entropy sucks (meaning it's possible to build a rainbow table of the likeliest values and get most passwords) and people reuse them a lot (meaning without salt, you'd have duplicates in the DB). Neither of those apply to refresh tokens, which are supposed to be long, totally random strings; even at only 128 bits, there's no meaningful risk at all of either a rainbow table containing the pre-image, or two tokens colliding in the entire lifetime of the app.
    – CBHacking
    Commented Nov 10, 2023 at 15:57
  • @CBHacking: Thanks for the reply - makes complete sense.
    – Dave New
    Commented Nov 16, 2023 at 6:57

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .