thacoon's Blog

Use Rocket's managed state within request guards

· thacoon

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// src/main.rs
 2#[macro_use] extern crate rocket;
 3
 4use rocket::http::Status;
 5use rocket::request::{Outcome, Request, FromRequest};
 6use rocket::State;
 7use rocket::fairing::AdHoc;
 8use serde::Deserialize;
 9
10#[derive(Deserialize)]
11struct Config {
12    api_key: String,
13}
14
15struct ApiKey<'r>(&'r str);
16
17#[derive(Debug)]
18enum ApiKeyError {
19    Missing,
20    Invalid,
21}
22
23#[rocket::async_trait]
24impl<'r> FromRequest<'r> for ApiKey<'r> {
25    type Error = ApiKeyError;
26
27    async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
28
29        // Retrieve the config state like this
30        let config = req.rocket().state::<Config>().unwrap();
31
32        fn is_valid(key: &str, api_key: &str) -> bool {
33            key == api_key
34        }
35
36        match req.headers().get_one("Authorization") {
37            None => Outcome::Failure((Status::Unauthorized, ApiKeyError::Missing)),
38            Some(key) if is_valid(key, &config.api_key) => Outcome::Success(ApiKey(key)),
39            Some(_) => Outcome::Failure((Status::Unauthorized, ApiKeyError::Invalid)),
40        }
41    }
42}
43
44#[get("/")]
45async fn index(_config: State<'_, Config>, _key: ApiKey<'_>) -> &'static str {
46    "Hello, world!"
47}
48
49fn rocket() -> rocket::Rocket {
50    let rocket = rocket::ignite();
51    let figment = rocket.figment();
52
53    let _config: Config = figment.extract().expect("config");
54
55    // Store the typed config in managed state
56    rocket
57        .mount("/", routes![index])
58        .attach(AdHoc::config::<Config>())
59}
60
61#[rocket::main]
62async fn main() {
63    rocket()
64        .launch()
65        .await;
66}
 1# Cargo.toml
 2[package]
 3name = "example"
 4version = "0.1.0"
 5edition = "2018"
 6
 7[dependencies]
 8# Need latest version of Rocket (min. 5.0.0) as guard with async_trait is used
 9rocket = { git = "https://github.com/SergioBenitez/Rocket", version = "0.5.0-dev" }
10serde = { version = "1.0", features = ["derive"] }
1# Rocket.toml
2[default]
3api_key = "secret-api-key"
4
5[debug]
6api_key = "secret-api-key"
7
8[release]
9api_key = "secret-api-key"

References

#rust #rocket

Reply to this post by email ↪