2

I'm not sure what's the option to store and manage session IDs for long lived sessions (3-6 months each).

Given that the session ID is generated with enough entropy and that the session ID is then given to the user via SSL and a Secure, HttpOnly cookie, these are the options I came up with for storing the session ID:

  1. Just store the session ID in plain text in the database or file system
    • Pros: fast, easy
    • Cons: any compromised backup or unauthorised access to the database gives access to all active sessions
  2. Give both a session ID and a session key to the user and hash the session key before storing them into the database
    • Pros: secure both if the user compromises the server and the database
    • Cons: possibly slow (IIRC cryptographically safe hashing functions are intended to be slow) for something that needs to occur at every request
  3. Create a signed JWT token over that session ID with a secret stored on the server
    • Pros: probably faster than hashing, bigger tokens (as in length)
    • Cons: not secure if both the server and database are compromised

What the recommended way to manage and most importantly store sessions?

3
  • Why would you make your sessions this long?
    – user163495
    Commented Feb 12, 2021 at 9:09
  • @MechMK1 User convenience. Being signed out every week or month is not exactly pleasant. I guess I could shorten it to 1-3 months, but the main problems still stand. Commented Feb 12, 2021 at 9:14
  • 2
    Long-lasting sessions of this kind are typically an illusion. In reality, the site issues a persistent token (a cookie usually) that is either signed or encrypted and contains information about the user and possibly the device. When the browser presents the token, the site validates the token and creates a new session-- silently, to make it seem like the session had lasted the whole time. Literally keeping a session that long would cause a lot of problems.
    – John Wu
    Commented Feb 12, 2021 at 18:23

2 Answers 2

7

Your second approach is the right one to take, with a few additional notes.

There's no need for a separate ID token. Just hash the session token and pass the hash digest to the DB (e.g. SELECT * FROM Sessions WHERE hashed_token = ?). You can, if you want faster lookups, create an index on the sessions table (though that makes creating and deleting sessions more expensive).

IIRC cryptographically safe hashing functions are intended to be slow

Not at all! Quite the opposite, in fact. For example, in SSL/TLS cipher suites that aren't using authenticated encryption modes, ever single packet you send or receive has been HMACed at both ends, each of which requires two instances of a secure hash function. While it's true that insecure hashes can be faster than secure ones tend to be, good performance is an essential property of a successful secure hash algorithm.

You're probably confusing secure hash functions in general with password hashing functions. Password hashing functions exist primarily because normal secure hashing functions are too fast, so it's possible to brute-force the low entropy of a memorable secret in a very short time (a single modern graphics card can compute billions, even tens of billions, of secure hashes per second). However, that's not relevant here. You definitely shouldn't use a password hashing function for session tokens, but you also don't need to. Instead of the (generously) 20-40 bits of entropy that a human-generated password might have, a machine-generated random token will (and should) generally be at least 128 bits of entropy, and can easily be much more. Brute-forcing that at mere billions, or even a trillion, of guesses per second will take... oh, about 710 million times the age of the universe. You'll be fine.

A bunch of people seem to be taking issue with the use of "session" here, but realistically you should be doing this with any and all persistent but opaque client-supplied blobs. It doesn't matter in the least if they're "classic" session tokens, refresh tokens, API access keys (assuming the client passes the key itself e.g. as a bearer token), and so on.

As a side note, it's funny that you said JWTs are "probably faster than hashing". What do you think the signature of a JWT is? It's either an HMAC (twice as expensive as a single secure hash iteration) or a digital signature (orders of magnitude slower than a secure hash iteration). The only way JWTs are faster is that you don't have to do a DB lookup (no I/O, no search time), which matters a lot sometimes (if the DB is the bottleneck) but is often totally acceptable (and you can integrate this DB lookup into the DB query you'd be doing anyhow to service the request, if you really want to). You also really, REALLY must never use JWTs for long-term sessions. They can't be revoked (because the entire point is that they're stateless and nothing lives on the server), so if the JWT ever becomes compromised, either it's valid until its expiration date (you can clean it off a cooperating client, but you can't make the attacker forget it), or you have to rotate the signing key (invalidating all the JWTs issued with it). Instead, JWTs should have a very short lifetime - a few minutes is common - and the client also gets a long-lived "refresh token" which is just an opaque blob identical to a session token except you only need it every few minutes instead of on every request. (You should hash the refresh token before storing it in the DB and when the client presents it for use.)


Hashing session tokens (and refresh tokens, and bearer tokens, and some kinds of API keys*, and every other client-supplied opaque value with a security significance) is very nearly as important as password hashing, for nearly all the same reasons (the lone exception being that the same token won't work on other sites, while the same password might), and the cost of doing so is actually much lower (because you can use a fast hash instead of a deliberately slow one).

Many threats against tokens/API keys/etc. stored in the DB are such that access to those tokens barely matters, due to full compromise of the DB (via high-privilege SQL injection, compromise of the DB server software or host machine, weak DB credentials for an exposed DB server, or a malicious DB admin). In such a case, the attacker has direct access to all the data, and also can inject their own credentials for any user that they want to log in as, so hashing tokens provides only trivial additional security. However, many less-complete compromises exist. A common one is compromise of a DB snapshot, such as via an insecure cloud storage or physical theft of backup tapes. An attacker might also achieve only limited SQL injection (restricted to read-only access, or to only certain tables). In such a case, hashing tokens can easily be the difference between totally compromising arbitrary users' accounts, and merely having a limited (and often outdated) information disclosure.

* Obviously you can't hash API keys that are used to sign requests, such as in AWS SigV4; the server needs to know the actual API key in that case. But if the key is presented directly - in the URL, Authorization header, cookie, custom header, body, etc. - and the server is checking it against a stored value (rather than reading a meaningful value out of the token, as in a JWT), then the server should be hashing it before storing (or checking for) it.

1

I think your threat model about a compromised server is a little odd, though I certainly don't fault you for being concerned about leaving secrets and sessions exposed like that. The thing is, if your server and/or database are compromised, sessions will probably be the least of your concerns; the attacker can already view and manipulate all of your data (assuming it's not encrypted with the key stored out of reach). At this point, sessions/secrets may only be marginally useful to the attacker for maintaining access in case they lose their access to the compromised server/database. There's nothing wrong with trying to practice defense in depth and mitigate this threat, but there's definitely a point in which you have to trust something on the server, else you will never get anything done.

My recommendation is to find a framework or library that fits your needs, and most importantly, has already implemented session management securely. Even for someone already well versed in security, there are serious pitfalls that must be avoided when implementing something like this from scratch; it's often better to use a proven implementation.

As pointed out by @JohnWu in the comments, sessions of the length you mention are usually an illusion (although, nothing stops someone from doing it). Typically, sessions have an expiration date, but are continuously refreshed and extended as the user interacts with the service. There is a common paradigm of using both access and refresh tokens (often with JWT) where the access token is short lived, and must be periodically replaced by using the refresh token.

2
  • 1
    also key here is that the refresh token is one-time use. That helps secure things a bit more. The server would know if it were used twice and could notify the user via e-mail. Which is why you should never rely on a session alone to allow access to change the user's account info. (force them to login again to change...) Commented Nov 10, 2021 at 18:56
  • 2
    I disagree; protecting long-lived session tokens (or refresh tokens or anything else of the sort) is important in the same way and for the same reason as protecting passwords in the DB. Unhashed tokens can be stolen in lots of ways that might not give full access to the DB (e.g. via a read-only SQLi, or a SQLi that can only access the sessions table, or via a stolen DB backup tape, or via a timing attack on the live server) and then the attacker could use the session to get writable and up-to-date access.
    – CBHacking
    Commented Jul 9, 2022 at 23:59

You must log in to answer this question.

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