Let's say your Rust-language application queries or sends resources from (or to) servers other than the one hosting it. Every time this happens, a cross-origin resource sharing (CORS) operation takes place. This ties in application programming interfaces and their methods as allowable verbs for your application's browser to recognize and render.
This post explores how you can safely implement CORS in Rust-lang contexts. We'll look at some errors commonly fired up by web browsers. We'll explore why they appear and how you can resolve them. Overall, you should be capable of implementing a simple Rust CORS header for your applications.
Before we get into the technical implementation of Rust CORS, let's sharpen your understanding of the concept. We'll do this with examples, so you'll understand why the effort is necessary.
Understanding Rust Cross-Origin Resource Sharing
Specific to the Rust programming language, we usually implement CORS on the back end to handle requests from apps written in different front-end languages.
The logic is simple. The web browser implements a security feature that enforces a containment of resource requests for the same origin: your server. This restriction happens even when your code specifies the intended origin or destination of each external transaction.
Typically, we use cross-origin resource sharing for the following reasons:
Rendering fonts from external resources to make sure content looks good across (all) platforms
Loading videos to stream from file-sharing platforms, such as Vimeo and YouTube
Populating iframes with data from dynamic sources, such as news websites or social media profiles
Typically, applications ask for these resources from their origins as though they're in the same location with the application. The only difference between the requests is the domain.
Same-origin requests vs. cross-origin requests.
From the illustration above, the same-origin request would look like this:
https://localhost/someresource.rs
In contrast, the cross-origin request would take this form:
https://somehost.com/resourcelocation/resource
Unless you allow cross-origin requests when building Rust applications, the browser will always shut them down and paint your logs red with errors.
Common Rust CORS Errors
Most CORS errors won't specify the language on the back end. Instead, they give you hints through mentioning your requested resources, along with the specific reason for declining. Have a look at the following error, for example:
And here's the request that causes this error:
$.ajax({
type: 'GET',
async: false,
crossDomain: true,
url: "http://localhost:3000/",
contentType: "application/json",
dataType: "json",
success: function (msg) {
console.log(msg);
$.each(msg, function (index) {
var row = '<tr><td> ' + msg[index].id + ' </td> <td> ' + msg[index].name + ' </td></tr>';
$("#tbDetails").append(row);
});
If the resource on http://localhost:3000 were on the same host as the document with this script, then that error would never pop up.
How would the developer solve this error?
He or she would have to specify a header with the Access-Control-Allow-Origin value. We'll talk more about this implementation in the solutions section. For now, though, let's look at another example as we gather evidence related to Rust CORS.
Another example of a Rust CORS-related error has to do with frameworks not covering all bases, as with Rocket back in 2017. It happened back then, and it happens even now sometimes, when you don't apply patches. Have a look:
This error instance happens when your back end sends a request (OPTIONS) before the actual request. This furnishes the server with the type of verbs and parameters to match for successful cross-origin requests.
In this example, the HTTP ok status failure simply tells the developer that once a request goes from the server to a destination, there's no HTTP 200 status mirrored back. We'll resolve this situation in the solutions section. For now, let's look at some CORS operations or options you might encounter as you explore the error-fix matrix.
Common CORS Operations
These are the operations and header options associated with CORS.
Access-Control-Allow-Origin: As shown in the first error example, you'd use this option to give passage to a request outside the application's domain server. You can embellish this as a header for specific scripts, or you can use it globally for all resources fetched externally.
Access-Control-Max-Age: You'd use this one to to grant access to permissions over a determined duration of time. "Max age" is typically one day, specified in seconds, 86400. You'll get this operation in the error if the specified time has lapsed.
Access-Control-Allow-Methods: This header option allows the default methods (PUT, GET, and so on) to send requests to an outside resource location.
When these appear in error logs, it's usually easy to implement them to a specific resource-hungry line of code. This way, you don't open access to any other links that hackers maliciously planted at runtime.
How to Enable CORS
Now for solving the error examples and enabling Rust CORS.
As mentioned above, you can either give explicit CORS access to one block of code, or you can implement an application-wide solution. Either option works if your application has hooks planted in a lot of external resource servers.
Applying CORS on a line basis solves the first error instance.
headers: {
'Access-Control-Allow-Origin': 'http://localhost:3000/'
}
You'd add the code above inside the Ajax script before allowing crossDomain access. Doing this makes certain that whichever domain specified gets exemption from the CORS blockade.
In the second example, not getting an HTTP ok response from the server is a suggestion that you enable CORS from the server side. Good coding etiquette demands that you apply permissions on the server, not the client. Otherwise, hackers can then add more of their own instructions and bypass all your security efforts.
Lucky for us, we don't have to reinvent the wheel! Most such issues have remedies already. The best way to apply Rust CORS is by appending CORS-related crates as middleware to your projects. In all this, be sure to read the code. Be certain that it aligns with your specific CORS instances before you add it to any of your projects.
Rust CORS Middleware
The Crate registry has middleware that spells out the framework and instance for which each example applies. For the Rocket framework example, this rocket cors crate has most of the ground covered. Essentially, it defines the headers, defines a fairing, and performs some tests on your behalf. This ensures safe and predictable behavior whenever your application asks for resources outside its server.
Here's a typical implementation of the rocket_cors crate:
use std::error::Error;
use rocket::http::Method;
use rocket::{get, routes};
use rocket_cors::{AllowedHeaders, AllowedOrigins};
#[get("/")]
fn cors<'a>() -> &'a str {
"Hello CORS"
}
#[rocket::main]
async fn main() -> Result<(), Box<dyn Error>> {
let allowed_origins = AllowedOrigins::some_exact(&["https://www.acme.com"]);
// You can also deserialize this
let cors = rocket_cors::CorsOptions {
allowed_origins,
allowed_methods: vec![Method::Get].into_iter().map(From::from).collect(),
allowed_headers: AllowedHeaders::some(&["Authorization", "Accept"]),
allow_credentials: true,
..Default::default()
}
.to_cors()?;
rocket::build()
.mount("/", routes![cors])
.attach(cors)
.launch()
.await?;
Ok(())
}
Essentially, adding the rocket_cors cargo through your Cargo.toml file allows you to call on its methods as libraries into your code.
In the example above, use rocket_cors::{AllowedHeaders, AllowedOrigins}; means you will have declared the allowed headers and origins well in advance. Calling these on a page or section of your application overrides any CORS errors before they flag.
The beauty of such implementations is that you can also specify any additional resources, as with the "deserialized" snippet of the code above. Such declarations remain valid on the instances for which the new CORS variable appears, not globally.
Rust CORS and CI/CD
At this point, we've explored (with examples) what CORS means in Rust applications. More important, however, is being able to cover all errors associated with CORS in the application's test phase, before it hits production. And for this, it's best to use tools like StackHawk.
StackHawk fits seamlessly in your CI/CD workflow to highlight parts of your code that may introduce security flaws and errors. This way, your developers get to resolve subtle code inconsistencies as your application versions. You can create an account or search the blog for information you can use.
This post was written by Taurai Mutimutema. Taurai is a systems analyst with a knack for writing, which was probably sparked by the need to document technical processes during code and implementation sessions. He enjoys learning new technology and talks about tech even more than he writes.