This guide explains how to secure an API in production. You’ll learn:
- The most important API security best practices
- Common vulnerabilities like Broken Object Level Authorization (BOLA)
- How to implement authentication, authorization, rate limiting, and input validation correctly
- How to test and verify your API security controls before release
Try Postman today →
APIs are the backbone of authentication, payments, mobile apps, partner integrations, and internal microservices. This also makes them prime targets for attackers.
When developing a REST API for either internal use or for public consumption, it’s crucial to have layered security controls implemented from the start. A single misconfigured endpoint can expose sensitive data, enable injection attacks, or allow unauthorized users to access resources they shouldn’t.
No single control is enough. Effective API security comes from combining multiple defenses that work together.
How do you secure an API?
To secure an API, use a combination of defensive strategies: require HTTPS for all traffic, set up strong authentication (OAuth 2.0, JWTs), conduct thorough input validation, implement rate limiting, practice the principle of least privilege, and watch for abnormal API behavior. Effective API security requires multiple integrated practices, not just one.
| Practice |
What it protects against |
Priority |
| HTTPS/TLS encryption |
Data interception, man-in-the-middle attacks |
Required |
| Authentication (OAuth 2.0, JWT) |
Unauthorized access, identity spoofing |
Required |
| Authorization and access control |
Privilege escalation, data leakage |
Required |
| Input validation |
Injection attacks, malformed requests |
Required |
| Rate limiting |
Brute-force attacks, DDoS, abuse |
High |
| API key management |
Credential theft, unauthorized usage |
High |
| Logging and monitoring |
Undetected breaches, slow incident response |
High |
| Security testing |
Unknown vulnerabilities, regressions |
High |
Why API security matters
APIs expose application logic and data to external consumers by design. Every endpoint is a potential attack surface that you’re deliberately making accessible. The more APIs you manage, the larger that surface becomes, and the harder it is to enforce security consistently across all of them.
The consequences of poor API security are well documented: broken authentication, excessive data exposure, and missing access controls consistently appear in the OWASP API Security Top 10, a widely referenced list of the most critical API vulnerabilities. The risks involved are real. Misconfigured APIs have led to breaches exposing millions of user records, unauthorized access to internal systems, and financial losses from abuse of unprotected endpoints.
The good news: most API vulnerabilities are preventable with the right practices applied consistently throughout the development lifecycle.
Encrypt all traffic with HTTPS
While TLS encryption is essential for external-facing APIs, most teams overlook its importance for internal traffic. Service-to-service communication in distributed systems faces interception risks, particularly in shared infrastructure or multi-tenant cloud environments where network traffic might breach trust boundaries.
# Enforce HTTPS in your API responses
Strict-Transport-Security: max-age=31536000; includeSubDomains
Use TLS 1.2 or higher (preferably TLS 1.3) and disable older protocols like SSLv3 and TLS 1.0. Configure your servers to reject unencrypted connections entirely rather than redirecting from HTTP to HTTPS, which still exposes the initial request.
For service-to-service communication, consider mutual TLS (mTLS), where both the client and server present certificates. This confirms identity from both sides, which is especially helpful in microservice setups to ensure internal callers are legitimate, beyond just encrypted connections.
Use strong authentication
The most common and preventable API vulnerability is weak or absent authentication. OAuth 2.0 with JWTs is the standard for production APIs, giving you delegated authorization flows with stateless token verification on every request.
POST /api/login HTTP/1.1
Content-Type: application/json
{
"grant_type": "client_credentials",
"client_id": "your_client_id",
"client_secret": "your_client_secret"
}
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600
}
Then pass the token in the Authorization header:
GET /api/users/12345 HTTP/1.1
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
Key authentication practices:
Use short-lived access tokens (minutes to hours, not days or weeks) and refresh tokens for extended sessions.
Validate JWT signatures on every request. Never trust a token without verifying its signature, expiration, and issuer.
Store tokens securely. Keep access tokens in memory on the client side, not in localStorage or cookies without proper flags.
Never rely on API keys alone for authentication. API keys identify the calling application but don’t verify user identity. Use them alongside OAuth 2.0, not as a replacement.
Implement proper authorization
The top vulnerability on the OWASP API Security Top 10 list is Broken Object Level Authorization (BOLA), and it’s surprisingly common. An API causes this when it verifies user authentication but does not check if they have permission to access the requested resource.
# Vulnerable: No ownership check
GET /api/orders/789
# Any authenticated user can access any order
# Secure: Verify the requesting user owns the resource
GET /api/orders/789
# Server checks: Does the authenticated user own order 789?
When you configure authentication, apply the principle of least privilege. Grant the minimum permissions needed for each role or scope. A reporting dashboard doesn’t need write access to user records, and a mobile app shouldn’t have admin privileges.
Use Role-Based Access Control (RBAC) or Attribute-Based Access Control (ABAC) to enforce permissions consistently:
// Middleware example: Check user role before allowing access
function authorize(requiredRole) {
return (req, res, next) => {
if (req.user.role !== requiredRole) {
return res.status(403).json({ error: "Forbidden" });
}
next();
};
}
app.delete("/api/users/:id", authorize("admin"), deleteUser);
The concept of input validation is easy, but ensuring it’s applied uniformly to every endpoint, parameter, header, and query string is challenging, particularly for growing APIs. This validation layer is designed to stop injection attacks (SQL, NoSQL, command injection), reject improperly formed payloads, and detect business logic exploits before they hit your application code.
// Validate input before processing
app.post("/api/users", (req, res) => {
const { name, email, role } = req.body;
// Check required fields exist
if (!name || !email) {
return res.status(400).json({ error: "Name and email are required" });
}
// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
return res.status(400).json({ error: "Invalid email format" });
}
// Whitelist allowed values
const allowedRoles = ["viewer", "editor", "admin"];
if (role && !allowedRoles.includes(role)) {
return res.status(400).json({ error: "Invalid role" });
}
// Proceed with validated data
createUser({ name, email, role: role || "viewer" });
});
Key validation practices:
- Use allowlists over denylists. Define what’s accepted rather than trying to block every possible malicious input.
- Validate data types, lengths, ranges, and formats. A user ID should be a number, not an arbitrary string.
- Sanitize output as well as input. Encode data before including it in responses to prevent cross-site scripting (XSS) in API consumers that render HTML.
- Use a schema validation library (like Joi, Zod, or JSON Schema) to enforce request structure consistently across endpoints.
Apply rate limiting and throttling
Rate limiting restricts the volume of requests a client can send over a defined period. Without it, your API is vulnerable to brute-force attacks, credential stuffing, denial-of-service (DoS) attacks, and general abuse from misbehaving clients.
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 67
X-RateLimit-Reset: 1700000000
When a client exceeds the limit, return a 429 Too Many Requests status with a Retry-After header:
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/json
{
"error": "Rate limit exceeded. Try again in 30 seconds."
}
Set different rate limits based on the endpoint’s sensitivity. For example, login endpoints and password reset flows should have stricter limits than read-only data endpoints. Consider per-user, per-IP, and per-API-key limits to prevent abuse from multiple angles.
When duplicate requests could cause actual harm to payment or transaction endpoints, use idempotency keys for secure retry handling.
POST /api/payments HTTP/1.1
Idempotency-Key: a1b2c3d4-unique-key
Content-Type: application/json
{
"amount": 99.99,
"currency": "USD"
}
Manage API keys and secrets securely
Careful handling of API keys, tokens, and credentials is essential at every stage, including generation, rotation, and revocation.
Never hardcode secrets in source code. This is one of the most common and most preventable security mistakes. Repository history retains keys even after you delete them from the current version.
# Bad: Hardcoded in source
API_KEY = "sk_live_abc123xyz789"
# Good: Loaded from environment variables
API_KEY = os.environ.get("API_KEY")
Key practices:
- Store secrets in environment variables or a dedicated secrets manager (like HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault).
- Rotate API keys on a regular schedule and immediately after any suspected exposure.
- Use scoped keys with the minimum permissions needed. A key that only needs read access shouldn’t have write privileges.
- Set expiration dates on keys and tokens. Long-lived credentials increase the window of exposure if they’re compromised.
- Monitor key usage for anomalies. A surge in requests from one key could signal a security breach.
Return only what’s needed
Oversharing data in API responses is a subtle but serious security risk. If your API returns full user objects when the client only needs a name and email, you’re unnecessarily exposing fields like internal IDs, roles, password hashes, or other sensitive attributes.
// Bad: Returns everything from the database
app.get("/api/users/:id", async (req, res) => {
const user = await db.users.findById(req.params.id);
res.json(user); // Includes password_hash, internal_notes, etc.
});
// Good: Return only what the client needs
app.get("/api/users/:id", async (req, res) => {
const user = await db.users.findById(req.params.id);
res.json({
id: user.id,
name: user.name,
email: user.email,
role: user.role
});
});
This approach, also known as data minimization, extends to error messages. Detailed error responses give attackers insight into your API’s structure. Return enough information for legitimate clients to fix their requests, but avoid exposing stack traces, database details, or internal service names in production.
Log, monitor, and alert
You can’t protect what you can’t see. With extensive logging and monitoring, you can spot ongoing attacks, examine incidents later, and find patterns that identify potential threats.
What to log:
-
All authentication attempts (successful and failed)
-
Authorization failures (403 responses)
-
Unusual request patterns (high volume from a single source, requests to non-existent endpoints)
-
Changes to sensitive resources (user roles, payment methods, API keys)
-
Rate limit violations
What not to log: Full request bodies containing passwords, tokens, credit card numbers, or other sensitive data. Log enough to investigate without creating a new exposure point.
Set up alerts for anomalies. For instance, a sharp rise in 401 responses could mean a credential stuffing attack, and a cluster of 500 errors might reveal an exploit attempt on a specific endpoint.
Test your API security in Postman
Security practices only work if they’re verified. Postman makes it straightforward to test authentication, authorization, input validation, and error handling across your API.
Test authentication enforcement:
-
Create a request to a protected endpoint without including an auth token.
-
Verify that you receive a 401 Unauthorized response.
-
Add a valid token and confirm the request succeeds.
-
Send an expired or malformed token and confirm you get a 401.
Test authorization controls:
-
Authenticate as a regular user.
-
Atstructurestempt to access or modify a resource owned by a different user.
-
Verify the API returns 403 Forbidden, not the resource data.
Test input validation:
-
Send requests with missing required fields and verify you get 400 Bad Request with a clear error message.
-
Send oversized payloads, special characters, and SQL injection strings to confirm the API rejects them.
-
Check that error responses don’t leak internal details like stack traces or database structures.
Test rate limiting:
-
Use Postman’s Collection Runner to send rapid sequential requests to a rate-limited endpoint.
-
Verify the API returns 429 Too Many Requests after hitting the limit.
-
Confirm the response includes Retry-After or rate limit headers.
Add assertions to your test scripts to automate these checks:
pm.test("Unauthorized request returns 401", function () {
pm.response.to.have.status(401);
});
pm.test("Response does not contain sensitive fields", function () {
const body = pm.response.json();
pm.expect(body).to.not.have.property("password_hash");
pm.expect(body).to.not.have.property("internal_notes");
});
Common API security mistakes to avoid
Relying on API keys as your only authentication. API keys identify applications, not users. They’re easily leaked in client-side code, browser history, or server logs. Pair them with proper user authentication like OAuth 2.0.
Returning too much data in responses. Don’t send entire database records when the client only needs two fields. Filter response data at the application layer to prevent accidental exposure of sensitive information.
Skipping authorization checks on individual resources. Verifying that a user is authenticated isn’t enough. Your API needs to verify permission for every resource requested, on every endpoint, without fail.
Inconsistent security policies across internal and external APIs. Internal APIs often get a pass on authentication, rate limiting, or input validation because they’re “not public-facing.” But lateral movement through internal APIs is a common attack pattern once an attacker gains initial access. Apply the same security baseline everywhere.
Using outdated dependencies. Vulnerabilities in third-party libraries are a common attack vector. Keep your dependencies updated and monitor security advisories for the frameworks and packages your API relies on.
Exposing detailed error messages in production. Stack traces and database errors help during development but give attackers a roadmap in production. Return generic error messages and log the details server-side.
API Security FAQs
| Question |
Answer |
| What’s the most critical API security practice? |
Strong authentication and authorization on every endpoint. |
| Should internal APIs use HTTPS? |
Yes. Always encrypt API traffic, including service-to-service communication. |
| Are API keys enough for security? |
No. API keys identify applications but don’t authenticate users. Use them alongside OAuth 2.0 or JWTs. |
| What is BOLA? |
Broken Object Level Authorization, which is when an API doesn’t verify a user’s access to a specific resource. It’s the #1 OWASP API vulnerability. |
| How often should I rotate API keys? |
Regularly (every 60–90 days) and immediately after any suspected compromise. |
| What status code for rate limiting? |
429 Too Many Requests, with a Retry-After header. |
| Should I validate input on the server? |
Always. Validate at the API layer regardless of what upstream clients or gateways do. |
| How can I test API security? |
Verify authentication, authorization, input validation, and rate limiting. Automate checks with test scripts in your CI/CD pipeline. |
The post API Security Best Practices: A Developer’s Guide to Protecting Your APIs appeared first on Postman Blog.