CORS and Security Headers
Protecting against browser-based attacks.
Learning Objectives
- Configure actix-cors
##Letting browsers talk to your API
Browsers enforce the same-origin policy: a page loaded from https://app.example.com is not allowed to read responses from https://api.example.com unless the API explicitly says it is okay. That permission system is called CORS (Cross-Origin Resource Sharing).
#When CORS matters
If your frontend (React, Vue, a static site) runs on a different domain or port from your Actix API, the browser will block requests unless your API sends the right CORS headers. Tools like curl and mobile apps are not affected; CORS is a browser rule.
#Preflight requests
For requests that can change data or use custom headers, the browser first sends an OPTIONS request, called a preflight, asking "is this allowed?" Your server answers with headers listing what it permits. Only then does the browser send the real request.
#Adding CORS to Actix Web
actix-cors = "0.7"use actix_cors::Cors;
use actix_web::{http, App, HttpServer};
HttpServer::new(|| {
let cors = Cors::default()
.allowed_origin("https://app.example.com")
.allowed_methods(vec!["GET", "POST", "PUT", "DELETE"])
.allowed_headers(vec![http::header::AUTHORIZATION, http::header::CONTENT_TYPE])
.supports_credentials()
.max_age(3600);
App::new().wrap(cors)
})allowed_originlists the exact origins you trust.allowed_methodsandallowed_headerscontrol what the browser may send.supports_credentialsallows cookies and auth headers, and then you cannot use a wildcard origin.max_agelets the browser cache the preflight answer.
#Do not use the permissive default in production
Cors::permissive() allows every origin, which is handy during development but unsafe for APIs that use cookies or sensitive data. Use an explicit allowlist in production.
#Other important security headers
Headers protect your users beyond CORS. You can add them with middleware::DefaultHeaders:
use actix_web::middleware::DefaultHeaders;
App::new().wrap(
DefaultHeaders::new()
.add(("X-Content-Type-Options", "nosniff"))
.add(("X-Frame-Options", "DENY"))
.add(("Referrer-Policy", "strict-origin-when-cross-origin"))
.add(("Strict-Transport-Security", "max-age=63072000; includeSubDomains")),
);X-Content-Type-Options: nosniffstops browsers guessing content types.X-Frame-Options: DENYprevents clickjacking.Strict-Transport-Securityforces HTTPS on future visits.- A
Content-Security-Policyheader limits where scripts can load from.
#Debugging CORS
Open the browser's developer tools and look at the Network tab. If a request fails with a CORS error, check that the response includes Access-Control-Allow-Origin and that the preflight OPTIONS request returned a successful status.
"What If?" Experiment
Learning by doing. Modify the code to see what happens.
Inspect a preflight request.
Run the command and read the response headers.
You see access-control-allow-origin and access-control-allow-methods in the response.
Explanation: These headers are what the browser reads to decide whether the real request may be sent.
Common Mistakes
Anticipate and avoid these frequent pitfalls when working with this concept.
Using Cors::permissive() in production
It removes friction during development and gets left in.
Explanation: Allow only the origins you control so other sites cannot read your users' data.
Forgetting to allow the Authorization header
Preflight rejects custom headers that are not on the allowlist.
Explanation: Add every header your frontend sends, otherwise the browser blocks the call before it reaches your handler.
Practical Exercise
Allow one frontend
TaskConfigure CORS so only http://localhost:5173 can call your API with GET and POST.
Requirements
- Use Cors::default()
- Allow only that origin
- Allow GET and POST and the Content-Type header
Need a hint?
Try solving the exercise in your local environment first. Once you have a solution, or if you get completely stuck, reveal the answer.
Real-World Application
Single-page apps and mobile web views hosted on a different domain need exactly this configuration, usually with the allowed origin read from an environment variable per environment.
Key Takeaways
- CORS is enforced by browsers, not by curl or servers calling each other.
- Use actix-cors with an explicit allowed_origin list in production.
- supports_credentials cannot be combined with a wildcard origin.
- Add security headers with DefaultHeaders (nosniff, frame options, HSTS).
Finished reading?
Mark this lesson as done to track your progress and earn XP.