title: "API Security Guide" description: "Comprehensive guide for securing API endpoints in Navius applications, covering authentication, authorization, input validation, and API-specific security concerns" category: "Guides" tags: ["security", "API", "REST", "endpoints", "validation", "rate limiting", "OWASP"] last_updated: "April 7, 2025" version: "1.0"
API Security Guide
Overview
This guide provides detailed instructions for securing API endpoints in Navius applications. APIs are critical entry points to your application that require robust security measures to protect your data and services.
API Security Fundamentals
Common API Security Threats
- Broken Authentication: Weak authentication allowing unauthorized access
- Excessive Data Exposure: Returning excessive data in API responses
- Broken Object Level Authorization: Improper access controls for resources
- Mass Assignment: Client-provided data modifying sensitive properties
- Injection Attacks: SQL, NoSQL, command injection via API inputs
- Improper Assets Management: Exposed debug endpoints or outdated APIs
- API Abuse: Excessive requests that impact availability
API Security Principles
- Defense in Depth: Multiple security layers
- Least Privilege: Limit access to necessary resources
- Zero Trust: Verify every request regardless of source
- Secure by Default: Security controls enabled by default
- Fail Securely: Errors default to secure state
Secure API Authentication
API Key Authentication
Configure API key authentication:
# config/default.yaml
api:
auth:
type: "apikey"
header_name: "X-API-Key"
key_validation: "database" # or "static", "redis"
rate_limiting:
enabled: true
limit: 100
window_seconds: 60
Implement API key validation:
#![allow(unused)] fn main() { use navius::api::auth::{ApiKeyValidator, DatabaseApiKeyValidator}; // Create API key validator let api_key_validator = DatabaseApiKeyValidator::new(db_pool).await?; // API key middleware async fn api_key_middleware( State(state): State<AppState>, req: Request, next: Next, ) -> Result<Response, StatusCode> { // Extract API key from header let api_key = req.headers() .get(state.config.api.auth.header_name.as_str()) .and_then(|h| h.to_str().ok()) .ok_or(StatusCode::UNAUTHORIZED)?; // Validate API key let client_info = state.api_key_validator .validate(api_key) .await .map_err(|_| StatusCode::UNAUTHORIZED)?; // Add client info to request extensions let mut req = req; req.extensions_mut().insert(client_info); // Continue to handler Ok(next.run(req).await) } // Generate new API key async fn generate_api_key( org_id: Uuid, permissions: Vec<String>, api_key_service: &ApiKeyService, ) -> Result<ApiKey, Error> { let api_key = api_key_service.generate(org_id, permissions).await?; Ok(api_key) } // Revoke API key async fn revoke_api_key( key_id: Uuid, api_key_service: &ApiKeyService, ) -> Result<(), Error> { api_key_service.revoke(key_id).await?; Ok(()) } }
Bearer Token Authentication
Implement JWT-based authentication:
#![allow(unused)] fn main() { use navius::api::auth::{JwtValidator, JwtConfig}; // Create JWT validator let jwt_config = JwtConfig { issuer: "navius-api".to_string(), audience: "navius-client".to_string(), key_id: "current-signing-key".to_string(), public_key_path: "/path/to/public.pem".to_string(), }; let jwt_validator = JwtValidator::new(jwt_config); // JWT middleware async fn jwt_middleware( State(state): State<AppState>, req: Request, next: Next, ) -> Result<Response, StatusCode> { // Extract token from Authorization header let token = req.headers() .get(HeaderName::from_static("authorization")) .and_then(|h| h.to_str().ok()) .and_then(|h| h.strip_prefix("Bearer ")) .ok_or(StatusCode::UNAUTHORIZED)?; // Validate JWT let claims = state.jwt_validator .validate(token) .map_err(|_| StatusCode::UNAUTHORIZED)?; // Add claims to request extensions let mut req = req; req.extensions_mut().insert(claims); // Continue to handler Ok(next.run(req).await) } }
OAuth 2.0 and OpenID Connect
Configure OAuth 2.0:
# config/default.yaml
api:
auth:
type: "oauth2"
provider: "entra"
entra:
tenant_id: "your-tenant-id"
client_id: "your-client-id"
jwks_uri: "https://login.microsoftonline.com/{tenant_id}/discovery/v2.0/keys"
issuer: "https://login.microsoftonline.com/{tenant_id}/v2.0"
audience: "api://your-app-id"
Implement OAuth 2.0 validation:
#![allow(unused)] fn main() { use navius::api::auth::{OAuth2Validator, OAuth2Config}; // Create OAuth2 validator let oauth2_config = OAuth2Config::from_config(&config)?; let oauth2_validator = OAuth2Validator::new(oauth2_config).await?; // OAuth2 middleware async fn oauth2_middleware( State(state): State<AppState>, req: Request, next: Next, ) -> Result<Response, StatusCode> { // Extract token from Authorization header let token = req.headers() .get(HeaderName::from_static("authorization")) .and_then(|h| h.to_str().ok()) .and_then(|h| h.strip_prefix("Bearer ")) .ok_or(StatusCode::UNAUTHORIZED)?; // Validate OAuth2 token let claims = state.oauth2_validator .validate(token) .await .map_err(|_| StatusCode::UNAUTHORIZED)?; // Add claims to request extensions let mut req = req; req.extensions_mut().insert(claims); // Continue to handler Ok(next.run(req).await) } }
API Authorization
Scopes and Permissions
Configure API scopes:
# config/default.yaml
api:
scopes:
- name: "users:read"
description: "Read user information"
- name: "users:write"
description: "Create or update users"
- name: "admin"
description: "Administrative access"
Implement scope-based authorization:
#![allow(unused)] fn main() { use navius::api::auth::{ScopeValidator, Claims}; // Scope validation middleware async fn scope_middleware( req: Request, next: Next, required_scopes: Vec<String>, ) -> Result<Response, StatusCode> { // Get claims from request extensions let claims = req.extensions() .get::<Claims>() .ok_or(StatusCode::UNAUTHORIZED)?; // Check if token has required scopes let has_scope = required_scopes.iter().any(|scope| { claims.scopes.contains(scope) }); if !has_scope { return Err(StatusCode::FORBIDDEN); } // Continue to handler Ok(next.run(req).await) } // Apply to routes let app = Router::new() .route("/users", get(get_users_handler)) .route_layer(middleware::from_fn(|req, next| { scope_middleware(req, next, vec!["users:read".to_string()]) })) .route("/users", post(create_user_handler)) .route_layer(middleware::from_fn(|req, next| { scope_middleware(req, next, vec!["users:write".to_string()]) })); }
Fine-grained API Permissions
Implement resource-based permissions:
#![allow(unused)] fn main() { use navius::api::auth::{PermissionValidator, ResourcePermission}; // Resource permission middleware async fn resource_permission_middleware( State(state): State<AppState>, req: Request, next: Next, ) -> Result<Response, StatusCode> { // Get claims from request extensions let claims = req.extensions() .get::<Claims>() .ok_or(StatusCode::UNAUTHORIZED)?; // Extract resource ID from request let resource_id = extract_resource_id(&req)?; // Determine action from method let action = match req.method() { &Method::GET => "read", &Method::POST => "create", &Method::PUT | &Method::PATCH => "update", &Method::DELETE => "delete", _ => "access", }; // Check if token has permission for this resource let permission = ResourcePermission { resource_type: "user", resource_id: Some(resource_id), action: action.to_string(), }; let has_permission = state.permission_validator .validate(claims, &permission) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; if !has_permission { return Err(StatusCode::FORBIDDEN); } // Continue to handler Ok(next.run(req).await) } }
Input Validation and Sanitization
Request Validation
Validate API requests:
#![allow(unused)] fn main() { use navius::api::validation::{Validator, ValidationRules}; use serde::{Deserialize, Serialize}; // Define validation schema #[derive(Debug, Deserialize, Serialize)] struct CreateUserRequest { #[validate(length(min = 3, max = 50))] username: String, #[validate(email)] email: String, #[validate(length(min = 8, max = 100), strong_password)] password: String, #[validate(phone)] phone: Option<String>, } // Create validator let validator = Validator::new(); // Request validation middleware async fn validate_request<T: DeserializeOwned + ValidatedRequest>( Json(payload): Json<T>, validator: &Validator, ) -> Result<Json<T>, StatusCode> { // Validate request validator.validate(&payload) .map_err(|_| StatusCode::BAD_REQUEST)?; Ok(Json(payload)) } // Use in handler async fn create_user_handler( State(state): State<AppState>, validated: ValidatedJson<CreateUserRequest>, ) -> impl IntoResponse { // Handler implementation with validated request let user = create_user(validated.0).await?; (StatusCode::CREATED, Json(user)) } }
Content Type Validation
Ensure correct content types:
#![allow(unused)] fn main() { // Content type validation middleware async fn content_type_middleware( req: Request, next: Next, allowed_types: Vec<&'static str>, ) -> Result<Response, StatusCode> { // Extract content type header let content_type = req.headers() .get(HeaderName::from_static("content-type")) .and_then(|h| h.to_str().ok()) .unwrap_or(""); // Check if content type is allowed let allowed = allowed_types.iter().any(|&t| content_type.starts_with(t)); if !allowed { return Err(StatusCode::UNSUPPORTED_MEDIA_TYPE); } // Continue to handler Ok(next.run(req).await) } // Apply to routes let app = Router::new() .route("/users", post(create_user_handler)) .route_layer(middleware::from_fn(|req, next| { content_type_middleware(req, next, vec!["application/json"]) })); }
API Schema Validation
Validate against OpenAPI schema:
#![allow(unused)] fn main() { use navius::api::validation::{OpenApiValidator, OpenApiConfig}; // Create OpenAPI validator let openapi_validator = OpenApiValidator::new(OpenApiConfig { schema_path: "/path/to/openapi.yaml".to_string(), validate_requests: true, validate_responses: true, }); // OpenAPI validation middleware async fn openapi_middleware( State(state): State<AppState>, req: Request, next: Next, ) -> Result<Response, StatusCode> { // Validate request against schema state.openapi_validator .validate_request(&req) .map_err(|_| StatusCode::BAD_REQUEST)?; // Call handler let response = next.run(req).await; // Validate response against schema state.openapi_validator .validate_response(&response) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; Ok(response) } }
Rate Limiting and Throttling
Rate Limiting Configuration
Configure rate limiting:
# config/default.yaml
api:
rate_limiting:
enabled: true
strategies:
- type: "ip"
limit: 100
window_seconds: 60
- type: "user"
limit: 1000
window_seconds: 3600
- type: "token"
limit: 5000
window_seconds: 3600
Rate Limiting Implementation
Implement rate limiting:
#![allow(unused)] fn main() { use navius::api::protection::{RateLimiter, RateLimitStrategy, RateLimitConfig}; // Create rate limiter let rate_limiter = RateLimiter::new( RateLimitConfig::from_config(&config)?, redis_client, ); // Rate limiting middleware async fn rate_limit_middleware( State(state): State<AppState>, req: Request, next: Next, ) -> Result<Response, StatusCode> { // Get client identifier (IP, user ID, or token) let client_id = get_client_identifier(&req)?; // Get rate limit strategy based on client let strategy = state.rate_limiter.get_strategy_for_client(&client_id); // Check rate limit let result = state.rate_limiter .check(client_id, strategy) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; if !result.allowed { return Err(StatusCode::TOO_MANY_REQUESTS); } // Add rate limit headers to response let response = next.run(req).await; let response = add_rate_limit_headers(response, result); Ok(response) } // Add rate limit headers to response fn add_rate_limit_headers(mut response: Response, result: RateLimitResult) -> Response { let headers = response.headers_mut(); headers.insert( HeaderName::from_static("x-ratelimit-limit"), HeaderValue::from(result.limit.to_string()), ); headers.insert( HeaderName::from_static("x-ratelimit-remaining"), HeaderValue::from(result.remaining.to_string()), ); headers.insert( HeaderName::from_static("x-ratelimit-reset"), HeaderValue::from(result.reset.to_string()), ); response } }
Throttling for Specific Endpoints
Implement endpoint-specific throttling:
#![allow(unused)] fn main() { // Endpoint-specific rate limit middleware async fn endpoint_rate_limit_middleware( State(state): State<AppState>, req: Request, next: Next, endpoint: &str, limit: u64, window_seconds: u64, ) -> Result<Response, StatusCode> { // Get client identifier let client_id = get_client_identifier(&req)?; // Create endpoint-specific key let key = format!("{}:{}", endpoint, client_id); // Check custom rate limit let result = state.rate_limiter .check_custom(key, limit, window_seconds) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; if !result.allowed { return Err(StatusCode::TOO_MANY_REQUESTS); } // Continue to handler let response = next.run(req).await; let response = add_rate_limit_headers(response, result); Ok(response) } // Apply to specific endpoint let app = Router::new() .route("/password-reset", post(password_reset_handler)) .route_layer(middleware::from_fn_with_state(app_state.clone(), |req, next, state| { endpoint_rate_limit_middleware(State(state), req, next, "password-reset", 5, 3600) })); }
API Response Security
Data Minimization
Implement response filtering:
#![allow(unused)] fn main() { use navius::api::response::{ResponseFilter, FilterConfig}; // Create response filter let filter_config = FilterConfig { default_fields: vec!["id", "name", "created_at"], sensitive_fields: vec!["email", "phone", "address"], field_policies: HashMap::from([ ("users".to_string(), vec!["id", "username", "created_at"]), ("orders".to_string(), vec!["id", "status", "items", "total"]), ]), }; let response_filter = ResponseFilter::new(filter_config); // Filter responses async fn filter_response<T: Serialize>( data: T, resource_type: &str, fields: Option<Vec<String>>, filter: &ResponseFilter, ) -> Result<Json<Value>, StatusCode> { let filtered = filter .filter_response(data, resource_type, fields) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; Ok(Json(filtered)) } // Use in handler async fn get_user_handler( State(state): State<AppState>, Path(user_id): Path<Uuid>, Query(params): Query<HashMap<String, String>>, ) -> impl IntoResponse { // Get user from database let user = get_user(user_id).await?; // Parse fields parameter let fields = params.get("fields").map(|f| { f.split(',').map(|s| s.trim().to_string()).collect() }); // Filter response let filtered = filter_response(user, "users", fields, &state.response_filter).await?; (StatusCode::OK, filtered) } }
Security Headers
Implement security headers:
#![allow(unused)] fn main() { use navius::api::security::ApiSecurityHeadersLayer; // Add API security headers let app = Router::new() .route("/", get(handler)) .layer(ApiSecurityHeadersLayer::new()); // Headers set: // - X-Content-Type-Options: nosniff // - Cache-Control: no-store // - Content-Security-Policy: default-src 'self' // - X-Frame-Options: DENY // - Strict-Transport-Security: max-age=31536000; includeSubDomains }
Safe Error Responses
Implement safe error handling:
#![allow(unused)] fn main() { use navius::api::error::{ApiError, ApiErrorResponse}; // Create API error async fn handle_api_error(error: ApiError) -> impl IntoResponse { let status = match error.kind { ApiErrorKind::NotFound => StatusCode::NOT_FOUND, ApiErrorKind::Validation => StatusCode::BAD_REQUEST, ApiErrorKind::Authentication => StatusCode::UNAUTHORIZED, ApiErrorKind::Authorization => StatusCode::FORBIDDEN, ApiErrorKind::RateLimit => StatusCode::TOO_MANY_REQUESTS, ApiErrorKind::Internal => StatusCode::INTERNAL_SERVER_ERROR, }; // Create safe error response let response = ApiErrorResponse { code: error.code, message: error.public_message, details: error.public_details, request_id: error.request_id, }; // Log detailed error information for debugging if error.kind == ApiErrorKind::Internal { error!(?error, "Internal API error"); } else { debug!(?error, "API error response"); } (status, Json(response)) } // Use in error handler async fn api_error_handler(error: BoxError) -> impl IntoResponse { if let Some(api_error) = error.downcast_ref::<ApiError>() { return handle_api_error(api_error.clone()).await; } // Convert other errors to internal API errors let api_error = ApiError::internal( "unexpected_error", "An unexpected error occurred", format!("{}", error), ); handle_api_error(api_error).await } }
Cross-Origin Resource Sharing (CORS)
CORS Configuration
Configure CORS:
# config/default.yaml
api:
cors:
enabled: true
allow_origins:
- "https://app.example.com"
- "https://admin.example.com"
allow_methods:
- "GET"
- "POST"
- "PUT"
- "DELETE"
allow_headers:
- "Authorization"
- "Content-Type"
expose_headers:
- "X-Request-ID"
max_age_seconds: 3600
allow_credentials: true
CORS Implementation
Implement CORS:
#![allow(unused)] fn main() { use navius::api::cors::{CorsLayer, CorsConfig}; use tower_http::cors::{CorsLayer as TowerCorsLayer, Any}; // Create CORS layer let cors_config = CorsConfig::from_config(&config)?; let cors_layer = if cors_config.enabled { let allowed_origins = cors_config.allow_origins .iter() .map(|origin| origin.parse().unwrap()) .collect::<Vec<_>>(); let allowed_methods = cors_config.allow_methods .iter() .map(|method| method.parse().unwrap()) .collect::<Vec<_>>(); let allowed_headers = cors_config.allow_headers .iter() .map(|header| header.parse().unwrap()) .collect::<Vec<_>>(); let exposed_headers = cors_config.expose_headers .iter() .map(|header| header.parse().unwrap()) .collect::<Vec<_>>(); Some( TowerCorsLayer::new() .allow_origin(allowed_origins) .allow_methods(allowed_methods) .allow_headers(allowed_headers) .expose_headers(exposed_headers) .max_age(Duration::from_secs(cors_config.max_age_seconds)) .allow_credentials(cors_config.allow_credentials) ) } else { None }; // Apply CORS layer if enabled let app = Router::new() .route("/", get(handler)); let app = if let Some(cors) = cors_layer { app.layer(cors) } else { app }; }
API Monitoring and Logging
Request Logging
Implement API request logging:
#![allow(unused)] fn main() { use navius::api::logging::{ApiLogger, LogConfig}; // Create API logger let log_config = LogConfig { request_headers: vec!["user-agent", "content-type", "accept"], response_headers: vec!["content-type", "cache-control"], log_body: false, log_query_params: true, mask_sensitive_headers: vec!["authorization", "x-api-key"], }; let api_logger = ApiLogger::new(log_config); // Logger middleware async fn api_logger_middleware( State(state): State<AppState>, req: Request, next: Next, ) -> Result<Response, StatusCode> { // Generate request ID if not present let request_id = get_or_generate_request_id(&req); // Log request let start_time = Instant::now(); state.api_logger.log_request(&req, request_id).await; // Process request let response = next.run(req).await; // Calculate duration let duration = start_time.elapsed(); // Log response state.api_logger.log_response(&response, request_id, duration).await; Ok(response) } }
Error Rate Monitoring
Implement error rate monitoring:
#![allow(unused)] fn main() { use navius::api::monitoring::{ErrorMonitor, AlertConfig}; // Create error monitor let error_monitor = ErrorMonitor::new( AlertConfig { error_threshold_percent: 5.0, window_seconds: 60, min_requests: 10, }, metrics_client, ); // Error monitoring middleware async fn error_monitor_middleware( State(state): State<AppState>, req: Request, next: Next, ) -> Result<Response, StatusCode> { // Process request let response = next.run(req).await; // Check if response is an error let is_error = response.status().is_client_error() || response.status().is_server_error(); // Record request result state.error_monitor .record(req.uri().path(), is_error) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; Ok(response) } }
API Metrics
Implement API metrics:
#![allow(unused)] fn main() { use navius::api::metrics::{ApiMetrics, MetricsConfig}; // Create API metrics let api_metrics = ApiMetrics::new( MetricsConfig { enabled: true, endpoint: "/metrics".to_string(), namespace: "navius_api".to_string(), }, prometheus_registry, ); // Metrics middleware async fn api_metrics_middleware( State(state): State<AppState>, req: Request, next: Next, ) -> Result<Response, StatusCode> { // Extract path for grouping similar routes let path = normalize_path(req.uri().path()); let method = req.method().clone(); // Start timer let start_time = Instant::now(); // Process request let response = next.run(req).await; // Record metrics let status = response.status().as_u16(); let duration = start_time.elapsed(); state.api_metrics.record_request( &path, method.as_str(), status, duration.as_secs_f64(), ); Ok(response) } }
Security Testing for APIs
API Security Testing Tools
#![allow(unused)] fn main() { use navius::api::testing::{SecurityScanner, ScanConfig}; // Create security scanner let security_scanner = SecurityScanner::new( ScanConfig { target_url: "https://api.example.com".to_string(), api_schema_path: "/path/to/openapi.yaml".to_string(), auth_token: Some("test-token".to_string()), scan_types: vec!["injection", "authentication", "authorization"], }, ); // Run security scan async fn run_security_scan(scanner: &SecurityScanner) -> Result<ScanReport, Error> { let report = scanner.scan().await?; // Output scan results for vulnerability in &report.vulnerabilities { println!( "Vulnerability: {} (Severity: {})", vulnerability.name, vulnerability.severity ); println!("Endpoint: {}", vulnerability.endpoint); println!("Description: {}", vulnerability.description); println!("Remediation: {}", vulnerability.remediation); println!(); } Ok(report) } }
API Fuzz Testing
Implement fuzz testing:
#![allow(unused)] fn main() { use navius::api::testing::{FuzzTester, FuzzConfig}; // Create fuzz tester let fuzz_tester = FuzzTester::new( FuzzConfig { target_url: "https://api.example.com".to_string(), api_schema_path: "/path/to/openapi.yaml".to_string(), auth_token: Some("test-token".to_string()), iterations: 1000, payloads_path: "/path/to/fuzz-payloads.txt".to_string(), }, ); // Run fuzz tests async fn run_fuzz_tests(tester: &FuzzTester) -> Result<FuzzReport, Error> { let report = tester.run().await?; // Output fuzz test results for issue in &report.issues { println!("Issue: {}", issue.description); println!("Endpoint: {}", issue.endpoint); println!("Request: {:?}", issue.request); println!("Response: {}", issue.response.status); println!(); } Ok(report) } }
API Security Best Practices
API Security Checklist
-
Authentication and Authorization
- Implement secure authentication (API keys, JWT, OAuth)
- Use proper authorization for all endpoints
- Implement token validation and revocation
-
Input Validation and Sanitization
- Validate all input parameters
- Sanitize data to prevent injection attacks
- Validate content types and schemas
-
Rate Limiting and Resource Protection
- Implement rate limiting for all endpoints
- Set appropriate timeouts for all operations
- Limit payload sizes
-
Response Security
- Return minimal data in responses
- Use appropriate security headers
- Return safe error messages
-
Transport Security
- Enforce HTTPS for all API communications
- Configure proper TLS settings
- Implement CORS properly
-
Logging and Monitoring
- Log all API access and errors
- Monitor for suspicious activity
- Set up alerts for security incidents
-
API Lifecycle Management
- Version API endpoints
- Deprecate and retire APIs safely
- Document security requirements
Secure API Design Principles
-
Design for Least Privilege
- Each API endpoint should require minimal permissions
- Scope access tokens to specific resources
-
Avoid Exposing Implementation Details
- Hide internal identifiers when possible
- Avoid leaking stack traces or internal error messages
-
Secure Parameter Handling
- Always validate query parameters and request bodies
- Use parameterized queries for database operations
-
Always Verify on Server
- Never trust client-side validation
- Revalidate all data server-side regardless of client validation
Troubleshooting API Security Issues
Common API Security Issues
-
Authentication Failures
- Invalid or expired tokens
- Missing credentials
- Incorrect API key format
-
Authorization Problems
- Missing permissions
- Incorrect scopes
- Resource access denied
-
Rate Limiting Issues
- Too many requests
- Inconsistent rate limit application
- Rate limit bypass attempts
-
Input Validation Failures
- Malformed input data
- Injection attack attempts
- Schema validation errors
Debugging API Security
#![allow(unused)] fn main() { // Enable detailed logging for API security components tracing_subscriber::fmt() .with_env_filter("navius::api::auth=debug,navius::api::validation=debug") .init(); // Create test tokens for debugging async fn create_debug_token( claims: HashMap<String, Value>, jwt_service: &JwtService, ) -> Result<String, Error> { let token = jwt_service.create_token(claims).await?; Ok(token) } }