Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

IIRC tptacek has been beating this drum for a while, but it seems that he got tired of it, so I should pick the drum sticks in his stead:

Use less crypto. The less crypto is being used, the fewer mistakes are being made.

When it comes to sessions, generated a secure random 256 bit token and use that as a session id. Store it in a database or in-memory store. Sticky sessions + local session token storage will fix your network latency problems when you start scaling out.

Federation becomes moderately difficult. Perhaps you could store a copy of session id on each node, and when a session id is compromised, proactively reach to all nodes and ask them to purge the session. This allows immediate mitigation for a session id leak, and since it doesn't rely on timeouts there is no vulnerability window for data exfiltratiin upon a breach. And no crypto.



This misses one huge benefit of JWTs: Other parties can trust your token if they were not the ones to sign it.

For example, say client A calls service B, using a token signed by service C. Previously, we were using randomly generated session keys, which meant B had to ask C every time. But with JWTs, B can directly verify that the token is genuine without asking C, because it has C's public key.

We still check with C now and then, but that's because the token auto-expires. We use a long-lived master token stored in a cookie to generate new short-lived ones.


The problem is that you're trying to treat sessions and authorizations as one and the same thing. They're not.

What kind of services are you envisioning? Direct-to-database services like Firebase are a horrible idea for a plethora of security-related reasons, and if you control both service B and C yourself, then you either a) use one-time authorization tokens for stateless services or b) exchange a one-time authorization token for a session on a stateful service.

In none of those three cases do you use the token as the session. Tokens are handed out on a single-use, as-needed basis.


The user does have a session. Tokens are temporary, short-lived authorizations.

Sessions require a central session store; every request has to go to the central session store to check the session's validity. Incurring one session check per API call is bad enough, but when each API call then invokes half a dozen other APIs, you have a problem. Central session stores don't scale with distributed architectures.

I've not heard the term "direct-to-database" before, but we've been doing it for about 6 years, albeit not using Firebase, and it's huge win over the classic "custom, ad-hoc API per use case" methodology. Exposing a shared data layer abstraction between microservices is no different than exposing a more targeted API (i.e. there's conceptually zero difference between "POST /objects/42" and "POST /users/42"), including as far as security is concerned.


There was a solution to that situation proposed here[0] where you keep using session tokens on the user end (so you can still do stuff like revoke sessions), but convert that to a signed token for all internal API calls.

[0] http://cryto.net/%7Ejoepie91/blog/2016/06/19/stop-using-jwt-...


Thanks. That's pretty much the solution we ended up with, though the article author doesn't see the whole picture. Asking a central authority to issue single-use tokens for every call will result in a huge amount of unneeded network traffic.


> Central session stores don't scale with distributed architectures.

Sessions stores scale as much as anything else.


Not what I was referring to. They scale just fine on their own, but the surrounding architecture doesn't when it becomes involved for every single call.

Simple scenario: Let's say user X wants to update document Y, which involves fetching a photo, processing it and storing it as Z. To read Y, we need to check the session. To update Y, we need to check the session. To store the photo, we need to check the session. We're already up to three roundtrips to the session store.

Some interactions require a lot more participants, each of which need to check the store. Over and over again, even though _clearly_ the world hasn't changed the last 300ms. If one API requires 6 calls to its partners, that's 6 times more roundtrips than necessary.


The entire last paragraph of my pevious post describes one way to deal with it, without consulting a central session store.


By previous post, did you the bit about "Federation becomes moderately difficult"?

I don't know if you've ever developed microservices, but building this logic into every single microservice would be a lot of work. We have dozens of microservices, written in different languages, so even if we wrote some generic glue as a library, we'd have to write it at least three times (Go, Ruby and Node.js).


Stop using so many different languages?


I haven't. If you use microservices you should have an API gateway with sticky sessions, there is no need to check permissions for each service. It's more secure that way too, as it uses less code.


It's an interesting idea, but that would mean every internal microservice request (they don't just service users) would have to go through an API gateway, and every call would have to be checked against a session store.

It also means (if I understand the design correctly) that the API gateway has to munge every request envelope to add the actual user ID as an HTTP header (or something similar with gRPC), so that the internal microservices can see it, otherwise they'd have to ask the session store, which would defeat the purpose.

This in turn would mean that every microservice has to trust the upstream, which would require some infrastructure to manage (i.e., microservices would no longer be simply exposed to the world; the API gateway would be a special trusted actor). You also now have a dependency on the API gateway, because the microservice wouldn't know how to deal with a session without it.


The services are behind the firewall, unreachable from the internet, and only accept requests from the gateway, and from each other. The gateway strips your SSL (which I'm sure you are using :)), authenticates the request against its local security token store, then adds a bunch of headers: request id for tracing purposes, user IP for logging purposes, user id for authorizing access to various resources, etc.

The service only accepts the request, it doesn't care where the request came from because it implicitly trust the environment to be protected from direct, unauthenticated calls. Request has the user id in it, so the service knows which data is being accessed.

This way we have separated concerns of security from that of state management. This is what I'm arguing for - security without unnecessary public-facing crypto, and separation of authentication from authorization. The problem public-facing crypto is that there are tons of obscure of attacks on it - manipulating individual bits and sending it to the victim often yields information. New exploits come to light every now and again. I was caught with my pants around my ankles when padding oracle attack against signed cookies came out of the clear blue sky: http://robertheaton.com/2013/07/29/padding-oracle-attack/

Now, to keep the session state you have three options:

- Do the same thing you do with JWT: send the data to the user and accept it back. You know the user has been authenticated already, he can only harm his own resources. When services call each other they pass the session state along with the call, same as JWT again.

- Keep the session data in the centralized session store. Which kid of defeats the purpose of eliminating the centralized session store. :) There are many ways to make that scale well for throughput, e.g. once you know the user id you can trivially shard the session store, but I'm not sure about the latency from accessing yet another machine. This needs to be tested though, perhaps it's negligible. But let's keep that out for now, given the scope of the article.

- Keep the session store at the gateway. When the service replies to the request, part of the payload is the new (additional) session data. The gateway strips that part and keeps it to itself. The next time gateway calls a service for the same user it passes the session data along. The service does not need to know anything about the gateway - it receives a request with user id and session state, then returns the result with new/updated session state. The nice thing is that the users never see the session state (shorter url, less data on the wire, lower chance of malformed data), and yet microservices never have to know where it came from. When services call each other they can attach the state along with it. It may get tricky having to pass the sate back and forth along the call chain of various services, but it's the same problem you would have with JWT anyways. The complexity is why I would circle back to sharded session state storage.

Hopefully this answers all of your questions, let me know if it doesn't? It's an interesting conversation, thanks for that.


You're right about the API gateway being a good pattern for sending external traffic to internal microservices, and it is something you'd want regardless of what session system you used.

I'd let microservices talk directly to each other, however, rather than going through the API gateway. This means you reserve the API gateway for external traffic, and trust the internal traffic. You still need to standardize on/configure a set of "context headers" to pass from one services to another so that the identity of the original caller is preserved; but since this won't rely on a session, it can be trusted and treated opaquely by each application.

My earlier point stands, though: Once you deploy something like this, you now have an explicit dependency on the API gateway, because services can never be directly exposed to the world. It impacts local development, too, because now you either talk using session via the API gateway (which you have to run locally) or directly to the service (using whatever identifying information it trusts), and the fact that they are different is a potential point of confusion, especially for someone not familiar with the architecture. ("It isn't working" — "Oh, you need to go through the API gateway.")

Interesting conversation indeed. I wish there were a better forum for such discussions. There's a lot about microservices where I'd love to exchange ideas and good patterns.


Put it in the API gateway?


You think mistakes can't be made with session ids? Anyways, there are well trusted libraries out there for JWT.


I don't understand. A session key is just a lookup key for a database table. What mistakes are made?


There are tricks there too. If you just lookup in the database, I could find out how much time it took to retrieve the strings, this will leak the session id over time. Databases don't do constant-time data retrieval. You would have to come up with a way to retrieve the session token from the database at constant time, or at least time that does not betray the session id.


But the session id isn't sensitive, is it?

I mean most session verification middleware checks the ip address of the request against the address of the session login. If the address's don't match you kill the session and force another login.

I usually just put the session id in a cookie and use https to encrypt the whole stream shrug


> The less crypto is being used, the fewer mistakes are being made.

I don't understand this position. What do you mean by "fewer mistakes are being made"?


Cryptography-related code is incredibly easy to fuck up, and the tiniest mistake can completely break your security model. A mistake that could be insignificant in other kinds of code, could easily be critical in crypto-related code.

Thus, you reduce both the amount of mistakes and the impact of mistakes by avoiding crypto where possible.

EDIT: Yes, this includes using existing libraries. It's not just the implementation of the primitives themselves where issues occur.


In fact, a heap of JWT libraries had massive vulnerabilities in them not that long ago, that mean you could easily forge JWTs.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: