DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Efficient API Communication With Spring WebClient
  • Enhanced API Security: Fine-Grained Access Control Using OPA and Kong Gateway
  • HTTP API: Key Skills for Smooth Integration and Operation (Part 1)
  • Rate Limiting Strategies for Efficient Traffic Management

Trending

  • AI Speaks for the World... But Whose Humanity Does It Learn From?
  • Simpler Data Transfer Objects With Java Records
  • The Future of Java and AI: Coding in 2025
  • Implementing Explainable AI in CRM Using Stream Processing
  1. DZone
  2. Data Engineering
  3. Databases
  4. Rocket.rs API Demo Implementing Header-Based API Key Protection

Rocket.rs API Demo Implementing Header-Based API Key Protection

How to set up an API using the Rocket.rs Rust crate, with some basic functionality and protection using a header-based API key.

By 
James Koonts user avatar
James Koonts
·
Aug. 30, 23 · Tutorial
Likes (1)
Comment
Save
Tweet
Share
2.1K Views

Join the DZone community and get the full member experience.

Join For Free

Check out the latest Rocket Documentation.

This is the first in a series of tutorials on working with Rocket.

Prerequisites

This article assumes that you have some familiarity with Rust, as well as the Rocket crate, and both of their semantics.

If you're more experienced with Rocket, you may wish to jump to the creating an API Key fairing section.

This article will walk you through setting up an API with some basic functionality and add protection using a header-based API key.

Side note: Throughout this tutorial, I use the > character to indicate your terminal's shell prompt. When following along, don't type the > character, or you'll get some weird errors.

Install Rustup and Cargo

Working with Rocket requires Rust. Some crates may require the Nightly Rust release channel.

I recommend using the latest stable version of Rust, but anything in Stable or Nightly should work fine. If you want to change the current channel type, information can always be found in the Rust Language Book.

Install Rust

> curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh


Change Release Channel

# for nightly channel
> rustup default nightly
# for stable channel
> rustup default stable
# update channel indexes
> rustup update


Create an Empty Project

You will need a place to put your work, so open a terminal to make a new directory for your project somewhere and set it up as a new Rust project using Cargo:

# Cargo creates a directory for the project then you can `cd` into it
> cargo new rocket-api
> cd rocket-api

# Cargo creates a git repository for us
> git status

# Cargo initializes new projects with a Test 'helloworld', let's run that.
> cargo run


Configure Rocket's Dependencies

Rocket is a very modular and extensible framework, which allows Rust devs to target different runtime targets and opt into various features by including a custom selection of modules.

If you're new to Rocket, I do recommend configuring your project in stages since this can make troubleshooting configuration issues much easier.

# Add the following lines to your Cargo.toml file, located in the root of the project directory.
# [dependencies]
rocket = { version = "^0.5.0-rc.2", features = ["json", "secrets"] }
config = {version = "0.13.1", features = ["json5"] }


Go ahead and run a build to compile our dependencies:

> cargo build


Basic Setup

Now that you have Rocket installed, configure what's needed to get your server up and running. At the bare minimum, Rocket requires a root route/path and the 'main' function block.

#[macro_use]
extern crate rocket;
use rocket::*;

#[get("/")]
async fn index() -> &'static str {
    "Hello, Astronauts!"
}

#[rocket::main]
pub async fn main() {

    build()
        .mount(
            "/",
            routes![
                index,
            ],
        )
        .launch()
        .await
        .unwrap();
}


Start and Test the Server

> cargo run
> curl http://127.0.0.1:8000


Observe the output from your Rocket server, as well as the response to your curl.

Setup Configuration File

It is not recommended to hard-code any API keys, so set up your project to derive some settings from a config file.

Create a Settings.toml Within [project-root]/config/

api_key = "yourapikey"
port = 8000


Add the Following to Your Main Function Block To Finish Configuring the Settings File

    // load toml config file
    let settings = Config::builder()
        .add_source(config::File::with_name("config/Settings"))
        .build()
        .unwrap();

    // deserialize settings file to HashMap
    let settings_map = settings
        .try_deserialize::<HashMap<String, String>>()
        .unwrap();

    // retrieve settings and create custom Rocket config object
    let config = rocket::Config {
        port: settings_map.clone().get("port").unwrap().parse().unwrap(),
        address: std::net::Ipv4Addr::new(0, 0, 0, 0).into(),
        ..rocket::Config::debug_default()
    };


The Updated main.rs File Will Now Look Like the Following

Notice that I changed the 'build' method to 'custom' in order to pass our custom Rocket configuration. I also used the .manage() method to ask Rocket to maintain the state of my settings_map object. This allows the use of the object within any of the routes.

#[macro_use]
extern crate rocket;
use std::collections::HashMap;
use config::Config;
use rocket::{custom, tokio};

#[get("/")]
async fn index(
    settings_map: &rocket::State<HashMap<String, String>>, // Rocket is managing settings_map so it may be accessed within our route
) -> &'static str {
    "Hello, Astronauts!"
}

#[rocket::main]
pub async fn main() {

    // load toml config file
    let settings = Config::builder()
        .add_source(config::File::with_name("config/Settings"))
        .build()
        .unwrap();

    // deserialize settings file to HashMap
    let settings_map = settings
        .try_deserialize::<HashMap<String, String>>()
        .unwrap();

    // retrieve settings and create custom Rocket config object
    let config = rocket::Config {
        port: settings_map.clone().get("port").unwrap().parse().unwrap(),
        address: std::net::Ipv4Addr::new(0, 0, 0, 0).into(),
        ..rocket::Config::debug_default()
    };

    custom(&config)
        .manage::<HashMap<String, String>>(settings_map.clone()) // asking Rocket to manage settings_map so that it may be accessed within the routes
        .mount(
            "/",
            routes![
                index,
            ],
        )
        .launch()
        .await
        .unwrap();
}


Add the Following To main.rs to Configure the “Rocket Fairing”

Adding ApiKey<'_> as a required parameter will allow only requests that include a matching header. If a request does not contain a matching header, the server will respond with a 4xx error. After verifying the presence of our header, the fairing will pass the key into our route/path. At this point within the function, confirm the key against api_key in the config file prior to responding to the client.

ApiKey Fairing Section

This section can be copy-pasted into an existing project and used by merely adding ApiKey<'_> to a route. This will not confirm the validity of the API key, though; that must be done within the route function using your own logic or logic based off of that in this example.

See below for a complete working example:

    use rocket::form::validate::Len;
    use rocket::http::Status;
    use rocket::outcome::{Outcome};
    use rocket::request::{Request, FromRequest};

    #[derive(PartialEq)]
    pub struct ApiKey<'r>(pub(crate) &'r str);

    #[derive(Debug)]
    pub enum ApiKeyError {
        MissingError,
        InvalidError,
    }

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

        async fn from_request(req: &'r Request<'_>) -> Outcome<ApiKey<'r>, (Status, ApiKeyError), ()> {
            /// Returns true if `key` is a valid API key string.
            fn is_valid(key: &str) -> bool {
                key.len() > 0
            }

            match req.headers().get_one("x-api-key") {
                None => Outcome::Failure((Status::BadRequest, ApiKeyError::MissingError)),
                Some(key) if is_valid(key) => Outcome::Success(ApiKey(key)),
                Some(_) => Outcome::Failure((Status::BadRequest, ApiKeyError::InvalidError)),
            }
        }
    }


Our Full Working Example

This entire section can be copy-pasted into a project to be used freely as a template. A more complicated example, which includes a database connection, may be found at Air Quality.

#[macro_use]
extern crate rocket;
use std::collections::HashMap;
use config::Config;
use rocket::{custom, tokio};
use rocket::form::validate::Len;
use rocket::http::Status;
use rocket::outcome::{Outcome};
use rocket::request::{Request, FromRequest};


#[get("/")]
async fn index(
    settings_map: &rocket::State<HashMap<String, String>>,
    key:ApiKey<'_> // if match on FromRequest is true, which checks for 'x-api-key' header, use this route
) -> &'static str {
    if key.0.to_string() == settings_map.get("api_key").unwrap().to_string() { // compare given key to config file
        "Hello, Astronauts!" // correct key
    } else {
        "Pool is closed" // incorrect key
    }
}


#[rocket::main]
pub async fn main() {

    // load toml config file
    let settings = Config::builder()
        .add_source(config::File::with_name("config/Settings"))
        .build()
        .unwrap();

    // deserialize settings file to HashMap
    let settings_map = settings
        .try_deserialize::<HashMap<String, String>>()
        .unwrap();

    // retrieve settings and create custom Rocket config object
    let config = rocket::Config {
        port: settings_map.clone().get("port").unwrap().parse().unwrap(),
        address: std::net::Ipv4Addr::new(0, 0, 0, 0).into(),
        ..rocket::Config::debug_default()
    };

    custom(&config)
        .manage::<HashMap<String, String>>(settings_map.clone()) // asking Rocket to manage settings_map so that it may be accessed within the routes
        .mount(
            "/",
            routes![
                index,
            ],
        )
        .launch()
        .await
        .unwrap();
}


#[derive(PartialEq)]
pub struct ApiKey<'r>(pub(crate) &'r str);

#[derive(Debug)]
pub enum ApiKeyError {
    MissingError,
    InvalidError,
}

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

    async fn from_request(req: &'r Request<'_>) -> Outcome<ApiKey<'r>, (Status, ApiKeyError), ()> {
        /// Returns true if `key` is a valid API key string.
        fn is_valid(key: &str) -> bool {
            key.len() > 0
        }

        match req.headers().get_one("x-api-key") {
            None => Outcome::Failure((Status::BadRequest, ApiKeyError::MissingError)),
            Some(key) if is_valid(key) => Outcome::Success(ApiKey(key)),
            Some(_) => Outcome::Failure((Status::BadRequest, ApiKeyError::InvalidError)),
        }
    }
}


Testing the API Key Protected Path, Using a FromRequest Fairing

Requests with an ApiKey<'_> parameter, stored in the corresponding header, should now only match with functions using the FromRequest implementation, also known as a Rocket Fairing. Observe how the endpoint will now only respond if the key passed by the request header and the key stored in Settings.toml match.

> curl -H 'x-api-key:yourapikey' http://127.0.0.1:8000
API Requests Rust (programming language)

Opinions expressed by DZone contributors are their own.

Related

  • Efficient API Communication With Spring WebClient
  • Enhanced API Security: Fine-Grained Access Control Using OPA and Kong Gateway
  • HTTP API: Key Skills for Smooth Integration and Operation (Part 1)
  • Rate Limiting Strategies for Efficient Traffic Management

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!