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.toml
file.
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"