Tech Story
As a platform engineer, I want refresh tokens stored as hashes in the database so that a database breach does not yield usable tokens that could be used to impersonate users.
Context
Refresh tokens are currently stored as plaintext crypto.randomBytes(32) hex strings. If the refresh_tokens table is compromised (SQL injection, backup leak, rogue DB access), every active user session can be hijacked. Storing the SHA-256 hash of the token instead means the raw token is never persisted — only the bearer of the original token can produce the correct hash.
Acceptance Criteria
Technical Elaboration
function hashToken(raw: string): string {
return crypto.createHash('sha256').update(raw).digest('hex');
}
- Call
hashToken(token) before every save and findOne/update involving the token column
- The entity column name stays
token; no schema migration needed
- Add a unique index on
refresh_tokens.token if one does not already exist (the hash is effectively a unique identifier)
Notes
- SHA-256 is appropriate here (not bcrypt) because: the input has high entropy (32 random bytes) so rainbow tables are infeasible, and lookup performance matters (bcrypt is intentionally slow)
- Do not log the raw token at any point in the token lifecycle
Tech Story
As a platform engineer, I want refresh tokens stored as hashes in the database so that a database breach does not yield usable tokens that could be used to impersonate users.
Context
Refresh tokens are currently stored as plaintext
crypto.randomBytes(32)hex strings. If therefresh_tokenstable is compromised (SQL injection, backup leak, rogue DB access), every active user session can be hijacked. Storing the SHA-256 hash of the token instead means the raw token is never persisted — only the bearer of the original token can produce the correct hash.Acceptance Criteria
generateRefreshToken()storessha256(token)in the DB, returns the raw token to the callerrefreshAccessToken()hashes the incoming token before looking it up in the DBrevokeRefreshToken()hashes the incoming token before the update querytext), only stored value changesTechnical Elaboration
hashToken(token)before everysaveandfindOne/updateinvolving the token columntoken; no schema migration neededrefresh_tokens.tokenif one does not already exist (the hash is effectively a unique identifier)Notes