How to Handle CORS in Rust with Axum: A Step-by-Step Guide
When building web applications, you might need to allow or restrict resources shared across different domains. This is where CORS (Cross-Origin Resource Sharing) comes into play. CORS is a security feature that restricts how resources on a web server can be requested from another domain.
In this article, we will walk you through the process of handling CORS in a Rust-based web server using the Axum framework. By the end of this article, you will have a solid understanding of CORS and how to implement it efficiently in your Axum application.
Step 1: Understanding CORS
Before we start coding, let’s quickly understand what CORS is.
CORS is a security feature implemented by browsers that restricts web pages from making requests to a domain other than the one that served the web page. When a cross-origin HTTP request is made, the browser sends a CORS request (called a preflight request) to check if the server allows the request from that specific origin.
Why do we need CORS?
- Security: CORS helps prevent malicious websites from making unauthorized API requests to another site on behalf of the user.
- Control: It gives you control over who can access your API and how they can do so.
Step 2: Setting Up Our Axum Server
Now that we have the background, let’s start by setting up a basic Axum server. We’ll then integrate CORS handling step by step.
- Create a new Rust project:
cargo new axum-cors-demo
cd axum-cors-demo
- Add dependencies to
Cargo.toml
:
In order to use Axum and handle CORS, we need to include the necessary libraries in our Cargo.toml
:
[dependencies]
axum = "0.6"
tokio = { version = "1", features = ["full"] }
tower-http = { version = "0.3", features = ["cors"] }
axum
: The web framework we’ll be using to handle HTTP requests.tokio
: For async runtime support.tower-http
: Provides utilities like CORS handling for Axum.
Step 3: Building the Basic Axum Server
Let’s start by creating a basic server that responds with a "Hello, World!" message. This will lay the foundation for adding CORS handling.
In your src/main.rs
, write the following code:
use axum::{Router, routing::get};
use std::net::SocketAddr;
async fn hello_world() -> &'static str {
"Hello, world!"
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/", get(hello_world));
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
println!("Server running on http://{}", addr);
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
Explanation:
hello_world()
is a simple handler that returns a static message.- The
Router
maps the root URL (/
) to this handler. - The server listens on
127.0.0.1:3000
.
At this point, if you run the server with cargo run
, you can open your browser and navigate to http://127.0.0.1:3000
, where you’ll see the "Hello, world!" message.
Step 4: Introducing CORS Handling
Now, let’s address the CORS issue by using the tower-http
crate. This crate offers a middleware to easily configure CORS in your Axum server.
- Add the CORS Middleware:
You can add CORS support using tower_http::cors::CorsLayer
. Here’s how to modify your code to handle CORS requests:
use axum::{Router, routing::get};
use std::net::SocketAddr;
use tower_http::cors::{CorsLayer, Any};
async fn hello_world() -> &'static str {
"Hello, world!"
}
#[tokio::main]
async fn main() {
let cors = CorsLayer::new().allow_origin(Any); // Allow all origins (open policy)
let app = Router::new()
.route("/", get(hello_world))
.layer(cors); // Attach CORS middleware
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
println!("Server running on http://{}", addr);
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
Explanation:
-
CORS Middleware: We create a
CorsLayer
usingtower_http::cors::CorsLayer::new()
. Theallow_origin(Any)
method allows requests from any origin. This is an open policy, which is useful for development or public APIs. In production, you should restrict it to specific origins. -
Applying CORS: We attach the CORS layer to the Axum app using
.layer(cors)
. This ensures that the CORS settings are applied to incoming requests.
What does this do?
- This allows all origins to make requests to your server. When the browser makes a cross-origin request (e.g., from
http://localhost:4000
tohttp://localhost:3000
), the server will respond with appropriate CORS headers.
Step 5: Customizing CORS Policy
While allowing all origins is fine for development, in a production environment, you often want to restrict which domains can access your API. You can easily configure this by specifying the allowed origins.
- Allow Specific Origins:
You can restrict access to specific origins by specifying a list of allowed origins:
use axum::{http::Method, routing::get, Router};
use std::net::SocketAddr;
use tower_http::cors::CorsLayer;
async fn hello_world() -> &'static str {
"Hello, world!"
}
#[tokio::main]
async fn main() {
let allowed_origins = ["http://localhost:4000".parse().unwrap()];
let cors = CorsLayer::new()
.allow_origin(allowed_origins) // Allow only localhost:4000
.allow_methods(vec![Method::GET, Method::POST]);
let app = Router::new().route("/", get(hello_world)).layer(cors);
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
println!("Server running on http://{}", addr);
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
Explanation:
- allow_origin: allows us to specify which domains are permitted to make cross-origin requests. In this case, only
http://localhost:4000
is allowed. - allow_methods: You can also specify which HTTP methods are allowed. In this example, we allow only
GET
andPOST
.
Step 6: Testing Your CORS Implementation
After starting your server with cargo run
, you can test the CORS behavior by trying to make requests from different origins:
- Try sending a request from an allowed origin (
http://localhost:4000
). - Then, try sending a request from a disallowed origin (e.g.,
http://example.com
).
The server will respond with appropriate CORS headers. If the origin is allowed, the request proceeds. If the origin is not allowed, you’ll receive a CORS error.
Challenges for You!
- Modify the CORS settings to only allow
GET
andPOST
requests. - Try testing different CORS policies using tools like Postman or curl to understand how the headers change.
Recap and Conclusion
Here’s what we covered in this article:
- CORS basics: We learned what CORS is and why it’s important for controlling access to web resources across different domains.
- Implementing CORS in Axum: We used
tower_http
to integrate CORS handling into an Axum server. We started with a permissive policy and then customized it to restrict origins. - Testing CORS: We tested the implementation by sending requests from allowed and disallowed origins.
CORS is a fundamental concept when building web APIs, especially when you want to control access from different domains. Now that you know how to handle CORS in Axum, you can apply these concepts to build more secure and flexible web services.
Happy coding!