Introduction: Engineering Secure Financial APIs
While open banking and Banking-as-a-Service create immense commercial opportunity, opening access to core banking systems introduces critical cybersecurity and data privacy vulnerabilities. If an API endpoint is improperly engineered, or if authentication credentials are leaked, malicious actors can gain unauthorized access to millions of consumer bank accounts.
To ensure seamless integration combined with ironclad security, financial institutions adhere strictly to standardized RESTful API design principles and advanced authorization frameworks like OAuth 2.0 and OpenID Connect. This lesson deconstructs REST architectural constraints, HTTP methods, JSON data payloads, OAuth 2.0 token delegation, and end-to-end API security protocols.
Part 1: RESTful API Design Principles in Finance
REST (Representational State Transfer) is an architectural style for designing networked applications, relying on a stateless, client-server, cacheable communications protocol—virtually always implemented over HTTP/HTTPS.
1. Core Principles of RESTful Design
Resource-Based URLs: Endpoints represent distinct financial resources organized hierarchically (e.g., /accounts/{account_id}/transactions).
Standard HTTP Methods: REST utilizes standard HTTP verbs to define operations on financial resources:
-
GET: Retrieve account balances or transaction history (e.g., GET /v1/accounts/12345).
-
POST: Create a new resource or initiate a payment (e.g., POST /v1/payments).
-
PUT / PATCH: Update existing customer profile information.
-
DELETE: Revoke user consent or close an active token session.
Stateless Communication: Every API request from a client must contain all the necessary authentication and context information required to process it, as the server stores zero session state between requests.
2. Standardized Payloads (JSON)
Modern financial APIs exchange structured data payloads formatted in JSON (JavaScript Object Notation), ensuring cross-platform compatibility between mobile applications, web frontends, and core banking mainframes.
Part 2: OAuth 2.0 and OpenID Connect (OIDC)
In open banking, a third-party application must never see or store a user’s master bank password. Security is achieved via OAuth 2.0, an industry-standard authorization framework.
1. The OAuth 2.0 Authorization Flow
Consent Request: A user attempts to link their bank account to a budgeting app. The app redirects the user to their bank’s official secure login portal.
User Authentication: The user logs in directly with their bank (the bank verifies identity; the third-party app never sees the password).
Granting Scopes: The user explicitly approves specific access permissions (Scopes), such as allowing the app to read account balances but prohibiting it from initiating wire transfers.
Authorization Code Exchange: The bank issues a temporary authorization code, which the app exchanges for an Access Token and a Refresh Token.
API Access: The third-party app includes the Access Token in the header of subsequent API requests (e.g., Authorization: Bearer eyJhbGciOi…).
2. OpenID Connect (OIDC)
While OAuth 2.0 handles authorization (what data the app can access), OpenID Connect runs on top of OAuth 2.0 to handle authentication (verifying the identity of the user via cryptographically signed ID tokens like JSON Web Tokens – JWTs).
Part 3: API Security Infrastructure and Threat Mitigation
Protecting financial APIs against sophisticated cyberattacks requires multi-layered defense mechanisms.
1. Mutual TLS (mTLS)
Standard TLS encrypts data in transit between a client and server. Mutual TLS (mTLS) takes security further by requiring both the client (the third-party FinTech app) and the server (the bank) to present cryptographic digital certificates to each other, verifying identity at the network transport layer before any API request is processed.
2. API Gateways and Rate Limiting
Financial institutions route all incoming API traffic through enterprise API Gateways. These gateways enforce strict security policies:
-
Rate Limiting and Throttling: Preventing Denial of Service (DoS) attacks by capping the maximum number of API requests a single client can make per second.
-
Payload Validation: Inspecting incoming JSON payloads to block injection attacks and malformed data before it reaches core banking databases.
1. RESTful API Design Deep-Dive
REST Constraints:
REST Architectural Constraints: 1. Client-Server: - Separation of concerns - Client handles UI, server handles data - Portable, scalable 2. Stateless: - No client state stored on server - Each request contains all needed information - Scalable, reliable 3. Cacheable: - Responses can be cached - Improves performance - Reduces server load 4. Uniform Interface: - Consistent API design - Resource-based URLs - Standard HTTP methods 5. Layered System: - Client cannot tell if connected directly to server - Load balancing, caching, security layers 6. Code on Demand (Optional): - Server can send executable code - Client-side logic
RESTful API Design Principles:
class RESTfulAPI: """ RESTful API Design Principles Implementation """ def __init__(self): self.api_version = 'v1' self.base_path = f'/api/{self.api_version}' # Resource endpoints self.resources = { 'accounts': f'{self.base_path}/accounts', 'transactions': f'{self.base_path}/accounts/{{account_id}}/transactions', 'payments': f'{self.base_path}/payments', 'customers': f'{self.base_path}/customers', 'balances': f'{self.base_path}/accounts/{{account_id}}/balances' } def design_resource_hierarchy(self): """ Design resource hierarchy for banking API """ return { 'customers': { 'path': '/customers', 'methods': ['GET', 'POST'], 'sub_resources': { 'accounts': { 'path': '/customers/{customer_id}/accounts', 'methods': ['GET', 'POST'], 'sub_resources': { 'transactions': { 'path': '/customers/{customer_id}/accounts/{account_id}/transactions', 'methods': ['GET'] }, 'balances': { 'path': '/customers/{customer_id}/accounts/{account_id}/balances', 'methods': ['GET'] } } } } }, 'payments': { 'path': '/payments', 'methods': ['POST'], 'sub_resources': { 'status': { 'path': '/payments/{payment_id}/status', 'methods': ['GET'] } } } } def implement_http_methods(self, resource_type): """ Implement appropriate HTTP methods """ method_mapping = { 'collection': ['GET', 'POST'], 'item': ['GET', 'PUT', 'PATCH', 'DELETE'], 'action': ['POST'] } return method_mapping.get(resource_type, ['GET']) def design_response_format(self): """ Design consistent response format """ return { 'success': { 'status': 'success', 'data': {}, # Actual response data 'timestamp': '2024-01-01T00:00:00Z', 'request_id': 'uuid', 'links': { 'self': '/api/v1/resource/123', 'next': '/api/v1/resource/123?page=2', 'prev': '/api/v1/resource/123?page=1' } }, 'error': { 'status': 'error', 'code': 'ERROR_CODE', 'message': 'Human readable error message', 'timestamp': '2024-01-01T00:00:00Z', 'request_id': 'uuid', 'details': {} # Additional error details } }
RESTful API Implementation:
from flask import Flask, request, jsonify, url_for from flask_restful import Api, Resource from marshmallow import Schema, fields, validate import uuid from datetime import datetime app = Flask(__name__) api = Api(app) # Data Schemas class AccountSchema(Schema): id = fields.Str(required=True) customer_id = fields.Str(required=True) account_type = fields.Str(validate=validate.OneOf(['checking', 'savings', 'credit'])) balance = fields.Float(required=True) currency = fields.Str(validate=validate.Length(equal=3)) status = fields.Str(validate=validate.OneOf(['active', 'frozen', 'closed'])) created_at = fields.DateTime() updated_at = fields.DateTime() class TransactionSchema(Schema): id = fields.Str(required=True) account_id = fields.Str(required=True) amount = fields.Float(required=True) currency = fields.Str(validate=validate.Length(equal=3)) type = fields.Str(validate=validate.OneOf(['deposit', 'withdrawal', 'transfer', 'payment'])) description = fields.Str() status = fields.Str(validate=validate.OneOf(['pending', 'completed', 'failed'])) created_at = fields.DateTime() # Resource Implementation class AccountResource(Resource): """ Account resource with standard RESTful methods """ def get(self, account_id=None): """ GET /accounts - List all accounts GET /accounts/{account_id} - Get specific account """ if account_id: # Get specific account account = self.get_account(account_id) if not account: return {'error': 'Account not found'}, 404 return { 'status': 'success', 'data': account, 'links': { 'self': url_for('accountresource', account_id=account_id), 'transactions': url_for('transactionresource', account_id=account_id), 'balances': url_for('balanceresource', account_id=account_id) } } else: # List accounts with pagination page = request.args.get('page', 1, type=int) limit = request.args.get('limit', 20, type=int) accounts = self.list_accounts(page, limit) return { 'status': 'success', 'data': accounts, 'pagination': { 'page': page, 'limit': limit, 'total': len(accounts) }, 'links': { 'self': url_for('accountresource', _external=True), 'next': url_for('accountresource', page=page+1, limit=limit), 'prev': url_for('accountresource', page=page-1, limit=limit) } } def post(self): """ POST /accounts - Create new account """ data = request.json # Validate request schema = AccountSchema() errors = schema.validate(data) if errors: return {'status': 'error', 'errors': errors}, 400 # Create account account = self.create_account(data) return { 'status': 'success', 'data': account, 'links': { 'self': url_for('accountresource', account_id=account['id']) } }, 201 def put(self, account_id): """ PUT /accounts/{account_id} - Update account (full) """ data = request.json # Validate request schema = AccountSchema() errors = schema.validate(data) if errors: return {'status': 'error', 'errors': errors}, 400 # Update account account = self.update_account(account_id, data) return { 'status': 'success', 'data': account } def patch(self, account_id): """ PATCH /accounts/{account_id} - Update account (partial) """ data = request.json # Partial update account = self.partial_update_account(account_id, data) return { 'status': 'success', 'data': account } def delete(self, account_id): """ DELETE /accounts/{account_id} - Close account """ self.close_account(account_id) return { 'status': 'success', 'message': 'Account closed successfully' }, 204 def get_account(self, account_id): """Mock account retrieval""" return { 'id': account_id, 'customer_id': 'CUST001', 'account_type': 'checking', 'balance': 5000.00, 'currency': 'USD', 'status': 'active', 'created_at': datetime.now().isoformat(), 'updated_at': datetime.now().isoformat() } def list_accounts(self, page, limit): """Mock account listing""" return [self.get_account(str(i)) for i in range((page-1)*limit, page*limit)] def create_account(self, data): """Mock account creation""" data['id'] = str(uuid.uuid4()) data['created_at'] = datetime.now().isoformat() data['updated_at'] = datetime.now().isoformat() return data def update_account(self, account_id, data): """Mock account update""" data['id'] = account_id data['updated_at'] = datetime.now().isoformat() return data def partial_update_account(self, account_id, data): """Mock partial account update""" account = self.get_account(account_id) account.update(data) account['updated_at'] = datetime.now().isoformat() return account def close_account(self, account_id): """Mock account closure""" pass # Register Resources api.add_resource(AccountResource, '/accounts', '/accounts/<string:account_id>')
2. OAuth 2.0 Deep-Dive
OAuth 2.0 Flow:
OAuth 2.0 Authorization Code Flow (Most Secure):
┌─────────────┐ ┌─────────────┐
│ Resource │ │ Authorization│
│ Owner │ │ Server │
│ (User) │ │ (Bank) │
└──────┬──────┘ └──────┬──────┘
│ │
│ 1. Authorization Request │
│ (Client redirects user to bank login) │
│───────────────────────────────────────────>│
│ │
│ 2. User Authenticates │
│ (User logs in, approves scopes) │
│<───────────────────────────────────────────│
│ │
│ 3. Authorization Code │
│ (Bank issues authorization code) │
│───────────────────────────────────────────>│
│ │
│ 4. Authorization Code Exchange │
│ (Client exchanges code for tokens) │
┌─────────────┐ │
│ Client │─────────────────────────────>│
│ (App) │ │
└─────────────┘<─────────────────────────────│
│ 5. Access Token & Refresh Token│
│ │
│ 6. API Request with Access Token │
│───────────────────────────────────────────>│
│ │
│ 7. Access Protected Resource │
│<───────────────────────────────────────────│
│ │
│ 8. Refresh Token (When token expires) │
│───────────────────────────────────────────>│
│ │
│ 9. New Access Token │
│<───────────────────────────────────────────│
OAuth 2.0 Implementation:
import jwt import hashlib import secrets from datetime import datetime, timedelta from functools import wraps class OAuth2Server: """ OAuth 2.0 Authorization Server Implementation """ def __init__(self): self.clients = {} self.authorization_codes = {} self.access_tokens = {} self.refresh_tokens = {} self.jwt_secret = 'your-secret-key' self.token_expiry = 3600 # 1 hour self.refresh_expiry = 86400 # 24 hours def register_client(self, client_name, redirect_uris, grant_types, scopes): """ Register OAuth 2.0 client """ client_id = secrets.token_urlsafe(32) client_secret = secrets.token_urlsafe(32) self.clients[client_id] = { 'client_id': client_id, 'client_secret': client_secret, 'client_name': client_name, 'redirect_uris': redirect_uris, 'grant_types': grant_types, 'scopes': scopes, 'active': True, 'created_at': datetime.now() } return client_id, client_secret def create_authorization_url(self, client_id, redirect_uri, scope, state): """ Create authorization URL for user consent """ # Validate client if client_id not in self.clients: raise ValueError('Invalid client_id') client = self.clients[client_id] # Validate redirect URI if redirect_uri not in client['redirect_uris']: raise ValueError('Invalid redirect_uri') # Validate scope requested_scopes = scope.split() for s in requested_scopes: if s not in client['scopes']: raise ValueError(f'Invalid scope: {s}') # Generate authorization code auth_code = secrets.token_urlsafe(32) # Store authorization code self.authorization_codes[auth_code] = { 'client_id': client_id, 'redirect_uri': redirect_uri, 'scope': scope, 'state': state, 'created_at': datetime.now(), 'expires_at': datetime.now() + timedelta(minutes=10) # 10 min expiry } # Create authorization URL auth_url = f"{redirect_uri}?code={auth_code}&state={state}" return auth_url def exchange_code_for_tokens(self, client_id, client_secret, auth_code, redirect_uri): """ Exchange authorization code for access and refresh tokens """ # Validate client if client_id not in self.clients: raise ValueError('Invalid client_id') client = self.clients[client_id] # Validate client secret if client_secret != client['client_secret']: raise ValueError('Invalid client_secret') # Validate authorization code if auth_code not in self.authorization_codes: raise ValueError('Invalid authorization code') auth_data = self.authorization_codes[auth_code] # Validate redirect URI if redirect_uri != auth_data['redirect_uri']: raise ValueError('Invalid redirect_uri') # Validate expiration if datetime.now() > auth_data['expires_at']: raise ValueError('Authorization code expired') # Validate client matches if client_id != auth_data['client_id']: raise ValueError('Authorization code not issued to this client') # Generate tokens access_token = self.create_access_token(client_id, auth_data['scope']) refresh_token = self.create_refresh_token(client_id) # Remove used authorization code del self.authorization_codes[auth_code] return { 'access_token': access_token, 'token_type': 'Bearer', 'expires_in': self.token_expiry, 'refresh_token': refresh_token, 'scope': auth_data['scope'] } def create_access_token(self, client_id, scope): """ Create JWT access token """ # Create JWT payload payload = { 'client_id': client_id, 'scope': scope, 'exp': datetime.now() + timedelta(seconds=self.token_expiry), 'iat': datetime.now(), 'jti': secrets.token_urlsafe(16) } # Generate JWT access_token = jwt.encode(payload, self.jwt_secret, algorithm='HS256') # Store token self.access_tokens[access_token] = { 'client_id': client_id, 'scope': scope, 'created_at': datetime.now(), 'expires_at': datetime.now() + timedelta(seconds=self.token_expiry) } return access_token def create_refresh_token(self, client_id): """ Create refresh token """ refresh_token = secrets.token_urlsafe(32) self.refresh_tokens[refresh_token] = { 'client_id': client_id, 'created_at': datetime.now(), 'expires_at': datetime.now() + timedelta(seconds=self.refresh_expiry) } return refresh_token def refresh_access_token(self, refresh_token, client_id, client_secret): """ Refresh access token using refresh token """ # Validate client if client_id not in self.clients: raise ValueError('Invalid client_id') client = self.clients[client_id] # Validate client secret if client_secret != client['client_secret']: raise ValueError('Invalid client_secret') # Validate refresh token if refresh_token not in self.refresh_tokens: raise ValueError('Invalid refresh token') refresh_data = self.refresh_tokens[refresh_token] # Validate client matches if client_id != refresh_data['client_id']: raise ValueError('Refresh token not issued to this client') # Validate expiration if datetime.now() > refresh_data['expires_at']: raise ValueError('Refresh token expired') # Generate new access token access_token = self.create_access_token(client_id, client['scopes'][0]) # Optionally rotate refresh token new_refresh_token = self.create_refresh_token(client_id) del self.refresh_tokens[refresh_token] return { 'access_token': access_token, 'token_type': 'Bearer', 'expires_in': self.token_expiry, 'refresh_token': new_refresh_token } def validate_access_token(self, access_token): """ Validate access token """ try: # Decode JWT payload = jwt.decode(access_token, self.jwt_secret, algorithms=['HS256']) # Check if token exists if access_token not in self.access_tokens: return {'valid': False, 'error': 'Token not found'} token_data = self.access_tokens[access_token] # Check expiration if datetime.now() > token_data['expires_at']: return {'valid': False, 'error': 'Token expired'} return { 'valid': True, 'client_id': payload['client_id'], 'scope': payload['scope'] } except jwt.ExpiredSignatureError: return {'valid': False, 'error': 'Token expired'} except jwt.InvalidTokenError: return {'valid': False, 'error': 'Invalid token'} # OAuth 2.0 Decorator for API Protection def require_oauth(required_scopes=None): """ Decorator to protect API endpoints with OAuth 2.0 """ def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): # Get authorization header auth_header = request.headers.get('Authorization') if not auth_header: return {'error': 'Missing authorization header'}, 401 # Parse header parts = auth_header.split() if len(parts) != 2 or parts[0].lower() != 'bearer': return {'error': 'Invalid authorization header format'}, 401 access_token = parts[1] # Validate token oauth_server = OAuth2Server() validation = oauth_server.validate_access_token(access_token) if not validation['valid']: return {'error': validation['error']}, 401 # Validate scopes if required_scopes: token_scopes = validation['scope'].split() for scope in required_scopes: if scope not in token_scopes: return {'error': f'Missing required scope: {scope}'}, 403 # Add validation data to request context request.oauth_data = validation return f(*args, **kwargs) return decorated_function return decorator
3. Mutual TLS (mTLS) Implementation
import ssl import socket import hashlib import base64 from OpenSSL import crypto class MutualTLS: """ Mutual TLS (mTLS) Implementation for API Security """ def __init__(self, ca_cert_path, server_cert_path, server_key_path): self.ca_cert = self.load_certificate(ca_cert_path) self.server_cert = self.load_certificate(server_cert_path) self.server_key = self.load_private_key(server_key_path) self.client_certificates = {} # Store client certificates def load_certificate(self, cert_path): """ Load X.509 certificate """ with open(cert_path, 'rb') as f: cert_data = f.read() return crypto.load_certificate(crypto.FILETYPE_PEM, cert_data) def load_private_key(self, key_path): """ Load private key """ with open(key_path, 'rb') as f: key_data = f.read() return crypto.load_privatekey(crypto.FILETYPE_PEM, key_data) def create_server_context(self): """ Create SSL context with mTLS """ context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) # Server certificate context.load_cert_chain(self.server_cert, self.server_key) # Require client certificate context.verify_mode = ssl.CERT_REQUIRED # Load CA certificates for client verification context.load_verify_locations(cafile=self.ca_cert) return context def verify_client_certificate(self, client_cert_der): """ Verify client certificate """ try: # Load client certificate client_cert = crypto.load_certificate(crypto.FILETYPE_ASN1, client_cert_der) # Verify signature store = crypto.X509Store() store.add_cert(self.ca_cert) store_ctx = crypto.X509StoreContext(store, client_cert) # Verify certificate store_ctx.verify_certificate() # Extract client information subject = client_cert.get_subject() client_id = subject.CN # Common Name contains client ID # Extract certificate fingerprint cert_der = crypto.dump_certificate(crypto.FILETYPE_ASN1, client_cert) cert_hash = hashlib.sha256(cert_der).hexdigest() return { 'valid': True, 'client_id': client_id, 'fingerprint': cert_hash, 'subject': { 'CN': subject.CN, 'O': subject.O, 'OU': subject.OU } } except Exception as e: return {'valid': False, 'error': str(e)} def register_client_certificate(self, client_id, cert_pem): """ Register client certificate for mTLS """ try: cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert_pem) # Store client certificate self.client_certificates[client_id] = { 'certificate': cert, 'registered_at': datetime.now(), 'active': True } return {'status': 'success', 'message': 'Certificate registered'} except Exception as e: return {'status': 'error', 'error': str(e)} def create_client_context(self, client_cert_path, client_key_path, ca_cert_path): """ Create SSL context for client (mTLS) """ context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) # Client certificate and key context.load_cert_chain(client_cert_path, client_key_path) # Load CA certificate for server verification context.load_verify_locations(cafile=ca_cert_path) return context def inspect_certificate(self, cert_der): """ Inspect certificate details """ try: cert = crypto.load_certificate(crypto.FILETYPE_ASN1, cert_der) # Extract details subject = cert.get_subject() issuer = cert.get_issuer() # Convert validity periods not_before = cert.get_notBefore().decode() not_after = cert.get_notAfter().decode() return { 'subject': { 'CN': subject.CN, 'O': subject.O, 'OU': subject.OU, 'L': subject.L, 'ST': subject.ST, 'C': subject.C }, 'issuer': { 'CN': issuer.CN, 'O': issuer.O }, 'not_before': not_before, 'not_after': not_after, 'serial': cert.get_serial_number(), 'version': cert.get_version() } except Exception as e: return {'error': str(e)}
4. API Security Threats and Mitigations
Common API Security Threats:
| Threat | Description | Mitigation |
|---|---|---|
| Injection Attacks | SQL, NoSQL, Command injection | Input validation, parameterized queries |
| Broken Authentication | Weak authentication mechanisms | Strong authentication, MFA |
| Sensitive Data Exposure | Data in transit or at rest | Encryption (TLS, AES), tokenization |
| Broken Access Control | Unauthorized access | Fine-grained permissions, role-based access |
| Security Misconfiguration | Insecure default configurations | Security hardening, regular audits |
| Cross-Site Scripting (XSS) | Malicious scripts in responses | Output encoding, Content Security Policy |
| Insecure Deserialization | Remote code execution | Safe serialization, input validation |
| Insufficient Logging | No audit trail | Comprehensive logging, monitoring |
Security Implementation:
import re import json from datetime import datetime from functools import wraps class APISecurity: """ API Security Implementation """ def __init__(self): self.sql_patterns = [ r'\bSELECT\b.*\bFROM\b', r'\bINSERT\b.*\bINTO\b', r'\bUPDATE\b.*\bSET\b', r'\bDELETE\b.*\bFROM\b', r'\bDROP\b.*\bTABLE\b', r'\bUNION\b.*\bSELECT\b' ] self.xss_patterns = [ r'<script.*?>.*?</script>', r'javascript:.*', r'on\w+=".*?"', r'<.*?on\w+=' ] def validate_input(self, input_data): """ Validate input against SQL injection and XSS """ if isinstance(input_data, dict): for key, value in input_data.items(): result = self.validate_input(value) if not result['valid']: return result elif isinstance(input_data, str): # Check for SQL injection patterns for pattern in self.sql_patterns: if re.search(pattern, input_data, re.IGNORECASE): return { 'valid': False, 'error': 'Potential SQL injection detected', 'pattern': pattern } # Check for XSS patterns for pattern in self.xss_patterns: if re.search(pattern, input_data, re.IGNORECASE): return { 'valid': False, 'error': 'Potential XSS injection detected', 'pattern': pattern } return {'valid': True} def sanitize_response(self, data): """ Sanitize response data """ if isinstance(data, dict): return {k: self.sanitize_response(v) for k, v in data.items()} elif isinstance(data, list): return [self.sanitize_response(item) for item in data] elif isinstance(data, str): # Remove potential XSS data = re.sub(r'<script.*?>.*?</script>', '', data, flags=re.IGNORECASE) data = re.sub(r'javascript:.*', '', data, flags=re.IGNORECASE) return data else: return data def audit_log(self, request, response, user_id): """ Create audit log entry """ log_entry = { 'timestamp': datetime.now().isoformat(), 'user_id': user_id, 'method': request.method, 'path': request.path, 'status_code': response.status_code, 'remote_addr': request.remote_addr, 'user_agent': request.user_agent.string, 'request_id': str(uuid.uuid4()) } # Store audit log self.store_audit_log(log_entry) return log_entry def store_audit_log(self, log_entry): """ Store audit log (implementation depends on system) """ # In production, store in database or log aggregation system print(f"Audit Log: {json.dumps(log_entry)}") def rate_limit_request(self, client_id, limit=100, window=60): """ Rate limit API requests """ # Redis implementation import redis redis_client = redis.Redis(host='localhost', port=6379, db=0) key = f"rate_limit:{client_id}" current = redis_client.get(key) if current is None: redis_client.setex(key, window, 1) return {'allowed': True} current = int(current) if current >= limit: return { 'allowed': False, 'message': 'Rate limit exceeded', 'limit': limit, 'window': window } redis_client.incr(key) return {'allowed': True} # Security Decorator def secure_api(require_auth=True, require_rate_limit=True): """ Security decorator for API endpoints """ def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): security = APISecurity() # Input validation if request.method in ['POST', 'PUT', 'PATCH']: input_data = request.get_json() if input_data: validation = security.validate_input(input_data) if not validation['valid']: return { 'status': 'error', 'error': validation['error'] }, 400 # Rate limiting if require_rate_limit: client_id = request.headers.get('X-Client-ID', 'unknown') rate_limit_result = security.rate_limit_request(client_id) if not rate_limit_result['allowed']: return { 'status': 'error', 'error': rate_limit_result['message'] }, 429 # Execute request response = f(*args, **kwargs) # Sanitize response if isinstance(response, tuple): data = response[0] status_code = response[1] if len(response) > 1 else 200 data = security.sanitize_response(data) response = (data, status_code) else: response = security.sanitize_response(response) # Audit log user_id = request.headers.get('X-User-ID', 'anonymous') security.audit_log(request, response.__dict__ if hasattr(response, '__dict__') else response, user_id) return response return decorated_function return decorator