Phx.gen.auth – isn't the storage of the session token into a signed cookie an overkill?

When we generate a token, we store the token in a signed cookie (via Plug.Conn.put_session/3) as seen in the generated auth code below:

token = Accounts.generate_user_session_token(user)
user_return_to = get_session(conn, :user_return_to)

conn
|> renew_session()
|> put_session(:user_token, token)

However, as our sessions are stateful (tokens stored in database with account id), aren’t signed cookie an unnecessary overkill (and consuming some server resources to verify signatures for every single request unnecessarily)?

As the tokens are stored in the database with attached information (e.g. account id), all the cookie needs to store is that token. Signed cookies, except if I missed something here, are useful for storing data in the cookie and make sure that data has not been altered; data such as the account id in order to have stateless sessions.

4 Likes

From the docs:

Tracking sessions
All sessions and tokens are tracked in a separate table. This allows you to track how many sessions are active for each account. You could even expose this information to users if desired.

But you’re right - if you don’t need that feature, there is no need to store the session in the database.

I do want to store the session in the database, for the ability to revoke a particular session.

The question is why store the token in a signed cookie.

Sorry, I misread your question.

If I’m getting this right, signing the session means that you’re not going to be able to use someone else’s token (they are not encrypted) - you’d have to steal the whole session. But I’d say this is mostly because people might want to put other stuff in the session which is hinted to in the generated comments/docs.

1 Like

Signing the cookie imo still has the benefit that it can‘t be tampered with. A user cannot run an enumeration attack trying to guess tokens of other users. It‘s also the default on phoenix to sign the session data, so I guess the performance question might not be as pressing as you made it sound. I don‘t have data on that though.

3 Likes

I think it doesn’t matter if a malicious user can get the underlying session token or not? That user may use and send the signed cookie all together.
What can he do with the session token that he cannot with the signed cookie?

As to adding custom data in the token @stefanchrobot, it doesn’t make much sense if you work with stateful sessions. And even then, it doesn’t matter because that data should not be sensitive anyway.

But I think these randomly generated tokens shouldn’t be able to be brute-forced?

Note also that the data you put in a signed cookie is actually readable. The signature only prevents the cookie to be altered. To make data hidden you need to encrypt the signed cookie.

Stateful session tokens is not equal to stateful session storage. The default session store is still a cookie. The auth token is not a session id.

There are actual server side session storages as well for Plug.Session, which are completely separate from auth.

1 Like

Session identifiers should be at least 128 bits long to prevent brute-force session guessing attacks.

Maybe it’s better performance-wise to increase the complexity of the session token rather than to sign the cookie.

Signing the cookie adds another layer of security. It’s not uncommon to put the user ID straight from the DB into the cookie. If the cookie is not signed, then you can impersonate other users.

If you’re only storing a cryptographically secure random token in the cookie and remember not to put anything else there then you might opt out of signing the session as this should be basically the same as talking to an API with an API key.

But I’m no security expert, so this may be a bad advice (I’m signing all my cookies).

2 Likes

What do you mean by auth token? If we talk about the token generated by phx gen auth, how is it different from a session id? These are just terms that mean the same thing?

Ok the default store is the cookie (and thus it must be signed as it is a store as you said) but I think the point of the question is if we can actually avoid that. Store only the token in an unsigned cookie without additional data, and the server holds all the state (which is already the case by the way). (Maybe I missed your point)

External libs?

This thread is kinda useless without a benchmark. Last decade CPU’s have optimizations for signing (and encrypting+decrypting) so I guess the overhead is way to low to bother. Changes are high you can find other code to optimize which in turn increases req/s by a lot more. When using a database, I am confident there is a config flag that will make more difference :slight_smile:

But to answer the question: anything not signed will be tampered with! Even when using a large random identifier, a hacker will try. And without rate limiting (cause it influences req/s) you can be sure they will succeed one day. When the session is signed, most hackers will continue to seek weaker spots (on somebody elses application)

Ps. Once you get hacked, the ‘avarage req/s of this year’ will take a huge blow! :wink: Be smart; sign.

5 Likes

Thank you:)

Just didn’t understand that part:

You mean that verifying a signature intrinsically leads to rate limiting? Why? Or is rate limiting something I need to implement additionally. Not sure what you had in mind.

mix phx.auth is build without needing to know about how you handle http sessions. It’s just concerned with authentication. Yes, it might look like a session ID. Yes, in the default setup it’s used similar to a session ID. But it’s not a session ID, because it identifies a logged in user, but not an http level session.

For server side http session handling you need third party libs implementing the Plug.Session behaviour. Then you no longer need signed cookies and you could even scrap the session tokens for auth (if sessions align 1:1 to auth). Though I’d suggest to keep things separate – simpler to change if requirements change.

Imo it’s not worth it to do that though, unless you actually have a need for server side sessions besides “I don’t want to sign cookie contents”. If you’re in a really heavily resource constraint system it might be, but even then I’d expect full blown server side session handling to take up at least as much resources if not more than doing that cookie signing.

2 Likes

Plus the token stored in the session is not hashed. Which means that if you don’t want to sign the session, you will need to at least hash the token to avoid timing attacks. So we are avoiding some of the work if we know it is going to the session.

Also, when it comes to security, having redundant measures is a good thing. For example, if your session tokens table leaks or someone gets access to it, they still won’t be able to sign in as any user because the token is signed (which is why we either hash or sign all tokens).

5 Likes

Thank you for your answers. Just to make it clear, it’s just a question and I’m not saying unsigned cookies are better for the storage of the token, I am just questioning the choices made:)

mix phx.auth could have called the lower-level put_resp_cookie/4 just as it did for the remember-me cookie, with the signing set to false. But I understand that mix phx.auth just uses the default session mechanism which is signed cookies and abstracted away.

A logged in user for one particular session though, which makes me think there is no real difference in the end for a developer.

However all we want to store is the account id. Storing a lot of extra information might be pretty much discouraged even in signed cookies because it increases the payload.

And maybe that is what I wanted to hear… :grin:

We can increase the size of the token and then store it as-is. Insufficient Session-ID Length | OWASP Foundation
(rand size parameter for :crypto.strong_rand_bytes)

Other server-side languages such as PHP also just have tokens in unsigned cookies if I’m not wrong.

1 Like

It could, but then it couples itself to http and needs to “own” the http level handling. Not doing that reduces the couling between the http level code and the code, which doesn’t care about the transport layer.

You can see this quite easily in an example. The current implementation doesn’t care which session handling library is used. By default it’s cookie (because that’s what phoenix itself defaults to), but using any other Plug.Session implementation will just work.

What you proposed essentially removes that flexibility completely and hardcodes cookie usage.

That’s what I meantioned before. It’s tempting to think those are one and the same thing, but they don’t have to be and keeping concerns separate (even if they happen to map 1:1) will keep the system flexible.

I’d challenge the assumption that a session will only ever hold the user/account id. There’s often a handful of small additional ephemeral or not so ephemeral data to keep around as well. The ones I can think of ad hoc is everything set/shared on unauthenticated sessions and taken over into logged in sessions like shopping carts, dark mode/light mode toggle settings, …

This is a misconception. Given the tokens used for auth are NOT a session IDs they’re not needed to be equally “secure”. As @josevalim mentioned even if someone gets to know those tokens they’re mostly useless if cookies contents are signed, because they cannot be used to impersonate another session/user.

You’d want to compare the security of the cookie signing (and optional encryption) of phoenix to the security of sessions on PHP if you want to compare apples to apples.

To be overly simplistic: The security here is in the session handling code and the tokens stored in the db just happen to be securely generated as well.

3 Likes

I meant: if you do not sign, you should implement rate limiting to defend against hackers trying thousands session keys per second (as they can be manipulated due to no signing). That would cause more overhead than signing :slight_smile:

1 Like

Maybe another side helps.

Having the session signed helps with runtime performance. It is faster to check a signature instead of doing first a cash lookup and thereafter, a database lookup.

If we take the threat modeled by owasp for getting access to a generic account, an attacker who is doing a brute force attack which we can defend with using a long session token. Each request would hit the database, which is calling a tcp connection. It also does load to your database. And only after that you can block the request.

On the other hand, the signing is just a hmac. This is using sha2 which newer processor have hardware routines backed into. So, the cost is negligible. Hammering this endpoint will not result in a risk of overusing your database. I would not say, that this prevents the need of using a request limiter. You still want to implement something like this.

This does not prevent the access of a middler in the middle attack.

The other threat josevalim mentioned, an attacket that gained access to your data.

Without signing you have a problem because now you have to trust that your sessions a short living. But the default for the session is 60 days. So enough time for an attacker to do some nasty stuff. Signing prevents this. And the statistics say you should not ask if your database leaks, but rather when your database leaks. So assuming that your data from the database will be public sometime in the feature is a good measurement. Because of this, you should also encrypt all personal stuff in your database.

I hope this shade some light on why signing is a good idea.

4 Likes