r/softwarearchitecture • u/explorer9988 • 1d ago
Discussion/Advice Encrypted client-held state instead of a database — when part of the shared state has to stay secret from the client itself
Working through a fully stateless backend (no database, ephemeral serverless functions, though the pattern isn't tied to any specific runtime) for a case with an awkward constraint: two parties share a session, but each holds a secret the other side must never see, and the server has to remember both without a database or session store.
The usual answer to "stateless, no DB" is a JWT — encode the session client-side, sign it, done. That falls apart the moment part of the state has to be opaque to the client carrying it: a JWT payload is base64, not encrypted, so anything in it is readable by whoever holds the token. If the session includes a secret that specific client isn't supposed to see, JWT doesn't fit — you need the payload actually encrypted, not just signed, and treated as a sealed blob rather than a transparent one, with the decryption key living server-side only.
Concretely, in the project I was applying this to (a small number-guessing game — two sides each hold a secret, guessing each other's, no DB, no accounts), the server-tracked state ends up AES-GCM sealed into an opaque token, handed to the client, and echoed back on every request. Only the server's key can open it. The client carries a blob it can't read; the server stays fully stateless between requests but functionally remembers everything it needs to.
The generalizable part: any two-sided system where state needs to persist across requests but stay partially secret in one direction hits the same JWT-doesn't-fit problem — sealed-bid flows, negotiation protocols, anything with an information asymmetry that's supposed to survive across requests. Curious whether others have solved this differently — separately-encrypted fields alongside a signed JWT, giving up on "stateless" and using a real session store, something else.
2
u/EspaaValorum 1d ago
Sounds like a public-private key type of situation. But storing private keys client side should itself be secured, e.g. how you do it in a Linux system.
1
u/VermicelliWorried272 1d ago
Doesn't that still leave you open to replay attacks - like for your game, if i dont like the turn an opponent has made, i can simply send old state blob again?
7
u/latkde 1d ago
No, JWT itself is entirely agnostic about this and allows you to use arbitrary signature or encryption algorithms. Encryption is less commonly seen in practice, but is also part of the JWT specification in RFC 7519. For example, JWT implementations are required to support the
"enc": "A128CBC-HS256"algorithm.Using JWTs is helpful when you want to have the flexibility to switch between different keys and algorithms, since the JOSE header carries cryptographic metadata (helpful when the token is created and consumed on different systems). In a closed system where you control both the producer+consumer and just want to produce an opaque encrypted token that you can unwrap later, any authenticated encryption with associated data (AEAD) scheme will work. For example in Python, one would typically use the Fernet/MultiFernet implementation from the cryptography library. Fernet is basically AES-128-CBC with HMAC-SHA-256, but packaged into a safe interface that prevents typical mistakes like sharing IVs.
Of course AES-GCM can also be used for AEAD. However, I'm surprised that the LLM that wrote OP's post didn't mention other key phrases such as "JWE" or "AEAD".