APIs Security
π APIs Security: Build APIs That Hackers Canβt Break
APIs are the front door of modern applications. πͺ
Mobile apps, web applications, microservices, payment systems, AI platforms, SaaS products, and even internal services communicate through APIs.
That makes APIs one of the most attractive targets for attackers.
A vulnerable API can expose:
- π€ User accounts
- π³ Payment information
- π Access tokens
- ποΈ Database records
- π Personal information
- π’ Internal business data
- π€ AI models and prompts
- βοΈ Cloud infrastructure
And the scary part?
An API can be perfectly functional and still be dangerously insecure.
Security isnβt something you add after building an API. It needs to be part of the APIβs architecture from day one.
Letβs explore how to build APIs that are secure by design, resistant to abuse, observable, and difficult to exploit. π‘οΈ
π§ 1. Understand the API Attack Surface
Before securing an API, understand what youβre protecting.
Consider:
Client
β
Internet
β
API Gateway
β
Authentication
β
Authorization
β
Application
β
Database
β
External Services
Every layer is an attack surface.
An attacker might attempt:
Authentication bypass
β
Authorization abuse
β
Parameter manipulation
β
Injection
β
Business-logic abuse
β
Data extraction
Therefore, API security isnβt just about adding JWT.
It is a defense-in-depth problem.
π 2. Strong Authentication
Authentication answers:
Who are you?
Never rely on:
GET /api/users/123
just because the client has a valid session.
Use appropriate authentication mechanisms such as:
- OAuth 2.0
- OpenID Connect
- Short-lived access tokens
- Secure session cookies
- API keys for service-to-service scenarios
- Mutual TLS for highly sensitive internal communication
β Weak approach
Authorization: Bearer permanent-token
A token that never expires is dangerous.
If stolen, the attacker may have access indefinitely.
β Better
Use short-lived access tokens:
Access Token
β
15 minutes
β
Expires
β
Refresh Token
β
New Access Token
Short token lifetime limits the damage of token theft.
πͺͺ 3. Authorization Is More Important Than Authentication
One of the biggest API security mistakes is assuming:
βThe user is authenticated, therefore they can access the resource.β
Wrong.
Authentication tells you who the user is.
Authorization tells you what they are allowed to do.
Consider:
GET /api/orders/1001
User A is authenticated.
But what happens if User A changes:
1001 β 1002
and receives User Bβs order?
Thatβs an authorization vulnerability, commonly associated with Broken Object Level Authorization (BOLA).
β Dangerous
@order = Order.find(params[:id])
β Safer
@order = current_user.orders.find(params[:id])
The database query itself enforces ownership.
This is much safer than:
if @order.user_id == current_user.id
after retrieving arbitrary records.
π‘οΈ 4. Implement Role-Based Access Control
Different users should have different capabilities.
Example:
Admin
βββ Create users
βββ Delete users
βββ View reports
βββ Manage settings
Manager
βββ View reports
βββ Manage employees
Employee
βββ View own profile
βββ Update own information
Never trust:
{
"role": "admin"
}
sent by the client.
The server must determine the userβs permissions.
π― 5. Validate Every Input
Never trust client input.
Assume everything coming from the network is potentially malicious.
Validate:
- Type
- Length
- Format
- Range
- Encoding
- Allowed values
- Required fields
- Nested structures
β Dangerous
User.where("email = '#{params[:email]}'")
β Better
User.where(email: params[:email])
Use parameterized queries or ORM mechanisms that safely bind values.
π 6. Prevent Injection Attacks
Injection can target:
- SQL
- NoSQL
- Shell commands
- LDAP
- GraphQL
- Template engines
- Search systems
β Donβt do this
User.where("name = '#{params[:name]}'")
β Do this
User.where(name: params[:name])
For raw SQL:
User.where("name = ?", params[:name])
The same principle applies outside SQL.
Never concatenate untrusted input into executable commands.
π¦ 7. Rate Limiting
Even a perfectly authenticated API can be abused.
Imagine:
POST /api/login
An attacker sends:
1 request
10 requests
100 requests
10,000 requests
1,000,000 requests
This can lead to:
- Brute-force attacks
- Credential stuffing
- API abuse
- Resource exhaustion
- Increased infrastructure costs
Implement rate limits.
For example:
Login:
5 attempts / minute / account
Password reset:
3 requests / hour / account
Public API:
100 requests / minute / IP
Expensive operation:
10 requests / minute / user
But donβt blindly rate-limit only by IP.
Attackers can rotate IP addresses.
Consider multiple dimensions:
IP
+
User
+
API Key
+
Account
+
Endpoint
π 8. Protect Against DDoS and Resource Exhaustion
Rate limiting protects individual endpoints.
Infrastructure-level protection should also include:
CDN
β
WAF
β
Load Balancer
β
API Gateway
β
Application
Use:
- Request limits
- Connection limits
- Payload limits
- Timeouts
- Queue limits
- Circuit breakers
- WAF rules
- Autoscaling carefully
Autoscaling alone isnβt a security mechanism.
Otherwise:
Attacker traffic
β
Autoscaling
β
More servers
β
Huge cloud bill πΈ
π¦ 9. Limit Request Payload Size
Never allow unlimited payloads.
β
POST /upload
Content-Length: 500GB
Your server should reject oversized requests before expensive processing.
Example:
client_max_body_size 10M;
Application-level validation should also exist.
Use stricter limits for endpoints that donβt require large payloads.
π 10. Always Use HTTPS
Never send sensitive API traffic over plain HTTP.
Use:
HTTPS
TLS 1.2+
TLS 1.3 preferred
HTTPS protects data in transit from interception and tampering.
Avoid:
http://api.example.com/login
Prefer:
https://api.example.com/login
Also configure:
- Secure cookies
- HttpOnly cookies
- SameSite policies
- HSTS where appropriate
- Strong TLS configuration
πͺ 11. Secure Cookies
If authentication uses cookies, configure them correctly.
Example:
Set-Cookie: session=abc123;
Secure;
HttpOnly;
SameSite=Lax
Secure
Cookie is sent only over HTTPS.
HttpOnly
JavaScript cannot directly read the cookie.
This helps reduce token theft through certain XSS scenarios.
SameSite
Controls cross-site cookie behavior and helps mitigate CSRF.
ποΈ 12. Secure JWT Properly
JWTs are powerfulβbut often misunderstood.
A JWT is not encryption by default.
It is usually signed.
Example:
Header.Payload.Signature
Donβt put secrets inside it:
{
"password": "super-secret-password"
}
Even if signed, the payload may be readable by someone who possesses the token.
Use:
- Short expiration times
- Strong signing keys
- Appropriate algorithms
- Key rotation
- Issuer validation
- Audience validation
- Token revocation strategy where needed
And never accept an algorithm simply because the client says it is acceptable.
π 13. Rotate Secrets and Keys
API keys, JWT signing keys, database credentials, and service secrets shouldnβt live forever.
Implement:
Generate
β
Deploy
β
Monitor
β
Rotate
β
Revoke old key
Never commit:
DATABASE_PASSWORD=supersecret
JWT_SECRET=mysecret
AWS_ACCESS_KEY=xxxxx
to Git.
Use:
- Environment variables
- Secret managers
- Vault systems
- Cloud secret-management services
π§Ή 14. Never Expose Sensitive Information in Responses
A common mistake is returning the entire database object.
β
{
"id": 10,
"name": "John",
"email": "john@example.com",
"password_hash": "...",
"reset_token": "...",
"internal_notes": "..."
}
β
{
"id": 10,
"name": "John",
"email": "john@example.com"
}
Use explicit response serializers.
For example:
render json: {
id: user.id,
name: user.name,
email: user.email
}
Donβt serialize your entire model by default.
π§Ύ 15. Donβt Leak Information Through Error Messages
β Bad
{
"error": "PG::UniqueViolation: duplicate key value violates unique constraint users_email_key"
}
This exposes internal implementation details.
β Better
{
"error": "Email is already registered"
}
For production APIs:
Client β Generic error
Server β Detailed logs
Never send stack traces to users.
π§ͺ 16. Prevent Account Enumeration
Consider:
POST /forgot-password
β
{
"error": "No account exists with this email"
}
An attacker can test millions of emails.
β
{
"message": "If an account exists, reset instructions will be sent."
}
Use consistent responses for sensitive operations.
π§± 17. Use Security Headers
Depending on your API architecture, useful HTTP security headers can include:
Strict-Transport-Security
Content-Security-Policy
X-Content-Type-Options
Referrer-Policy
Cache-Control
Not every header is equally relevant to every API.
The important principle is:
Configure headers according to the resources and clients your API actually serves.
π 18. Protect CORS
CORS is frequently misunderstood.
β Dangerous
Access-Control-Allow-Origin: *
This can be inappropriate for authenticated browser APIs.
Instead, explicitly allow trusted origins:
https://app.example.com
https://admin.example.com
Also be careful with:
Access-Control-Allow-Credentials: true
Never combine permissive credentialed CORS with uncontrolled origins.
π΅οΈ 19. Logging and Monitoring
Security without visibility is incomplete.
Log important security events:
Login failure
Login success
Password reset
Permission denied
Token refresh
Suspicious API usage
Rate-limit violation
Admin actions
Example:
{
"event": "authorization_denied",
"user_id": 123,
"endpoint": "/api/orders/1002",
"timestamp": "2026-08-18T14:30:00Z"
}
But never log secrets.
Avoid:
Authorization: Bearer eyJhbGci...
password=...
credit_card=...
Use structured logging and centralized monitoring.
π¨ 20. Detect Suspicious Behavior
Donβt only ask:
βIs this request authenticated?β
Also ask:
βDoes this behavior look normal?β
For example:
User normally:
10 requests/minute
Suddenly:
20,000 requests/minute
Trigger:
Rate limit
β
Alert
β
Temporary restriction
β
Security investigation
Behavior-based controls can detect abuse that traditional authentication wonβt catch.
π 21. API Versioning
Avoid breaking clients unexpectedly.
Use:
/api/v1/users
/api/v2/users
Security fixes should not become impossible because old clients depend on vulnerable behavior.
Define:
- Deprecation policies
- Supported versions
- Sunset dates
- Migration strategies
Remove obsolete API versions.
𧬠22. Secure Internal APIs Too
A common mistake:
βItβs internal, so itβs safe.β
No.
Internal APIs can be attacked after an attacker compromises:
- A server
- A container
- A cloud account
- A service credential
- A developer machine
Use:
Service A
β
Authentication
β
Authorization
β
Service B
Consider:
- mTLS
- Service identities
- Short-lived credentials
- Network segmentation
- Least privilege
π§° 23. API Gateway as a Security Layer
A gateway can centralize:
TLS termination
Authentication
Rate limiting
WAF
Request validation
Routing
Logging
API quotas
Architecture:
Internet
β
βΌ
βββββββββββ
β CDN β
ββββββ¬βββββ
β
βΌ
βββββββββββ
β WAF β
ββββββ¬βββββ
β
βΌ
βββββββββββββββββ
β API Gateway β
βββββββββ¬ββββββββ
β
βββββββββββΌββββββββββ
βΌ βΌ βΌ
Service A Service B Service C
β β β
βββββββββββΌββββββββββ
βΌ
Database
π§ 24. Donβt Trust Client-Side Validation
Suppose your frontend says:
if (amount <= 1000) {
submit();
}
An attacker can simply call the API directly:
POST /api/payment
{
"amount": 999999999
}
The server must enforce:
validates :amount,
numericality: {
greater_than: 0,
less_than_or_equal_to: 1000
}
Frontend validation is for user experience.
Backend validation is for security.
π° 25. Protect Business Logic
Some of the most dangerous vulnerabilities arenβt technicalβtheyβre logical.
Imagine:
Product price = βΉ10,000
Client sends:
{
"price": 1
}
If the server trusts it:
βΉ10,000 β βΉ1 π±
Never trust client-controlled business values.
Instead:
Client
β
product_id
β
Server
β
Database price
β
Calculate total
The server should calculate critical values.
π 26. Make Critical Operations Idempotent
Consider a payment API:
POST /api/payment
Network failure occurs.
Client retries.
Without idempotency:
Payment #1 β βΉ10,000
Payment #2 β βΉ10,000
πΈ Customer gets charged twice.
Use an idempotency key:
Idempotency-Key: 7f91a2...
The server stores the result associated with that key.
Retry:
Same key
β
Existing result
β
Return previous response
This is essential for payment and other critical operations.
𧨠27. Avoid Mass Assignment Vulnerabilities
Suppose your API accepts:
{
"name": "John",
"email": "john@example.com",
"is_admin": true
}
If your application blindly assigns all parameters:
User.update(params)
you could accidentally allow privilege escalation.
β Use strong parameter allowlists
params.require(:user).permit(
:name,
:email
)
Never allow security-sensitive fields unless explicitly required and authorized.
π€ 28. Control File Uploads
File upload APIs are extremely sensitive.
Donβt blindly trust:
filename
extension
MIME type
Implement:
- File size limits
- Allowed file types
- Content validation
- Malware scanning where appropriate
- Randomized storage names
- Storage outside executable directories
- Access control
- Download authorization
Never assume:
photo.jpg
is actually an image.
ποΈ 29. Secure Database Access
Your API shouldnβt connect to the database using a superuser.
Use:
API
β
Application DB User
β
Only required permissions
Apply least privilege.
If the API only needs:
SELECT
INSERT
UPDATE
donβt give it unrestricted administrative privileges.
π§© 30. Dependency Security
Your API can be secure while your dependencies arenβt.
Regularly scan:
Ruby gems
npm packages
Python packages
Docker images
OS packages
Use:
- Dependency lockfiles
- Automated vulnerability scanning
- Regular updates
- Software composition analysis
- Container image scanning
Supply-chain attacks are increasingly important.
π§ͺ 31. Security Testing
Donβt wait for hackers to find vulnerabilities.
Test your API continuously.
Automated tests
Unit Tests
Integration Tests
Authorization Tests
Security Tests
Dependency Scans
Dynamic testing
Use API security testing tools to discover:
Injection
Broken authorization
Authentication issues
Unexpected responses
Rate-limit failures
π§± 32. Defense in Depth
The strongest API isnβt protected by one mechanism.
It looks like:
HTTPS
β
WAF
β
Rate Limiting
β
Authentication
β
Authorization
β
Input Validation
β
Business Logic Validation
β
Database Least Privilege
β
Monitoring
β
Incident Response
If one layer fails, another layer should still protect the system.
π« 33. Common API Security Mistakes
β Mistake #1: Using only JWT
JWT solves only part of the authentication problem.
You still need:
Authorization
Validation
Rate limiting
Monitoring
Secure storage
β Mistake #2: Trusting IDs from clients
GET /users/123
doesnβt mean the requester owns user 123.
Always enforce authorization.
β Mistake #3: Returning entire database objects
render json: User.find(params[:id])
can expose fields you never intended to expose.
Use explicit serializers.
β Mistake #4: Logging tokens
Authorization: Bearer eyJ...
π¨ Huge mistake.
Logs frequently have broad access and long retention.
β Mistake #5: No rate limiting
Even authenticated endpoints can be abused.
β Mistake #6: Hardcoding secrets
JWT_SECRET = "my-super-secret"
Never.
β Mistake #7: Detailed production errors
Never expose:
Stack traces
SQL queries
File paths
Framework versions
Database errors
Internal service names
β Mistake #8: Assuming internal APIs are safe
Internal β trusted.
β Mistake #9: Relying on frontend security
Anything running in the browser can be modified by the attacker.
β Mistake #10: Forgetting old API versions
An abandoned:
/api/v1
can become the weakest entry point into your infrastructure.
π‘οΈ 34. A Production-Grade Secure API Architecture
A mature architecture might look like:
π INTERNET
β
βΌ
βββββββββββββββ
β CDN β
ββββββββ¬βββββββ
β
βΌ
βββββββββββββββ
β WAF β
ββββββββ¬βββββββ
β
βΌ
βββββββββββββββ
β API Gateway β
β β
β Rate Limit β
β Auth β
β Validation β
ββββββββ¬βββββββ
β
βΌ
ββββββββββββββββββββ
β Load Balancer β
ββββββββββ¬ββββββββββ
β
ββββββββββββββββΌβββββββββββββββ
βΌ βΌ βΌ
βββββββββββ βββββββββββ βββββββββββ
β API #1 β β API #2 β β API #3 β
ββββββ¬βββββ ββββββ¬βββββ ββββββ¬βββββ
β β β
ββββββββββββββββΌβββββββββββββββ
βΌ
βββββββββββββββ
β PostgreSQL β
βββββββββββββββ
β
βββββββββββββββ
β Redis β
βββββββββββββββ
βββββββββββββββββββββββββββ
β Security Monitoring β
β Logs + Alerts + SIEM β
βββββββββββββββββββββββββββ
π 35. The API Security Golden Rules
If you remember nothing else, remember these:
π Authentication
Verify who the caller is.
π Authorization
Verify what they are allowed to do.
π§Ή Validation
Never trust input.
π¦ Rate Limiting
Assume every endpoint can be abused.
π Encryption
Protect data in transit and at rest.
π― Least Privilege
Give users and services only the permissions they need.
π΅οΈ Monitoring
Know what your API is doing.
π§ͺ Testing
Continuously attempt to break your own API before someone else does.
π§ Business Logic
Never trust client-controlled prices, roles, permissions, balances, or security-sensitive state.
π₯ Final Thought
API security isnβt about making an API βunbreakable.β
No internet-facing system can honestly promise that.
The real goal is to make your API:
Difficult to exploit, difficult to abuse, easy to monitor, and quick to recover when something goes wrong.
The strongest API architecture assumes that:
Users can lie.
Clients can be modified.
Tokens can be stolen.
Requests can be replayed.
Dependencies can contain vulnerabilities.
Internal services can be compromised.
Attackers will eventually discover your endpoints.
Your job isnβt to hope they donβt.
Your job is to make sure that when they do try:
Authentication stops them π
Authorization limits them π
Validation rejects them π§Ή
Rate limiting slows them π¦
WAF filters them π‘οΈ
Monitoring detects them ποΈ
Least privilege limits the blast radius π―
Backups and recovery minimize the damage π
Secure APIs arenβt built by adding one security feature.
Theyβre built by creating multiple independent layers of protection where every layer assumes the previous one might fail.
π Build APIs like youβre already being attackedβbecause eventually, you will be.
#APISecurity #CyberSecurity #BackendDevelopment #SoftwareEngineering #WebDevelopment
© Lakhveer Singh Rajput - Blogs. All Rights Reserved.