Why We Don’t Trust the Database With Authentication – Sturdy Statistics
When writing an API, it’s easy to unknowingly introduce a dangerous, implicit assumption: that the database is the ultimate source of truth. If a record is in the database, an application often treats it as authoritative. I want to explain in this post why that might be a very bad idea.
Here at Sturdy Statistics, our core engineering philosophy is Defense in Depth: we work to prevent failure at every layer, but we design each layer as though the others could fail. When it comes to API authentication, trusting the database as the sole arbiter of identity is a critical vulnerability masquerading as standard practice.
Here is why we don’t trust our database with authentication, and how we structurally prevent database-level compromises from becoming full-system breaches.
The Bypass: When SQL Injection Grants System-Wide Access
Consider the dangerous implicit default for handling API keys. If you treat a machine token like a user password, the implementation seems obvious:
Generate a random secret.
Hash it (e.g., SHA-256).
Store the hash in an api_keys table alongside an org_id.
When a request comes in, hash the provided secret and look for a matching row.
This feels secure because the database only holds hashes, not plaintext secrets. But let’s look at what happens if an attacker discovers a blind SQL injection vulnerability anywhere else in the application.
The attacker doesn’t need to read the database or invert any hashes. An attacker can simply register a legitimate account and generate his own valid API key. Since the key is both legitimate and legitimately his, he knows the plaintext secret, and he knows the application will successfully verify it. So far, this is entirely licit.
Now let’s imagine the attacker finds a SQL injection exploit. By executing a simple UPDATE statement, he can copy the hash of his key and paste it over the hash of the victim’s key. When the attacker sends a request attempting to access the victim’s data, he uses his own (valid) key. The API middleware hashes it, finds the victim’s row (which now contains the attacker’s pasted hash), and grants access.
Boom. Now it doesn’t matter how many other security measures we have in place; the attacker has just bypassed the API security perimeter and the entire key-based tenant boundary layer. His attack uses a valid key. Because the application blindly trusts the database’s record of the hash, a simple database write translated into complete tenant takeover.
The Fix: Cryptographic Binding
Securing the database against SQL injection is obviously a priority, but the “bypass” nature of this attack means we need Defense in Depth comparable to what we apply at the perimeter. We must separate the security of the authentication system from the security of the database.
To achieve this, Sturdy Statistics does not store simple hashes of API keys. Instead, we use a server-side cryptographic pepper – a high-entropy secret TPM-sealed to the backend – to compute an HMAC-SHA512 signature.
Crucially, we don’t just sign the secret. We sign the structural context of the key:
Stored Hash = HMAC-SHA512(Pepper, api-key-id<br>|| rotation-version<br>|| org-id<br>|| secret)<br>Where do these pieces come from? The api-key-id, rotation-version, and secret are parsed directly from the incoming client token. The org-id is strictly pulled from the requested URL path parameter. The database provides the stored hash to check against, along with its record of the api-key-id, rotation-version, and org-id. Thus, the secret and stored hash each appear only once. The other identifiers are each stored in two locations. This “double-book accounting” allows us to check for consistency.
If an attacker manages to write to the database and swaps a hash from Organization A into Organization B’s row, the attack fails immediately. When our authentication middleware verifies the incoming token, it pulls Organization B’s org-id from the URL path and injects it into the HMAC calculation. Because the hash encodes the wrong org-id, the hash changes, the signature is rejected, and the attacker is locked out.
Crucially, because this signature requires the Pepper – which lives exclusively in backend memory and never touches the database – even an attacker with full DB read and write access is thwarted. It is impossible for him to mint or modify keys without the pepper. The database no longer decides identity on its own; it stores verifiers that only the backend can validate. Thus the backend’s defense layers remain intact and can’t be bypassed via the DB.
Rollback Resistance, or Zombie Keys
You might wonder why we also include the rotation-version in the hash. This binds the signature to a specific epoch in the key’s lifecycle, which is the first half of defending against rollback attacks . If a key is compromised and eventually rotated, its old V1 hash is invalidated.
But what if an attacker with DB...