Secure Spring Boot API with API Key and Secret Authentication
Learn how to implement API key and secret authentication in Spring Boot.
20+ years shipping production Java in banking & fintech. Written from production experience, not tutorials.
- ✓Basic knowledge of Spring Boot and Spring Security
- ✓Familiarity with Maven or Gradle
- ✓Understanding of HTTP headers
- Use a custom filter to extract and validate API key and secret from headers.
- Store secrets hashed (BCrypt) and keys in a secure vault; never hardcode.
- Combine with rate limiting and logging for production readiness.
- Avoid common mistakes like storing secrets in plaintext or ignoring timing attacks.
Think of an API key and secret like a username and password for a machine. The key identifies the client, the secret proves they are who they say they are. Spring Boot acts like a bouncer checking both at the door.
Let's be honest: most tutorials on API key authentication are dangerously simplistic. They show you how to check a hardcoded key in a filter and call it a day. In production, you need more: you need to support key rotation, handle secrets securely, and protect against timing attacks. I've seen startups burn down because they stored secrets in plaintext in a config file that ended up in a public GitHub repo.
In this article, I'll show you how to implement a robust API key and secret authentication scheme in Spring Boot. We'll use a custom filter, store secrets hashed with BCrypt, and add rate limiting and logging. I'll also share a real production incident where a fintech company lost thousands because of a missing 'constant-time comparison'.
By the end, you'll have a production-ready solution that avoids the traps I've seen teams fall into over the years.
Why API Key and Secret Authentication?
In the microservices world, you often need machine-to-machine authentication that's simpler than OAuth2 but more secure than a single API key. The API key identifies the client, and the secret proves possession. This is common in B2B APIs where you issue credentials to partners.
I've used this pattern in payment gateways and SaaS platforms. It's straightforward to implement and doesn't require a full identity provider. But it's also easy to get wrong. The most common mistake? Treating the secret like a password you can compare in plaintext.
Let's build a solution that's both secure and maintainable.
Storing Secrets Securely: BCrypt All the Way
Never store secrets in plaintext. Even if your database is secure, a SQL injection or backup leak can expose them. Always hash secrets using a strong algorithm like BCrypt. The API key itself can be stored in plaintext (it's just an identifier), but the secret must be hashed.
When validating, use BCrypt's checkpw() method, which is constant-time. Don't compare raw strings. Here's how your service should look:
MessageDigest.isEqual(). It was fine, but BCrypt's built-in salt and cost made key rotation easier.What the Official Docs Won't Tell You
Spring Security's official documentation focuses on OAuth2 and JWT. API key authentication is often left as an exercise for the reader. Here are the gotchas I've learned the hard way:
- Filter Order Matters: If you register your filter after Spring Security's filter chain, it will never be invoked for secured endpoints. Use
SecurityFilterChainto add your filter at the right position. - Constant-Time Comparison Is Not Optional:
String.equals()short-circuits on the first mismatched character. This leaks timing information. UseMessageDigest.isEqual()or BCrypt's.checkpw() - Rate Limiting on Auth Endpoints: Attackers can brute-force secrets if you don't rate-limit. Use Bucket4j or a similar library to throttle failed attempts.
- Logging Without Secrets: Never log the secret. Log the API key and a masked version of the secret (e.g., first 4 characters). This helps debugging without compromising security.
- Key Rotation: Support multiple valid keys per client to allow rotation without downtime. Store an expiration date and deactivate old keys after a grace period.
Generating API Keys and Secrets
Use a cryptographically secure random generator to create keys and secrets. The key should be a unique identifier (e.g., UUID), and the secret should be a high-entropy string (e.g., 32 bytes of random data encoded in Base64).
Here's a utility class you can use:
RandomStringUtils from Apache Commons, which is not cryptographically secure. Always use SecureRandom.Testing Your Authentication Filter
Unit tests are good, but integration tests with @WebMvcTest or @SpringBootTest are better. You want to verify that the filter is actually invoked and that invalid credentials return 401.
Here's a test example using MockMvc:
Rate Limiting and Logging
To prevent brute-force attacks, implement rate limiting on authentication failures. Use Bucket4j or Spring's built-in support with @RateLimiter. Log every authentication attempt (without the secret) for auditing.
Here's a simple rate-limiting filter:
The Timing Attack That Leaked API Keys
String.equals() for secret comparison was safe because the secret was hashed.String.equals(), which is not constant-time. An attacker could measure response times to guess the secret character by character.String.equals() with MessageDigest.isEqual() which provides constant-time comparison. Also moved to comparing hashed secrets using BCrypt's checkpw() method.- Always use constant-time comparison for secrets.
- Never compare raw secrets; compare hashes.
- Add random delays to authentication responses to obscure timing differences.
- Log authentication attempts (without secrets) for auditing.
- Rotate secrets regularly and invalidate old ones immediately.
curl -H 'X-API-Key: your-key' -H 'X-API-Secret: your-secret' https://api.example.com/endpointCheck application logs for 'Authentication failed' messages.| File | Command / Code | Purpose |
|---|---|---|
| ApiKeyAuthFilter.java | public class ApiKeyAuthFilter extends OncePerRequestFilter { | Why API Key and Secret Authentication? |
| ApiKeyService.java | @Service | Storing Secrets Securely |
| SecurityConfig.java | @Configuration | What the Official Docs Won't Tell You |
| ApiKeyGenerator.java | public class ApiKeyGenerator { | Generating API Keys and Secrets |
| ApiKeyAuthFilterTest.java | @WebMvcTest(controllers = SomeController.class) | Testing Your Authentication Filter |
| RateLimitingFilter.java | public class RateLimitingFilter extends OncePerRequestFilter { | Rate Limiting and Logging |
Key takeaways
Interview Questions on This Topic
How would you implement API key authentication in Spring Boot?
Frequently Asked Questions
20+ years shipping production Java in banking & fintech. Written from production experience, not tutorials.
That's Spring Security. Mark it forged?
3 min read · try the examples if you haven't