Rust Actix-Web HTTP Basic Auth

Protect simple admin website areas with basic auth in actix-web, including safety against timing attacks. Pragmatic choice for single-admin web sites or quickly securing a resource.


NOTE: basic auth is considered "secure enough" only when combined with TLS. The credentials are sent with every single request, so any traffic sniffing could instantly get access keys when transferred unencrypted.

to protect a bunch of resources in actix-web, we can define a web handler that can be used as follows:

use actix_web_httpauth::middleware::HttpAuthentication:

// ... -snip-
.wrap(HttpAuthentication::basic(validator))
// ... -snip-

this is how the validator function can be built:

use actix_web::{dev::ServiceRequest, Error};
use actix_web_httpauth::extractors::AuthenticationError;
use actix_web_httpauth::{extractors::basic::BasicAuth, headers::www_authenticate};

pub async fn validator(
    req: ServiceRequest,
    credentials: BasicAuth,
) -> Result<ServiceRequest, (Error, ServiceRequest)> {
    if secure_compare(
        credentials.user_id(),
        credentials.password().unwrap_or_default(),
    ) {
        Ok(req)
    } else {
        let challenge = www_authenticate::basic::Basic::new();
        Err((AuthenticationError::new(challenge).into(), req))
    }
}

note that the ADMIN_PASSWORD is hashed only once when first used, in order to save computation on subsequent calls (strong hashing is cpu intensive!).

next on, we need the function to compare the given credentials with the hardcoded ones on the server, using a constant time comparison to prevent timing attacks:

use constant_time_eq::constant_time_eq;

const SALT: &str = "CRYPTIC HASH";
const ADMIN_USER: &str = "USERNAME";
lazy_static::lazy_static! {
    static ref ADMIN_PASSWORD: [u8; 32] = hash_string("PASSWORD");
}

fn secure_compare(username: &str, password: &str) -> bool {
    if username != ADMIN_USER {
        return false;
    }

    if password.is_empty() {
        return false;
    }

    let input_password = hash_string(password);

    constant_time_eq(&input_password, &ADMIN_PASSWORD[..])
}

and finally the hashing function used above, that generates salted hashes of a constant length so the constant_time_eq works optimally:

use bcrypt_pbkdf::bcrypt_pbkdf;

fn hash_string(string: &str) -> [u8; 32] {
    let mut output = [0u8; 32];
    bcrypt_pbkdf(string.as_bytes(), SALT.as_bytes(), 10, &mut output)
        .expect("bcrypt_pbkdf failed on password");
    output
}

Linked Technologies

What it's made of

illustration of Actix-Web
Actix-Web

Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust. It runs on top of tokio and is probably the most mature rust web framework, with lots of available crates.

illustration of Rust
Rust

A language empowering everyone to build reliable and efficient software. Futuristic swiss army knife and probably the most powerful mainstream technology today

Linked Categories

Where it's useful

illustration of Backend Development
Backend Development

Uncover the power behind the scenes of web services and applications, focusing on creating robust, scalable backend systems that support frontend experiences.

illustration of DevOps
DevOps

Get a glimpse into the world of DevOps, where coding meets collaboration, speeding up everything from software builds to system fixes. Learn about the seamless integration of development and operations to make tech life easier and more efficient.