Posts Use Rocket's managed state within request guards
Post
Cancel

Use Rocket's managed state within request guards

The problem

Request guards represents some validation policy and is one of Rocket’s most powerful instruments. You can read more about guards in the docs. For example you can use guards to add authentication to handlers, there is even a nice example in the api doc. However, for simplification the valid api key is hard coded in the example. But we dont want this in a real project, so the problem is that we want to have a configurable api key.

The solution

One way to have a configurable api key is by using Rocket’s configuration abilities. You define a Rocket.toml that is used to configure Rocket but you can also add additional configuration parameters, like api_token. This file is committed so you cannot have your production secrets in it. In production you would overwrite your secrets with ROCKET_env variables, as these have a higher priority than the Rocket.tomlfile.

Then you can use AdHoc::config() to store a typed config in managed state.

And then retrieve it with request.rocket().state::<Config>().

The following code shows this in action based on the example taken from Rocket’s docs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// src/main.rs
#[macro_use] extern crate rocket;

use rocket::http::Status;
use rocket::request::{Outcome, Request, FromRequest};
use rocket::State;
use rocket::fairing::AdHoc;
use serde::Deserialize;

#[derive(Deserialize)]
struct Config {
    api_key: String,
}

struct ApiKey<'r>(&'r str);

#[derive(Debug)]
enum ApiKeyError {
    Missing,
    Invalid,
}

#[rocket::async_trait]
impl<'r> FromRequest<'r> for ApiKey<'r> {
    type Error = ApiKeyError;

    async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {

        // Retrieve the config state like this
        let config = req.rocket().state::<Config>().unwrap();

        fn is_valid(key: &str, api_key: &str) -> bool {
            key == api_key
        }

        match req.headers().get_one("Authorization") {
            None => Outcome::Failure((Status::Unauthorized, ApiKeyError::Missing)),
            Some(key) if is_valid(key, &config.api_key) => Outcome::Success(ApiKey(key)),
            Some(_) => Outcome::Failure((Status::Unauthorized, ApiKeyError::Invalid)),
        }
    }
}

#[get("/")]
async fn index(_config: State<'_, Config>, _key: ApiKey<'_>) -> &'static str {
    "Hello, world!"
}

fn rocket() -> rocket::Rocket {
    let rocket = rocket::ignite();
    let figment = rocket.figment();

    let _config: Config = figment.extract().expect("config");

    // Store the typed config in managed state
    rocket
        .mount("/", routes![index])
        .attach(AdHoc::config::<Config>())
}

#[rocket::main]
async fn main() {
    rocket()
        .launch()
        .await;
}
1
2
3
4
5
6
7
8
9
10
# Cargo.toml
[package]
name = "example"
version = "0.1.0"
edition = "2018"

[dependencies]
# Need latest version of Rocket (min. 5.0.0) as guard with async_trait is used
rocket = { git = "https://github.com/SergioBenitez/Rocket", version = "0.5.0-dev" }
serde = { version = "1.0", features = ["derive"] }
1
2
3
4
5
6
7
8
9
10
# Rocket.toml
[default]
api_key = "secret-api-key"

[debug]
api_key = "secret-api-key"

[release]
api_key = "secret-api-key"

References

This post is licensed under CC BY 4.0 by the author.