How To Do Basic Authentication Using a Rust Rocket Api

Updated at

Purpose

While learning rust I had quite some difficulty to find easy to understand documentation on how to implement basic authentication or api key based authentication on a rust rocket api route.

Scenario

Let's say that you have an api route like this:

0#[get("/")]
1fn index() -> Result<CustomResponse, WebError> {
2    return match reg.render_template(template, &json!({})) {
3        Ok(value) => {
4            Ok(HtmlTemplate("<html></html>"))
5        }
6        Err(_) => {
7            Err(Internal("Failed to render view.".to_string()))
8        }
9    };
10}

If we want to add authentication to this endpoint, we need some way to force users to pass a valid api key.

Rocket will already fail if the request parameters are not exactly passed as they are specified. We will make use of this gatekeeping feature to add authentication.

API Key and Basic Authentication for Rust Rocket API's

To authenticate on your route with an api key or basic authentication we need to specify a parameter on our endpoint.

0#[get("/")]
1fn index(_key: ApiKey<'_>) -> Result<CustomResponse, WebError> {
2    return match reg.render_template(template, &json!({})) {
3        Ok(value) => {
4            Ok(HtmlTemplate("<html></html>"))
5        }
6        Err(_) => {
7            Err(Internal("Failed to render view.".to_string()))
8        }
9    };
10}

The underscore in front of key tells rust that this variable is intentionally there and that it is fine to not use it.

What happens here?

Rust Rocket Request Guards

The feature that we rely on here are requests guards. If the request is not executed as expected, rocket will throw a bad request. We make use of that and add a new struct called ApiKey.

0use rocket::http::Status;
1use rocket::request::Outcome;
2use rocket::request::FromRequest;
3
4struct ApiKey<'r>(&'r str);

We also need some special errors to be handled. Two cases exist: Invalid and Missing.

0#[derive(Debug)]
1enum ApiKeyError {
2    Missing,
3    Invalid,
4}

Now we need to teach Rocket how to extract the api key and validate it if a requests is processed.

For this we will expect the api key in the header X-Api-Key:

0#[rocket::async_trait]
1impl<'r> FromRequest<'r> for ApiKey<'r> {
2    type Error = ApiKeyError;
3
4    async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
5        let api_key = "MY_SECRET_KEY"
6        let is_valid = |key: &str| -> bool {
7            key == api_key
8        };
9
10        match req.headers().get_one("X-Api-Key") {
11            None => Outcome::Error((Status::BadRequest, ApiKeyError::Missing)),
12            Some(key) if is_valid(key) => Outcome::Success(ApiKey(key)),
13            Some(key) if !is_valid(key) => Outcome::Error((Status::Unauthorized, ApiKeyError::Invalid)),
14            Some(_) => Outcome::Error((Status::InternalServerError, ApiKeyError::Invalid)),
15        }
16    }
17}

Conclusion

If you combine all of our authentication examples from above you will get the following code:

0use rocket::http::Status;
1use rocket::request::Outcome;
2use rocket::request::FromRequest;
3
4struct ApiKey<'r>(&'r str);
5
6#[derive(Debug)]
7enum ApiKeyError {
8    Missing,
9    Invalid,
10}
11
12#[rocket::async_trait]
13impl<'r> FromRequest<'r> for ApiKey<'r> {
14    type Error = ApiKeyError;
15
16    async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
17        let api_key = "MY_SECRET_KEY"
18        let is_valid = |key: &str| -> bool {
19            key == api_key
20        };
21
22        match req.headers().get_one("X-Api-Key") {
23            None => Outcome::Error((Status::BadRequest, ApiKeyError::Missing)),
24            Some(key) if is_valid(key) => Outcome::Success(ApiKey(key)),
25            Some(key) if !is_valid(key) => Outcome::Error((Status::Unauthorized, ApiKeyError::Invalid)),
26            Some(_) => Outcome::Error((Status::InternalServerError, ApiKeyError::Invalid)),
27        }
28    }
29}
30
31#[get("/")]
32fn index(_key: ApiKey<'_>) -> Result<CustomResponse, WebError> {
33    return match reg.render_template(template, &json!({})) {
34        Ok(value) => {
35            Ok(HtmlTemplate("<html></html>"))
36        }
37        Err(_) => {
38            Err(Internal("Failed to render view.".to_string()))
39        }
40    };
41}

This code will make sure that every call to "/" without a valid api key is stopped by Rocket's request guard mechanism.

You can extend this mechanism to handle basic authentication by adjusting the FromRequest trait that we implemented for ApiKey.