Client API Key Lite: Service to Service Design
A lightweight Client API Key design for service-to-service communication using a 6-column database table and 10 lines of code.

Stock photo for illustration only, not from the actual event
- Design API keys for service-to-service communication using a 6-column table and 10 lines of code
- Store SHA-256 hashes instead of plain text to prevent risks during database leaks
- Use revoked_at instead of a UNIQUE constraint on the consumer to support zero-downtime key rotation
- Separate the resolver from framework middleware for better code maintainability
When another team requests integration with your service, identifying the caller accurately is paramount. Common approaches usually lean towards two extremes: storing a random UUID or hash in an environment variable until it leaks, or bringing in the entire OAuth2 suite which introduces unnecessary complexity. This post explores the middle ground.
This design focuses strictly on service-to-service communication, excluding user-generated keys created from web interfaces. Three foundational assumptions enable this lightweight approach. If dealing with a public API, these assumptions break down, requiring rate limiting, a key management dashboard, expiration dates, and granular permission scopes.

Stock photo for illustration only, not from the actual event
Early-stage security architecture often forces a trade-off between simplicity and complexity. Adopting a light design reduces initial engineering overhead while requiring a robust database foundation to prevent costly technical debt later.
The 6-column database table structure consists of:
- id INT AUTO_INCREMENT PRIMARY KEY
- consumer VARCHAR(100) NOT NULL (do not add UNIQUE)
- key_hash CHAR(64) NOT NULL UNIQUE (SHA-256 hex of the full key)
- revoked_at DATETIME NULL
- description VARCHAR(255) (ticket number and requester)
- created_at and updated_at timestamps
The first critical decision is avoiding plain-text key storage to eliminate risks from database dumps lingering on developer machines. Storing the SHA-256(key) and querying the hash directly eliminates the need for Bcrypt, as a 256-bit random value leaves nothing to guess.
Another vital practice is utilizing a revoked_at timestamp column paired with an explicit AND revoked_at IS NULL condition directly inside the SQL query, avoiding application-layer filtering to prevent future oversights.
"สิ่งที่ UNIQUE ตรงนี้แปลว่า คุณหมุน key (key rotation) ไม่ได้ เพราะ key rotation ที่ปลอดภัยต้องมีช่วงที่ key เก่ากับใหม่ใช้ได้พร้อมกัน"
Dev.to Author
Omitting the UNIQUE constraint on the consumer column enables seamless key rotation with zero downtime. The process involves inserting a new key, switching the consumer over, verifying that zero traffic remains on the old key, and subsequently revoking the old key.
When generating keys, command syntax matters—using printf instead of echo prevents unwanted trailing newlines that alter hash values. Furthermore, deliver credentials securely via the consumer's secret manager rather than standard chat or email channels.
Source: Dev.to
Found something wrong in this article? Report an issue with this article
Comments
Leave a Comment