Unauthenticated. Unauthenticated rate limits should be stricter to prevent abuse.
from jsonschema import validate, ValidationError
" required " : [ " customer_id " , " items " ],
" customer_id " : { " type " : " integer " , " minimum " : 1 },
" required " : [ " product_id " , " quantity " ],
" product_id " : { " type " : " integer " , " minimum " : 1 },
" quantity " : { " type " : " integer " , " minimum " : 1 , " maximum " : 999 }
" notes " : { " type " : " string " , " maxLength " : 1000 }
" additionalProperties " : False
@app.route ( ' /api/orders ' , methods = [ ' POST ' ])
@require_bearer_token ([ ' write:orders ' ])
data = request.get_json()
validate(data, CREATE_ORDER_SCHEMA )
except ValidationError as e:
return jsonify({ " error " : f "Validation failed: { e.message } " }), 400
def validate_pagination ( request ):
"""Validate and sanitize pagination parameters."""
limit = int (request.args.get( ' limit ' , 20 ))
offset = int (request.args.get( ' offset ' , 0 ))
except ( ValueError , TypeError ):
return None , None , " limit and offset must be integers "
limit = max ( 1 , min (limit, 100 )) # Clamp to [1, 100]
offset = max ( 0 , min (offset, 10000 )) # Cap at 10000
return limit, offset, None
from flask_cors import CORS
# SAFE: explicit allowlist
' https://app.example.com ' ,
' https://admin.example.com '
CORS(app, origins = ALLOWED_ORIGINS , methods = [ ' GET ' , ' POST ' , ' PUT ' , ' DELETE ' ],
allow_headers = [ ' Authorization ' , ' Content-Type ' ],
supports_credentials = True )
# VULNERABLE: wildcard with credentials
# CORS(app, origins='*', supports_credentials=True) # BLOCKED by browsers
Strategy Example Pros Cons URL path /api/v1/ordersSimple, visible URL changes Header Accept: application/vnd.api.v1+jsonClean URLs Hidden, harder to test Query parameter /api/orders?version=1Easy to add Cache-busting issues
# URL path versioning (recommended for most APIs)
@app.route ( ' /api/v1/orders ' )
return jsonify(legacy_format(orders))
@app.route ( ' /api/v2/orders ' )
return jsonify(modern_format(orders))
# Sparse fieldsets (JSON:API pattern)
@app.route ( ' /api/v1/users ' )
fields = request.args.get( ' fields ' , '' ).split( ' , ' )
filtered = [{k: u[k] for k in fields if k in u} for u in users]
# Offset pagination (simple, but slow at high offsets)
@app.route ( ' /api/orders ' )
limit = min ( int (request.args.get( ' limit ' , 20 )), 100 )
offset = max ( int (request.args.get( ' offset ' , 0 )), 0 )
orders = db.query( " SELECT * FROM orders ORDER BY id LIMIT %s OFFSET %s " , (limit, offset))
return jsonify({ " data " : orders, " offset " : offset, " limit " : limit})
# Cursor pagination (efficient at any position)
@app.route ( ' /api/orders ' )
limit = min ( int (request.args.get( ' limit ' , 20 )), 100 )
cursor = request.args.get( ' cursor ' )
" SELECT * FROM orders WHERE id > %s ORDER BY id LIMIT %s " ,
" SELECT * FROM orders ORDER BY id LIMIT %s " ,
next_cursor = orders[ - 1 ][ ' id ' ] if orders else None
" next_cursor " : next_cursor
Method Performance at page 10000 Consistency on inserts/deletes URL bookmarkable Offset Slow (scans 10000+ rows) Unstable (rows shift) Yes Cursor Fast (index lookup) Stable (deterministic) No (cursor changes)
from hashlib import sha256
@app.route ( ' /api/orders ' , methods = [ ' POST ' ])
@require_bearer_token ([ ' write:orders ' ])
idempotency_key = request.headers.get( ' Idempotency-Key ' )
return jsonify({ " error " : " Idempotency-Key header required " }), 400
# Check if this key was already processed
existing = redis.get( f "idempotency: { idempotency_key } " )
return jsonify(json.loads(existing)), 200 # Return original response
order = create_order_from_request(request)
response = jsonify(order)
# Store the response for future retries (TTL = 24h)
f "idempotency: { idempotency_key } " ,
def verify_webhook_signature ( request , secret ):
signature = request.headers.get( ' X-Webhook-Signature ' )
payload = request.get_data()
return hmac.compare_digest(signature, expected)
@app.route ( ' /webhooks/payment ' , methods = [ ' POST ' ])
if not verify_webhook_signature(request, WEBHOOK_SECRET ):
return jsonify({ " error " : " Invalid signature " }), 401
event = request.get_json()
process_payment_event(event)
return jsonify({ " status " : " ok " }), 200
API Gateway responsibilities:
1. Authentication (validate tokens, API keys)
2. Rate limiting (per-client, per-route)
3. Request transformation (headers, body)
4. Response transformation (filtering, formatting)
5. Load balancing (round-robin, least connections)
6. Circuit breaking (fail fast on downstream failures)
7. Request logging and monitoring
Option Complexity Customization Use Case Kong Medium High (Lua plugins) Large-scale, extensible AWS API Gateway Low Medium AWS ecosystem Envoy High Very High Service mesh, gRPC Nginx Medium High General purpose, lightweight Traefik Low Medium Container environments
# Limit maximum query depth to prevent complex/nested queries
def validate_query_depth ( query_ast , current_depth = 0 ):
if current_depth > MAX_QUERY_DEPTH :
raise GraphQLDepthError( f "Query depth exceeds maximum of {MAX_QUERY_DEPTH} " )
for field in query_ast.selection_set.selections:
if hasattr (field, ' selection_set ' ) and field.selection_set:
validate_query_depth(field, current_depth + 1 )
# Assign cost to each field and limit total query complexity
def calculate_complexity ( query_ast ):
for field in query_ast.selection_set.selections:
field_name = field.name.value
cost = FIELD_COSTS .get(field_name, 1 )
if hasattr (field, ' selection_set ' ) and field.selection_set:
cost *= calculate_complexity(field)
// Disable introspection in production
const server = new ApolloServer ({
introspection : process.env. NODE_ENV !== ' production ' ,
# openapi.yaml security schemes
authorizationUrl : https://auth.example.com/authorize
tokenUrl : https://auth.example.com/token
write:orders : Create/update orders
- BearerAuth : [ read:orders ]
Client-side validation improves UX but provides zero security. An attacker can send any payload Directly to your API. Always validate on the server, regardless of what the client does.
Reflecting the Origin header without validation allows any origin to make authenticated requests. Always use an explicit allowlist.
Using sequential integer IDs (1, 2, 3…) allows attackers to enumerate resources. Use UUIDs or Hashids for public identifiers.
Without rate limiting, attackers can brute-force credentials, enumerate usernames, and perform Credential stuffing attacks. Rate limit all authentication endpoints.
Stack traces reveal implementation details (framework, library versions, file paths) that help Attackers craft targeted exploits. Return generic error messages in production; log details Server-side.
A[Api Security] --> B[Key Concepts]
A --> D[Practical Applications]
B --> E[Fundamental definitions]
D --> G[Real-world usage]
This topic covers the essential concepts and techniques related to api security, including key principles and practical applications.
Key concepts include:
core concepts and definitions key principles and frameworks practical applications common techniques and methods evaluation and critical analysis A thorough understanding of these concepts, combined with regular practice and review, is essential for mastery of this topic.
Worked examples demonstrating the application of key concepts are covered in the detailed sub-pages linked above.