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.
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.
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.
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.