Back to Blog

API Security Testing: A Practical Guide for Developers

Learn API security testing with practical examples. Cover authentication flaws, injection attacks, and data exposure prevention.

RESTK Team
14 min read

APIs are the connective tissue of modern software. They power mobile apps, connect microservices, enable third-party integrations, and expose business logic to the outside world. That ubiquity makes them the single most attractive attack vector for malicious actors.

According to the OWASP API Security Top 10, the majority of API breaches stem from predictable, preventable vulnerabilities: broken authentication, excessive data exposure, lack of rate limiting, and injection attacks. These are not exotic zero-day exploits. They are configuration mistakes and missing validation checks that any developer can test for -- if they know what to look for.

This guide covers the practical security tests every developer should run against their APIs, with concrete examples you can execute today.

Why API Security Testing Matters

Traditional web application security testing focuses on browser-based vulnerabilities: XSS in rendered HTML, CSRF tokens in forms, cookie security. API security testing is different. APIs communicate in structured data (JSON, XML, Protocol Buffers), are often stateless, and expose functionality directly without a browser intermediary.

This creates a distinct threat model:

  • No browser protections. APIs do not benefit from browser-enforced security policies like Content Security Policy or SameSite cookies by default.
  • Direct access to business logic. An API endpoint that transfers funds or deletes accounts is one HTTP request away from any client that has the URL.
  • Machine-speed attacks. Without rate limiting, an attacker can send thousands of requests per second to brute-force credentials or enumerate resources.
  • Data exposure at scale. A single misconfigured list endpoint can leak millions of records.

The OWASP API Security Top 10 identifies the most critical risks: Broken Object Level Authorization (BOLA), Broken Authentication, Broken Object Property Level Authorization, Unrestricted Resource Consumption, Broken Function Level Authorization, Server-Side Request Forgery, Security Misconfiguration, Lack of Protection from Automated Threats, Improper Asset Management, and Unsafe Consumption of APIs.

You do not need to test for all of these at once. Start with the fundamentals below and expand your coverage over time.

Authentication and Authorization Testing

Broken authentication is consistently ranked as one of the most dangerous API vulnerabilities. If an attacker can bypass your auth, every other security control is irrelevant.

Tests to Run

Missing authentication headers:

# Send a request to a protected endpoint without any auth
curl -X GET https://api.example.com/v1/users/me \
  -H "Content-Type: application/json"
# Expected: 401 Unauthorized

Invalid and expired tokens:

# Expired JWT token
curl -X GET https://api.example.com/v1/users/me \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZXhwIjoxNjAwMDAwMDAwfQ.invalid"

# Malformed token
curl -X GET https://api.example.com/v1/users/me \
  -H "Authorization: Bearer not-a-real-token"

# Both should return 401 Unauthorized

Broken Object Level Authorization (BOLA):

# Authenticate as User A, then try to access User B's data
curl -X GET https://api.example.com/v1/users/USER_B_ID/profile \
  -H "Authorization: Bearer USER_A_TOKEN"
# Expected: 403 Forbidden (not 200 OK with User B's data)

JWT validation weaknesses:

  • Change the alg field in the JWT header to none and send an unsigned token
  • Modify the payload claims (e.g., change role: "user" to role: "admin") without re-signing
  • Use an expired token and verify the server rejects it

OAuth flow validation:

  • Submit an authorization code more than once (replay attack)
  • Use a redirect URI that differs from the registered one
  • Attempt token exchange with a mismatched PKCE code verifier

For teams managing authentication tokens across environments, storing credentials securely is essential. See our guide on environment variables best practices for strategies on managing auth tokens without exposing them.

What to Look For

Any response that returns 200 OK when it should return 401 or 403 is a critical finding. Also watch for error messages that leak information -- a response like "error": "User not found" versus "error": "Invalid password" tells an attacker whether a username exists.

Input Validation and Injection Testing

APIs that trust client input without validation are vulnerable to injection attacks. Even if your front-end validates input, the API must enforce its own validation -- attackers bypass front-ends entirely.

SQL Injection

# Attempt SQL injection in a query parameter
curl -X GET "https://api.example.com/v1/users?search=admin'%20OR%201=1--" \
  -H "Authorization: Bearer $TOKEN"

# Attempt SQL injection in a JSON body
curl -X POST https://api.example.com/v1/users/search \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"username": "admin\" OR \"1\"=\"1"}'
# Expected: 400 Bad Request (not a data dump)

XSS via API Responses

# Submit a script tag in a user-controlled field
curl -X POST https://api.example.com/v1/comments \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"body": "<script>alert(document.cookie)</script>"}'
# The API should sanitize or escape the input

Even if the API does not render HTML, stored XSS payloads in API responses can be executed when a front-end application displays the data. The API should sanitize input at the boundary.

Parameter Tampering

# Try to set admin privileges via a hidden field
curl -X POST https://api.example.com/v1/users \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "New User", "email": "[email protected]", "role": "admin"}'
# Expected: role field should be ignored or return 403

# Manipulate IDs in request bodies
curl -X PUT https://api.example.com/v1/orders/123 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id": "456", "status": "shipped"}'
# Expected: server should use the URL parameter ID, not the body ID

Request Body Manipulation

  • Send a Content-Type: application/json header with XML in the body
  • Send extremely large payloads (1 MB+ JSON bodies) to test for denial-of-service
  • Include unexpected nested objects or arrays where the API expects flat values
  • Submit null bytes, unicode control characters, and other encoding edge cases

Rate Limiting and Throttle Testing

Without rate limiting, your API is vulnerable to brute-force attacks, credential stuffing, and resource exhaustion.

Tests to Run

# Test login endpoint rate limiting
for i in $(seq 1 20); do
  curl -s -o /dev/null -w "%{http_code}\n" \
    -X POST https://api.example.com/v1/auth/login \
    -H "Content-Type: application/json" \
    -d '{"email": "[email protected]", "password": "wrong-password-'$i'"}'
done
# Expected: After N attempts, responses should change to 429 Too Many Requests

What to verify:

  • The API returns 429 Too Many Requests after exceeding the limit
  • The Retry-After header is present and contains a reasonable value
  • Rate limits apply per-user or per-IP, not globally
  • Different endpoints have appropriate limits (login should be stricter than read endpoints)
  • Rate limits cannot be bypassed by rotating headers like X-Forwarded-For

Check rate limit headers in responses:

HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1706140800

If these headers are missing, the API likely does not implement rate limiting -- that is a finding worth reporting.

Data Exposure Testing

APIs frequently return more data than the client needs. This excessive data exposure is a top OWASP API risk because developers often serialize entire database objects instead of selecting specific fields.

Tests to Run

Check for excessive fields in responses:

# Request a user profile and inspect the response
curl -X GET https://api.example.com/v1/users/me \
  -H "Authorization: Bearer $TOKEN" | jq .

Look for fields that should not be exposed to the client:

  • Internal database IDs (_id, internal_id)
  • Password hashes (password_hash, password_digest)
  • Infrastructure details (created_by_service, database_shard)
  • Other users' data in nested objects
  • Soft-delete flags (is_deleted, deleted_at)
  • Debug or internal metadata

Check list endpoints for data leakage:

# Request a list and check if it returns other users' records
curl -X GET "https://api.example.com/v1/orders" \
  -H "Authorization: Bearer $USER_A_TOKEN" | jq '.[].user_id' | sort -u
# Expected: only User A's ID should appear

Inspect error responses for information disclosure:

# Trigger an error and check the response
curl -X GET "https://api.example.com/v1/users/nonexistent-id" \
  -H "Authorization: Bearer $TOKEN"

Verbose error messages are a goldmine for attackers. Watch for:

  • Stack traces in production responses
  • Database error messages (SQLSTATE[42S02]: Table 'users' not found)
  • Internal file paths (/var/www/app/src/controllers/UserController.js:42)
  • Framework and version information (Express 4.18.2, Django 4.2)

The API should return generic error messages in production: "error": "Resource not found" -- not the underlying implementation details.

For a deeper look at keeping sensitive data out of your API workflow entirely, read Privacy-First API Development.

CORS and Header Security Testing

Cross-Origin Resource Sharing misconfigurations are among the most common API security issues, and they are easy to test for.

CORS Tests

# Test with a malicious origin
curl -X OPTIONS https://api.example.com/v1/users \
  -H "Origin: https://evil-site.com" \
  -H "Access-Control-Request-Method: GET" \
  -v 2>&1 | grep -i "access-control"

# Expected: Origin should NOT be reflected back if it's not whitelisted
# Dangerous: Access-Control-Allow-Origin: https://evil-site.com
# Dangerous: Access-Control-Allow-Origin: *

What to verify:

  • Access-Control-Allow-Origin does not use a wildcard (*) for authenticated endpoints
  • The server does not blindly reflect the Origin header value
  • Access-Control-Allow-Credentials: true is never combined with Access-Control-Allow-Origin: *
  • Preflight responses do not allow dangerous methods (PUT, DELETE) from untrusted origins

Security Headers

curl -I https://api.example.com/v1/users \
  -H "Authorization: Bearer $TOKEN"

Check for the presence of these security headers:

HeaderExpected ValuePurpose
X-Content-Type-OptionsnosniffPrevents MIME-type sniffing
X-Frame-OptionsDENY or SAMEORIGINPrevents clickjacking
Strict-Transport-Securitymax-age=31536000; includeSubDomainsEnforces HTTPS
Cache-Controlno-store (for sensitive data)Prevents caching of sensitive responses
Content-TypeCorrect type with charsetPrevents content-type confusion

Content-Type Validation

# Send a request with a mismatched content type
curl -X POST https://api.example.com/v1/users \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: text/plain" \
  -d '{"name": "Test User"}'
# Expected: 415 Unsupported Media Type (not silent acceptance)

The API should reject requests with unexpected content types rather than attempting to parse them.

Transport Security

All API communication should happen over HTTPS. This is non-negotiable.

Tests to Run

# Attempt HTTP connection (should redirect to HTTPS or refuse)
curl -v http://api.example.com/v1/health 2>&1 | grep -E "< HTTP|Location:"
# Expected: 301 redirect to HTTPS or connection refused

# Check TLS version (reject TLS 1.0 and 1.1)
curl -v --tlsv1.0 https://api.example.com/v1/health 2>&1
# Expected: Connection failure (TLS 1.0 should not be accepted)

# Verify HSTS header
curl -I https://api.example.com/v1/health 2>&1 | grep -i strict-transport
# Expected: Strict-Transport-Security: max-age=31536000; includeSubDomains

What to verify:

  • HTTP requests are either rejected or permanently redirected (301) to HTTPS
  • TLS 1.0 and 1.1 are disabled; only TLS 1.2+ is accepted
  • HSTS header is present with a long max-age value
  • SSL certificates are valid and not self-signed in production
  • Certificate pinning is implemented for mobile API clients

Building Security Testing Into Your Workflow

Security testing should not be a one-time audit. It should be woven into your daily development workflow so that vulnerabilities are caught as they are introduced, not months later in a penetration test.

Test Security in Your API Client

Your API client is the most natural place to run security tests. Instead of only testing happy paths, build a dedicated "Security Tests" folder in your collections:

My API Collection/
  Authentication/
    Valid login
    Invalid credentials
    Expired token
    Missing auth header
    BOLA - access other user's data
  Input Validation/
    SQL injection attempts
    XSS payload in fields
    Oversized request body
    Unexpected content types
  Authorization/
    User accessing admin endpoints
    Accessing other user's resources
    Privilege escalation attempts

Run this security collection after every significant API change. In RESTK, you can execute an entire collection with a single command, making it practical to include security tests in your regular testing cadence. See the full list of RESTK features that support this workflow.

Environment-Based Auth Testing

Use separate environments to test different authorization levels:

// admin-env.json
{
  "baseUrl": "https://staging-api.example.com",
  "authToken": "Bearer ADMIN_TOKEN",
  "testUserId": "admin-user-id"
}

// regular-user-env.json
{
  "baseUrl": "https://staging-api.example.com",
  "authToken": "Bearer USER_TOKEN",
  "testUserId": "regular-user-id"
}

// unauthenticated-env.json
{
  "baseUrl": "https://staging-api.example.com",
  "authToken": "",
  "testUserId": ""
}

Run the same collection against each environment. Requests that succeed with admin credentials should fail with regular user credentials and return 401 with no credentials. This approach catches authorization gaps systematically rather than through ad-hoc testing.

Automate Security Checks in CI

Export your security test collection and run it as a CI/CD pipeline step:

- name: Run API security tests
  run: |
    newman run security-tests.json \
      --environment staging.json \
      --reporters cli,junit \
      --reporter-junit-export security-results.xml

Block deployments when security tests fail. A broken security test is more critical than a broken feature test.

Keeping Credentials Safe While Testing

There is an irony in API security testing: the process of testing security often involves handling the very secrets you are trying to protect. Authentication tokens, API keys, OAuth client secrets, and admin credentials all flow through your testing tool.

This is where the security posture of your API client itself matters. If your tool syncs collections and environments to the cloud by default, your test credentials -- including production tokens and admin keys -- are stored on the vendor's servers. If that vendor is breached, your credentials are exposed.

RESTK takes a fundamentally different approach with a privacy-first architecture:

  • Local storage by default. All requests, responses, collections, environments, and credentials are stored on your local filesystem. Nothing leaves your machine unless you explicitly enable cloud sync.
  • AES-256 encryption. Sensitive values are encrypted at rest using AES-256 encryption. Even if someone gains access to your local storage files, secrets remain encrypted.
  • Native keychain integration. API keys and tokens can be stored in macOS Keychain, protected by the same encryption that guards your system passwords.
  • No content telemetry. RESTK never collects the content of your requests, responses, headers, or environment variables. Your security test payloads -- including any injection strings, auth tokens, and sensitive test data -- stay on your device.

When you are running injection tests with payloads like '; DROP TABLE users;-- or testing with production admin tokens, the last thing you want is that data sitting on a vendor's cloud server. Local-first storage eliminates that risk entirely.

A Security Testing Checklist

Use this as a starting point for your API security testing practice:

  • All endpoints return 401 when no authentication is provided
  • Expired and malformed tokens are rejected with 401
  • Users cannot access other users' resources (BOLA testing)
  • Role-based restrictions are enforced (regular users cannot hit admin endpoints)
  • SQL injection payloads return 400, not data
  • XSS payloads are sanitized or escaped in stored responses
  • Unexpected fields in request bodies are ignored (no mass assignment)
  • Rate limiting is active on authentication endpoints
  • Rate limit headers are present in responses
  • Responses do not contain internal fields (password hashes, internal IDs)
  • Error messages do not expose stack traces or infrastructure details
  • CORS is configured to reject unauthorized origins
  • Security headers are present (HSTS, X-Content-Type-Options, etc.)
  • HTTP requests are redirected to HTTPS
  • TLS 1.0 and 1.1 are disabled

Start with authentication and authorization tests -- they have the highest impact. Add input validation and data exposure tests next. Layer in CORS, headers, and transport security as your coverage matures.

Security testing is not a one-time checkbox. It is a practice that improves with every iteration. Build it into your workflow, automate what you can, and keep your testing tools as secure as the APIs you are protecting.


Related reading: