Api security
π Professional Multi-Agent Skills
npx -y skills add kinhluan/skills --skill api-securityAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
API security best practices, OWASP API Security Top 10 2023, authentication, authorization, rate limiting, and API gateway security. Use when securing REST APIs, GraphQL, OAuth 2.0 implementations, or API gateway configurations.
SKILL.md
15.6 KB, as published. Nobody here has run it
API Security
Comprehensive API security guidance covering OWASP API Security Top 10 2023, authentication, authorization, rate limiting, and secure API design.
"APIs are the new perimeter. Secure them like your front door."
π― When to Use
- Securing REST/GraphQL APIs
- Implementing OAuth 2.0 / JWT authentication
- Configuring API gateways (Kong, AWS API Gateway, Azure API Management)
- Designing secure API architectures
- Rate limiting and throttling
- API vulnerability assessment
- API documentation security review
π‘οΈ OWASP API Security Top 10 2023
API1:2023 β Broken Object Level Authorization (BOLA)
What: Users can access objects they shouldn't by manipulating IDs in API requests.
Vulnerable:
GET /api/orders/12345
# No check if current user owns order 12345
Secure:
from fastapi import Depends, HTTPException
@app.get("/api/orders/{order_id}")
def get_order(
order_id: str,
current_user: User = Depends(get_current_user)
):
order = db.get_order(order_id)
if order.user_id != current_user.id and not current_user.is_admin:
raise HTTPException(403, "Access denied")
return order
Checklist:
- Validate object ownership on every request
- Use random UUIDs instead of sequential IDs
- Implement authorization middleware
- Test with different user contexts
API2:2023 β Broken Authentication
What: Weak authentication mechanisms allow attackers to impersonate users.
Secure JWT Implementation:
import jwt
from datetime import datetime, timedelta
# Token generation
def create_access_token(user_id: str, secret: str) -> str:
payload = {
"sub": user_id,
"iat": datetime.utcnow(),
"exp": datetime.utcnow() + timedelta(minutes=15),
"type": "access"
}
return jwt.encode(payload, secret, algorithm="HS256")
# Token verification
def verify_token(token: str, secret: str) -> dict:
try:
payload = jwt.decode(
token, secret,
algorithms=["HS256"],
options={"require": ["exp", "sub"]}
)
return payload
except jwt.ExpiredSignatureError:
raise HTTPException(401, "Token expired")
except jwt.InvalidTokenError:
raise HTTPException(401, "Invalid token")
Secure Session Management:
# Refresh token rotation
def refresh_access_token(refresh_token: str) -> tuple:
payload = verify_token(refresh_token, REFRESH_SECRET)
# Invalidate old refresh token
token_store.revoke(payload["jti"])
# Issue new pair
new_access = create_access_token(payload["sub"], ACCESS_SECRET)
new_refresh = create_refresh_token(payload["sub"])
return new_access, new_refresh
Checklist:
- Short-lived access tokens (15 min)
- Refresh token rotation
- Secure token storage (HttpOnly cookies)
- MFA for sensitive operations
- Account lockout after failed attempts
- Strong password policy (NIST 800-63b)
API3:2023 β Broken Object Property Level Authorization
What: Exposing sensitive fields or allowing mass assignment.
Vulnerable:
# Returns ALL user fields including password_hash
@app.get("/api/users/{user_id}")
def get_user(user_id: str):
return db.users.find_one({"_id": user_id})
Secure:
from pydantic import BaseModel
class UserPublic(BaseModel):
id: str
username: str
display_name: str
created_at: datetime
# password_hash is NOT included
@app.get("/api/users/{user_id}", response_model=UserPublic)
def get_user(user_id: str):
user = db.users.find_one({"_id": user_id})
return UserPublic(**user)
# Prevent mass assignment
class UserUpdate(BaseModel):
display_name: str | None = None
email: str | None = None
# role is NOT included β cannot be changed via API
API4:2023 β Unrestricted Resource Consumption
What: No rate limiting allows DoS via resource exhaustion.
Rate Limiting Implementation:
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
@app.post("/api/login")
@limiter.limit("5/minute")
def login(credentials: LoginRequest):
...
@app.get("/api/data")
@limiter.limit("100/minute")
def get_data():
...
# Per-user rate limiting
@app.get("/api/expensive")
@limiter.limit("10/hour", per_method=True, key_func=lambda: current_user.id)
def expensive_operation():
...
API Gateway Rate Limiting (Kong):
plugins:
- name: rate-limiting
config:
minute: 100
policy: redis
redis_host: redis
fault_tolerant: true
hide_client_headers: false
API5:2023 β Broken Function Level Authorization
What: Missing authorization checks on administrative functions.
Secure:
from functools import wraps
def require_admin(f):
@wraps(f)
async def wrapper(*args, **kwargs):
user = await get_current_user()
if not user.is_admin:
raise HTTPException(403, "Admin access required")
return await f(*args, **kwargs)
return wrapper
@app.delete("/api/users/{user_id}")
@require_admin
def delete_user(user_id: str):
db.users.delete_one({"_id": user_id})
API6:2023 β Unrestricted Access to Sensitive Business Flows
What: Automated abuse of business logic (e.g., ticket scalping, vote manipulation).
Mitigations:
# CAPTCHA for high-risk operations
@app.post("/api/purchase")
@require_captcha
def purchase_ticket(request: PurchaseRequest):
...
# Device fingerprinting
@app.post("/api/vote")
def cast_vote(request: VoteRequest):
fingerprint = generate_device_fingerprint(request)
if vote_store.has_voted_today(request.poll_id, fingerprint):
raise HTTPException(429, "Already voted today")
...
# Progressive delays
@app.post("/api/resend-verification")
def resend_verification(email: str):
count = verification_store.get_count(email)
delay = min(2 ** count, 3600) # Exponential backoff, max 1 hour
time.sleep(delay)
...
API7:2023 β Server Side Request Forgery (SSRF)
What: Server makes requests to attacker-controlled URLs.
Secure:
import urllib.parse
import ipaddress
def validate_url(url: str) -> bool:
parsed = urllib.parse.urlparse(url)
# Whitelist schemes
if parsed.scheme not in {"http", "https"}:
return False
# Block internal IPs
blocked_hosts = {
"localhost", "127.0.0.1", "0.0.0.0", "::1",
"169.254.169.254" # AWS metadata
}
if parsed.hostname in blocked_hosts:
return False
# Resolve and check IP
try:
ip = socket.getaddrinfo(parsed.hostname, None)[0][4][0]
if ipaddress.ip_address(ip).is_private:
return False
except:
return False
return True
@app.post("/api/webhook")
def set_webhook(request: WebhookRequest):
if not validate_url(request.url):
raise HTTPException(400, "Invalid URL")
# ...
API8:2023 β Security Misconfiguration
What: Default configs, verbose errors, unnecessary features.
Secure Configuration:
# FastAPI production config
app = FastAPI(
debug=False,
docs_url=None, # Disable Swagger in production
redoc_url=None, # Disable ReDoc in production
openapi_url=None # Disable OpenAPI schema
)
# Security headers
@app.middleware("http")
async def add_security_headers(request, call_next):
response = await call_next(request)
response.headers["Content-Security-Policy"] = "default-src 'self'"
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
return response
# Generic error messages
@app.exception_handler(Exception)
async def handle_error(request, exc):
logger.error(f"Error: {exc}", exc_info=True)
return JSONResponse(
status_code=500,
content={"error": "Internal server error", "request_id": request.state.request_id}
)
API9:2023 β Improper Inventory Management
What: Shadow APIs, deprecated versions, and undocumented endpoints.
Mitigations:
# API versioning strategy
/api/v1/users # Current stable
/api/v2/users # Latest (may have breaking changes)
/api/v1/deprecated # Marked for removal
# Sunset headers
Sunset: Sat, 31 Dec 2024 23:59:59 GMT
Deprecation: true
# API discovery and documentation
@app.get("/api/versions")
def list_versions():
return {
"current": "v2",
"supported": ["v1", "v2"],
"deprecated": ["v0"],
"sunset_dates": {"v1": "2024-12-31"}
}
# API inventory scanning
# Use tools like: OWASP ZAP, Burp Suite, Postman
API10:2023 β Unsafe Consumption of APIs
What: Trusting data from third-party APIs without validation.
Secure:
import requests
from pydantic import BaseModel, ValidationError
class ThirdPartyUser(BaseModel):
id: str
email: str
name: str
@app.get("/api/external-user/{user_id}")
def get_external_user(user_id: str):
# Validate input
if not user_id.isalnum():
raise HTTPException(400, "Invalid user ID")
# Call third-party API
try:
response = requests.get(
f"https://api.thirdparty.com/users/{user_id}",
timeout=5,
headers={"Authorization": f"Bearer {API_KEY}"}
)
response.raise_for_status()
except requests.RequestException:
raise HTTPException(502, "External service unavailable")
# Validate response
try:
user = ThirdPartyUser(**response.json())
except ValidationError:
raise HTTPException(502, "Invalid response from external service")
return user
π OAuth 2.0 & JWT Security
OAuth 2.0 Flow Selection
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β OAuth 2.0 Flows β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Authorization Code + PKCE β Mobile/SPAs (recommended) β
β Authorization Code β Web apps with backend β
β Client Credentials β Server-to-server β
β Device Code β Input-constrained devices β
β Implicit β β DEPRECATED, do not use β
β Password β β DEPRECATED, do not use β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
PKCE Implementation
import secrets
import hashlib
import base64
# Step 1: Generate code verifier and challenge
code_verifier = base64.urlsafe_b64encode(
secrets.token_bytes(32)
).rstrip(b'=').decode('ascii')
code_challenge = base64.urlsafe_b64encode(
hashlib.sha256(code_verifier.encode()).digest()
).rstrip(b'=').decode('ascii')
# Step 2: Authorization request
# GET /authorize?response_type=code&client_id=...&redirect_uri=...&code_challenge=...&code_challenge_method=S256
# Step 3: Token exchange
# POST /token
# grant_type=authorization_code&code=...&redirect_uri=...&client_id=...&code_verifier=...
JWT Best Practices
# Use RS256 for asymmetric signing (public key verification)
# Use HS256 only for internal service-to-service
# Key rotation
# Maintain multiple valid keys, rotate every 90 days
# Token binding
# Bind tokens to TLS session or device fingerprint
# Secure claims
"""
Required claims:
- sub: subject (user ID)
- iss: issuer
- aud: audience
- exp: expiration
- iat: issued at
- jti: unique token ID (for revocation)
Forbidden claims:
- Never include passwords or secrets
- Never include PII without encryption
"""
π GraphQL Security
Query Depth Limiting
from graphql.validation import ValidationRule
class QueryDepthLimit(ValidationRule):
def __init__(self, max_depth: int = 10):
self.max_depth = max_depth
def enter_selection_set(self, node, *args):
depth = self._get_depth(node)
if depth > self.max_depth:
raise GraphQLError(f"Query exceeds maximum depth of {self.max_depth}")
# Apollo Server
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [createComplexityLimitRule(1000)],
plugins: [queryDepthLimit(10)]
});
Query Complexity Analysis
# Calculate complexity score
def calculate_complexity(node, depth=0):
score = 1
for child in node.selections:
score += calculate_complexity(child, depth + 1) * (depth + 1)
return score
# Reject overly complex queries
MAX_COMPLEXITY = 1000
@app.post("/graphql")
def graphql_endpoint(query: GraphQLQuery):
ast = parse(query)
complexity = calculate_complexity(ast)
if complexity > MAX_COMPLEXITY:
raise HTTPException(400, f"Query complexity {complexity} exceeds limit {MAX_COMPLEXITY}")
π API Security Checklist
Authentication
- Strong authentication (OAuth 2.0 + PKCE or mTLS)
- Short-lived tokens with rotation
- MFA for sensitive operations
- Secure token storage (HttpOnly, Secure, SameSite)
Authorization
- Object-level authorization on every endpoint
- Function-level authorization for admin operations
- Principle of least privilege
- Regular access reviews
Input Validation
- Strict schema validation (OpenAPI, JSON Schema)
- Parameterized queries (prevent injection)
- File upload restrictions (type, size, content)
- URL validation (prevent SSRF)
Output Protection
- Response filtering (prevent data leakage)
- Error message sanitization
- CORS properly configured
- Security headers on all responses
Rate Limiting
- Per-IP rate limiting
- Per-user rate limiting
- Per-endpoint rate limiting
- Progressive delays for repeated failures
Monitoring
- API access logging
- Anomaly detection
- Failed authentication alerting
- Rate limit violation tracking
π οΈ Tools
| Tool | Purpose |
|---|---|
| OWASP ZAP | API security scanning |
| Burp Suite | Web/API penetration testing |
| Postman | API testing & documentation |
| Insomnia | API testing |
| Kong | API gateway |
| AWS API Gateway | Cloud API gateway |
| Azure API Management | Cloud API gateway |
| JWT.io | JWT debugging |
| OAuth.tools | OAuth flow testing |