API key authentication is one of the oldest and most widely used methods for securing APIs. Despite the rise of OAuth 2.0, JWTs, and other token-based protocols, API keys remain the go-to choice for a huge number of APIs — and for good reason. They are simple to understand, easy to implement, and straightforward for your API consumers to use.
In this guide, we will walk through everything you need to know to implement API key authentication properly: from generating cryptographically secure keys, to storing them safely, validating requests, handling rotation, and adding per-key rate limiting. Whether you are building a public API, an internal service, or a developer platform, the patterns here will help you ship a secure, production ready key management system.
If you are still deciding which authentication method is right for your API, take a look at our comparison of the top 7 API authentication methods for a broader overview.
When to Use API Keys vs. OAuth or JWTs
Before diving into implementation, it is worth understanding where API keys fit in the authentication landscape. Here is a quick comparison:
| Criteria | API Keys | OAuth 2.0 | JWTs |
|---|---|---|---|
| Complexity | Low | High | Medium |
| Best for | Server-to-server, developer APIs | User-delegated access, third-party apps | Stateless auth, microservices |
| Identity granularity | Per application or consumer | Per user and application | Per user or service |
| Revocation | Immediate (check on each request) | Token expiry or revocation list | Requires revocation list or short TTL |
| Setup time | Minutes | Hours to days | Hours |
API keys are the right choice when:
- Your API consumers are other services or backend applications, not end users in a browser.
- You need a simple onboarding flow — give the developer a key and they are up and running.
- You want to identify and rate-limit individual consumers without the overhead of an OAuth authorization server.
- You are building a developer platform where each consumer gets their own credentials.
API keys are not ideal when you need user-level delegation (e.g., "this app can read my profile but not post on my behalf") — that is where OAuth shines.
For a deeper look at API key authentication patterns, see our API key authentication guide.
How API Keys Work
The flow for API key authentication is refreshingly simple:
Here is what happens at each step:
- Client sends a request with the API key in a header. The most common
patterns are
Authorization: Bearer <key>or a custom header likeX-API-Key: <key>. - The server extracts the key from the incoming request.
- The server hashes the key using a one-way hash function (like SHA-256).
- The hash is looked up in the database to find the matching consumer record.
- Permissions and rate limits are checked against the consumer's configuration.
- The server responds — either with the requested data (200) or an authentication error (401/403).
The key insight is that the server never stores the raw API key. It only stores a hash. This means even if your database is compromised, the attacker cannot use the hashes to make API calls.
Generating Secure API Keys
A good API key needs to be long enough that it cannot be guessed or brute-forced, and structured so that it is easy for developers to identify and manage.
Entropy Requirements
Your API key should have at least 128 bits of entropy. For reference, a UUID v4 has 122 bits of randomness — close, but not quite ideal. A 32-byte random value gives you 256 bits of entropy, which is more than sufficient.
Key Structure and Prefixes
A common best practice is to add a prefix to your API keys. This serves several purposes:
- Identification: Developers (and secret scanners like GitHub's) can immediately tell what service the key belongs to.
- Versioning: You can change the prefix when you change your key format.
- Routing: In a multi-tenant system, the prefix can indicate the environment or region.
For example, Zuplo uses the prefix zpka_ for API keys. Stripe uses sk_live_
and sk_test_. Pick a prefix that is short, unique to your service, and
indicates the key type.
Code Examples
Here is how to generate a secure API key in TypeScript:
And the equivalent in Python:
A few important notes:
- Always use a cryptographically secure random number generator
(
crypto.randomBytesorsecrets.token_bytes), neverMath.random()or Python'srandommodule. - Use base64url encoding (not hex) to keep keys shorter while preserving entropy.
- The full key (prefix + random part) is what you give to the developer. You will only store a hash of it on your end.
Secure Storage: Never Store Plain Text Keys
This is the most critical rule of API key management: never store API keys in plain text. If your database is compromised, plain text keys give attackers instant access to every one of your consumer's accounts.
Instead, hash each key with SHA-256 before storing it. SHA-256 is a good choice here (over bcrypt or argon2) because:
- API keys have high entropy (unlike passwords), so brute-force attacks against the hash are impractical.
- SHA-256 is fast, which matters when you are validating keys on every single API request.
- It produces a fixed-length output that is easy to index in your database.
Storage Schema
Here is an example database schema for storing API keys:
Notice a few things:
- The
key_hashcolumn stores the SHA-256 hash, not the raw key. - The
key_prefixstores the first few characters of the key. This allows you to show a partial key in your dashboard (e.g., "myapi_k7Hj...") so consumers can identify which key is which, without exposing the full key. - Each key has its own
rate_limit,scopes, andexpires_at— giving you fine-grained control per consumer.
Hashing Example
The developer sees the raw key exactly once — when it is first created. After that, you only ever work with the hash.
Validating API Keys
When a request comes in, you need to extract the key, hash it, look it up, and verify it is still valid. Here is a middleware pattern for a Node.js/Express application:
And a similar pattern in Python with FastAPI:
A Note on Constant-Time Comparison
You might notice that we are comparing hashes using a database query rather than comparing strings directly in application code. When the database finds (or does not find) a matching hash, the timing is determined by the database index lookup, which does not leak information about how many characters matched.
If you ever need to compare hashes directly in application code, always use a
constant-time comparison function like crypto.timingSafeEqual in Node.js or
hmac.compare_digest in Python. Standard string equality (=== or ==) can
leak information through timing side channels because it short-circuits on the
first mismatched character.
Declaring API Key Auth in OpenAPI
If you are building an API with an OpenAPI specification (and you should be),
here is how to declare API key authentication using the securitySchemes
component:
The securitySchemes definition tells tooling (like API documentation
generators, SDKs, and testing tools) that your API expects an API key. The
security array at the top level applies the scheme globally, while you can
override it per operation if needed.
If you prefer using a custom header (like X-API-Key), simply change the name
field:
Per-Key Rate Limiting
Rate limiting is essential for protecting your API from abuse, and doing it per-key (rather than just per-IP) gives you much finer control. Per-key rate limiting lets you:
- Set different limits for different tiers of consumers (free vs. paid).
- Identify abusive consumers directly, even if they rotate IP addresses.
- Enforce usage quotas tied to billing or subscription plans.
Rate Limiting Strategies
There are several algorithms you can use for rate limiting:
| Algorithm | Pros | Cons |
|---|---|---|
| Fixed window | Simple to implement | Burst at window boundaries |
| Sliding window | Smooth distribution | More memory and computation |
| Token bucket | Allows controlled bursts | Slightly more complex |
| Leaky bucket | Steady output rate | No bursts allowed |
For most APIs, a sliding window or token bucket approach provides the best balance between fairness and flexibility.
Rate Limiting with Zuplo
If you are using Zuplo as your API gateway, configuring per-key rate limiting is straightforward. You can add a rate limiting policy directly in your route configuration:
Setting rateLimitBy to "user" means the rate limit is applied per
authenticated API key consumer. Each consumer gets their own bucket of 1000
requests per minute. You can also use Zuplo's
API key management to set
different rate limits for different consumers directly in the dashboard, no code
required.
Key Rotation
API keys get leaked. Developers accidentally commit them to GitHub, paste them in Slack, or leave them in logs. Having a solid key rotation strategy is not optional — it is a necessity.
Rotation Strategies
There are two main approaches to key rotation:
1. Grace Period Rotation
This is the most common and developer-friendly approach. When a consumer requests a new key:
- Generate a new key and store its hash.
- Mark the old key as "expiring" with a grace period (e.g., 24-72 hours).
- Both keys work during the grace period.
- After the grace period, the old key is automatically deactivated.
This gives the consumer time to update their integration without any downtime.
2. Multiple Active Keys
Allow each consumer to have multiple active keys at the same time (typically two to three). This way, the consumer can:
- Create a new key.
- Deploy their application with the new key.
- Verify everything works.
- Delete the old key.
This is the approach used by services like AWS (which provides two access key slots) and is the safest option because the consumer controls the timing.
Implementation Pattern
Here is how you might implement the multiple active keys approach:
API Key Security Checklist
Here is a comprehensive checklist to make sure your API key implementation follows security best practices:
- Always use HTTPS. API keys sent over plain HTTP can be intercepted by anyone on the network. There are no exceptions to this rule.
- Never store keys in plain text. Hash all keys with SHA-256 before storing them in your database.
- Never log API keys. Scrub keys from your application logs, access logs, and error reports. Log the key prefix or a key ID instead.
- Set expiration dates. Keys should not live forever. Set a default expiration (e.g., 90 days or one year) and notify consumers before their keys expire.
- Support key rotation. Allow consumers to create new keys and deactivate old ones without downtime.
- Use key prefixes. Prefixes make keys identifiable and enable secret scanning tools to detect leaked keys.
- Implement per-key rate limiting. Protect your API from abuse and ensure fair usage across consumers.
- Use the Authorization header. Prefer
Authorization: Bearer <key>over query string parameters. Query strings get logged in web servers, proxies, and browser history. - Never embed keys in client-side code. API keys in JavaScript bundles, iOS apps, or Android APKs are trivially extractable. Keys should only be used in server-to-server communication.
- Monitor for leaked keys. Use tools like GitHub secret scanning or GitGuardian to detect keys that have been accidentally committed to repositories.
- Scope keys to minimum permissions. Each key should only have access to the endpoints and actions it needs. Follow the principle of least privilege.
- Provide a key management dashboard. Give consumers visibility into their keys, including creation dates, last used timestamps, and the ability to create and revoke keys.
Implementing API Key Authentication with Zuplo
Building all of the above from scratch is a significant amount of work. You need to handle key generation, hashing, storage, validation on every request, rate limiting, rotation, a consumer dashboard, and ongoing maintenance.
Zuplo provides a fully managed API key authentication service that handles all of this out of the box. Here is what you get:
Automatic key generation and storage. Zuplo generates cryptographically secure API keys with configurable prefixes and stores them securely. You never have to manage a keys database yourself.
Built-in validation. Add API key authentication to any route with a single policy — no custom middleware required:
Per-consumer rate limiting. Set rate limits per consumer directly in the Zuplo dashboard or API. Different consumers can have different limits based on their plan or tier.
Self-serve developer portal. Zuplo automatically generates a developer portal where your API consumers can sign up, create API keys, view their usage, and rotate keys — all without you writing a single line of portal code.
OpenAPI integration. Zuplo reads your OpenAPI specification and automatically applies the correct security schemes, generates documentation, and validates requests.
Key rotation and management. Consumers can create multiple keys and deactivate old ones through the developer portal. You get full audit logs of every key event.
Secret scanning integration. Zuplo integrates with GitHub's secret scanning program, so if a consumer accidentally pushes their API key to a public repository, the key can be automatically revoked.
To learn more about how Zuplo handles API key management, check out the Zuplo API key management documentation.
Start Securing Your API Today
API key authentication, when done right, is a powerful and practical way to secure your API. The key (pun intended) is to follow the fundamentals: generate keys with sufficient entropy, never store them in plain text, validate on every request, support rotation, and rate limit per consumer.
If you want to skip the custom implementation work and get all of this out of the box, sign up for a free Zuplo account and have API key authentication running on your API in minutes. Your developers (and your security team) will thank you.