API Security Best Practices Every Developer Should Know
APIs are the connective tissue of modern software — and one of the most targeted attack surfaces. Here are the security practices that separate robust APIs from vulnerable ones.
APIs power everything. Your mobile app talks to your backend through an API. Your payment processor, your CRM, your analytics platform — all APIs. Modern software is less a monolith and more a network of services communicating through well-defined interfaces.
That interconnectedness is what makes APIs so powerful. It's also what makes them such an attractive target. The OWASP API Security Top 10 exists for a reason: API vulnerabilities are common, consequential, and often preventable.
Here's what secure API development actually looks like in practice.
1. Authentication: Get the Fundamentals Right
Every API endpoint that returns or modifies data needs authentication. That sounds obvious, but unauthenticated endpoints are still one of the most common findings in API security reviews — often because a developer added an endpoint quickly for testing and never locked it down.
Use industry-standard protocols. OAuth 2.0 with OpenID Connect for user-facing APIs. API keys or mutual TLS for service-to-service communication. Don't invent your own authentication scheme.
Prefer short-lived tokens. JWTs should expire in minutes to hours, not days. Use refresh token rotation so that a stolen refresh token can only be used once before it's invalidated.
Validate tokens properly. Check the signature, the expiry, the issuer, and the audience. A JWT with a valid signature but the wrong audience claim should be rejected. Many implementations skip these checks.
Never put credentials in URLs. API keys and tokens in query parameters get logged by proxies, CDNs, and web servers. Use the Authorization header.
2. Authorisation: Verify Every Access Decision
Authentication tells you who is making a request. Authorisation tells you what they're allowed to do. These are separate concerns, and conflating them is a common source of vulnerabilities.
Enforce authorisation at the resource level, not just the route level. A user who is authenticated and authorised to access /api/orders should only be able to access their own orders — not every order in the system. This is the IDOR (Insecure Direct Object Reference) vulnerability class, and it's endemic in custom APIs.
Apply the principle of least privilege. API keys and service accounts should have the minimum permissions required for their function. A read-only integration doesn't need write access. A reporting service doesn't need access to user credentials.
Implement scope-based access control. OAuth scopes let you define granular permissions that clients must explicitly request. A mobile app requesting read:profile shouldn't be able to call endpoints that require admin:users.
3. Input Validation and Sanitisation
Every piece of data your API receives is potentially hostile. Validate it before you use it.
Define and enforce a schema for every request. Use a validation library to check that incoming data matches the expected types, formats, and constraints. Reject requests that don't conform — don't try to fix them.
Validate on the server, not just the client. Client-side validation improves user experience. Server-side validation is what actually protects you. An attacker bypasses your frontend entirely.
Be explicit about what you accept. If a field should be a positive integer between 1 and 100, validate exactly that. Don't accept a string and try to coerce it. Don't accept values outside the expected range.
Sanitise data before using it in downstream systems. Even after validation, data that will be used in database queries, shell commands, or HTML output needs appropriate escaping or parameterisation.
4. Rate Limiting and Throttling
Without rate limiting, your API is vulnerable to brute force attacks, credential stuffing, denial of service, and resource exhaustion. Rate limiting is not optional.
Apply limits at multiple levels:
- Per IP address (to catch unauthenticated abuse)
- Per API key or user (to catch authenticated abuse)
- Per endpoint (to protect expensive operations)
- Globally (to protect infrastructure capacity)
Return 429 Too Many Requests with a Retry-After header. This gives legitimate clients the information they need to back off gracefully.
Implement exponential backoff for authentication endpoints. Failed login attempts should trigger increasing delays. After a threshold of failures, lock the account and require verification.
Consider business logic rate limits in addition to technical ones. A user who sends 500 password reset emails in an hour is abusing your system even if each individual request is technically valid.
5. Secure Data Transmission
All API traffic should be encrypted in transit. This means:
Enforce HTTPS everywhere. Redirect HTTP to HTTPS. Set Strict-Transport-Security headers. Don't allow TLS 1.0 or 1.1 — require TLS 1.2 at minimum, prefer TLS 1.3.
Encrypt internal service communication too. Traffic between microservices on a private network is still vulnerable to interception if the network is compromised. Mutual TLS (mTLS) provides both encryption and service-level authentication.
Don't log sensitive data. Request logging is essential for debugging and security monitoring, but logging request bodies that contain passwords, tokens, or personal data creates a secondary exposure risk. Redact sensitive fields before they reach your log pipeline.
6. Minimise Your API's Attack Surface
Every endpoint you expose is a potential attack vector. Reduce the surface area deliberately.
Don't expose endpoints you don't need. Internal APIs used only by your own services shouldn't be publicly accessible. Use network-level controls (VPCs, security groups, API gateways) to enforce this.
Return only the data the client needs. Over-fetching — returning entire database records when only a few fields are needed — exposes more data than necessary and increases the impact of any authorisation failure. Design your response schemas carefully.
Version your API and deprecate old versions. Old API versions accumulate technical debt and security debt. Establish a deprecation policy and enforce it. Running v1 and v2 and v3 of the same API indefinitely means maintaining three attack surfaces.
Disable unused HTTP methods. If an endpoint only supports GET and POST, return 405 Method Not Allowed for PUT, DELETE, and PATCH. Don't leave methods enabled by default.
7. Error Handling and Information Disclosure
Error messages are a surprisingly rich source of information for attackers. Stack traces reveal internal architecture. Detailed database errors reveal schema structure. Verbose authentication errors reveal whether a username exists.
Return generic error messages to clients. "Invalid credentials" is correct. "User not found" or "Incorrect password" are not — they reveal whether the username exists.
Log detailed errors server-side. The information you strip from client responses still needs to be available for debugging. Log it internally with a correlation ID that you can reference when investigating issues.
Use consistent HTTP status codes. 401 Unauthorized for unauthenticated requests. 403 Forbidden for authenticated but unauthorised requests. 404 Not Found for missing resources. 422 Unprocessable Entity for validation failures. Consistent status codes make your API predictable and help clients handle errors correctly.
8. Security Headers and CORS
If your API is consumed by web clients, HTTP security headers matter.
Configure CORS correctly. Don't set Access-Control-Allow-Origin: * on APIs that handle authenticated requests. Specify the exact origins you trust. Validate that the Origin header matches your allowlist before including it in the response.
Set security headers on API responses:
Content-Type: application/json(prevents MIME sniffing)X-Content-Type-Options: nosniffCache-Control: no-storeon responses containing sensitive data
9. Dependency and Infrastructure Security
Your API's security is only as strong as the libraries and infrastructure it runs on.
Keep dependencies updated. Automated dependency scanning should be part of your CI pipeline. Critical CVEs in your dependencies need to be patched quickly — within 24 hours for actively exploited vulnerabilities.
Scan your container images. If you're deploying in containers, scan images for known vulnerabilities before pushing to production. Use minimal base images to reduce the attack surface.
Rotate secrets regularly. API keys, database credentials, and service account tokens should have defined rotation schedules. Use a secrets manager rather than environment variables or config files.
Building Security Into Your API Development Process
The best time to address API security is during design, not after deployment. Threat modelling — systematically thinking through how an attacker might abuse each endpoint — should happen before a line of code is written.
Security review should be part of your code review process. Automated scanning (SAST, DAST, dependency scanning) should run on every pull request. Penetration testing should happen before major releases.
These practices aren't expensive relative to the cost of a breach. They're the baseline for building APIs that you can trust with your customers' data — and your business's reputation.
Explore Topics
Written by
XcodeFactory Team
Content creator and writer sharing insights and stories.
