5.4 KiB
Work Summary: CORS Configuration from .env
Date: 2026-01-12 Status: ✅ Complete
Overview
Updated the CORS middleware to load allowed origins from the .env configuration file instead of being hard-coded, providing flexibility for different deployment environments.
Problem
The initial CORS implementation had two issues:
-
Hard-coded Origins: CORS allowed origins were hard-coded in the middleware:
.allow_origin("http://localhost:3000".parse::<HeaderValue>().unwrap()) .allow_origin("http://localhost:8080".parse::<HeaderValue>().unwrap()) // etc. -
CORS Panic: When using
allow_credentials(true)(required for JWT authentication), the CORS spec prohibits:allow_origin(Any)- Cannot use wildcard with credentialsallow_headers(Any)- Cannot use wildcard headers with credentials
Solution
1. Updated SecurityConfig
Added separate fields for access and refresh token expiration:
pub jwt_access_expiration: u64, // Default: 3600 (1 hour)
pub jwt_refresh_expiration: u64, // Default: 604800 (7 days)
2. Updated CORS Middleware
Changed create_cors_layer() to accept origins as parameter:
pub fn create_cors_layer(allowed_origins: Vec<String>) -> CorsLayer
Features:
- Accepts list of allowed origins from config
- Falls back to default development origins if empty
- Explicitly specifies headers and methods (no wildcards)
- Maintains credential support for authentication
3. Updated AppState
Added cors_origins field to store configuration:
pub struct AppState {
pub db: PgPool,
pub jwt_config: Arc<JwtConfig>,
pub cors_origins: Vec<String>, // NEW
}
4. Configuration Flow
.env file
↓
Config::load()
↓
config.server.cors_origins
↓
AppState
↓
Server::build_router()
↓
create_cors_layer(origins)
Configuration
.env File
# Leave empty for default development origins
ATTUNE__SERVER__CORS_ORIGINS=
# Or specify custom origins (comma-separated)
ATTUNE__SERVER__CORS_ORIGINS=http://localhost:3000,https://app.example.com,https://admin.example.com
Default Origins (when empty)
If no origins are specified, the API uses these development defaults:
http://localhost:3000http://localhost:8080http://127.0.0.1:3000http://127.0.0.1:8080
Allowed Headers
Explicitly set to support authentication:
Authorization- For Bearer tokensContent-Type- For JSON requestsAccept- For content negotiation
Allowed Methods
GETPOSTPUTDELETEPATCHOPTIONS(for preflight requests)
Files Modified
-
crates/common/src/config.rs- Split
jwt_expirationintojwt_access_expirationandjwt_refresh_expiration
- Split
-
crates/api/src/middleware/cors.rs- Made CORS layer accept origins as parameter
- Added default origins fallback
- Fixed credentials + wildcard issue
-
crates/api/src/state.rs- Added
cors_originsfield to AppState
- Added
-
crates/api/src/server.rs- Pass
cors_originsfrom state to middleware
- Pass
-
crates/api/src/main.rs- Load CORS origins from config
- Pass to AppState constructor
- Added logging for CORS configuration
-
.env- Added
ATTUNE__SERVER__CORS_ORIGINSconfiguration
- Added
-
.env.example- Updated JWT configuration comments
- Added
ATTUNE__SECURITY__JWT_ACCESS_EXPIRATION - Added
ATTUNE__SECURITY__JWT_REFRESH_EXPIRATION
-
docs/quick-start.md- Added CORS configuration documentation
Testing
# Start server
cargo run --bin attune-api
# Expected logs:
# INFO JWT configuration initialized (access: 3600s, refresh: 604800s)
# INFO CORS configured with default development allowed origin(s)
# INFO Server listening on 127.0.0.1:8080
# Test health check
curl http://localhost:8080/api/v1/health
# Response: {"status":"ok"}
Benefits
- Flexibility: Origins can be changed without recompiling
- Environment-specific: Different origins for dev/staging/prod
- Security: No wildcard origins with credentials
- Convenience: Defaults work for local development
- Documentation: Clear configuration in .env file
Production Deployment
For production, specify exact origins:
ATTUNE__SERVER__CORS_ORIGINS=https://app.example.com,https://admin.example.com
Never use wildcards or overly permissive origins in production!
Security Notes
- ✅ CORS credentials enabled (required for JWT auth)
- ✅ Explicit origins (no wildcards)
- ✅ Explicit headers (no wildcards)
- ✅ Explicit methods
- ✅ Configurable per environment
Example Configurations
Local Development (default)
ATTUNE__SERVER__CORS_ORIGINS=
Development with Custom Frontend
ATTUNE__SERVER__CORS_ORIGINS=http://localhost:3000
Staging Environment
ATTUNE__SERVER__CORS_ORIGINS=https://staging.example.com
Production (Multiple Origins)
ATTUNE__SERVER__CORS_ORIGINS=https://app.example.com,https://admin.example.com,https://mobile.example.com
Next Steps
- Consider adding origin validation in config loading
- Add metrics for CORS rejections
- Document CORS troubleshooting in API docs
- Consider per-route CORS policies for future features
References
- CORS Spec: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
- Tower HTTP CORS: https://docs.rs/tower-http/latest/tower_http/cors/
- OWASP CORS Security: https://cheatsheetseries.owasp.org/cheatsheets/CORS_Cheat_Sheet.html