A Basic Authentication middleware for the ntex web framework.
Crates.io Documentation License: MIT Rust
- Cache - Built-in authentication result cache to reduce validation overhead
- Flexible Configuration - Supports multiple user validation methods
- Path Filtering - Supports skipping authentication for specific paths
- BCrypt Support - BCrypt password hashing (enabled by default, disable with
default-features = false) - JSON Response - Optional JSON error response (requires
jsonfeature) - Custom Validator - Support for custom user validation logic
- Regex Paths - Regular expression path matching (requires
regexfeature) - Safety - Timing-safe password comparison (
timing-safe, enabled by default). Automatic password memory cleanup usingzeroizecrate (secure-memory, enabled by default)
Add the dependency in your Cargo.toml:
[dependencies] ntex-basicauth = "0"
Enable optional features if needed:
[dependencies] ntex-basicauth = { version = "0", features = ["bcrypt", "regex"] }
use ntex::web; use ntex_basicauth::BasicAuthBuilder; use std::collections::HashMap; #[ntex::main] async fn main() -> std::io::Result<()> { web::HttpServer::new(move || { let mut users = HashMap::new(); users.insert("admin".to_string(), "secret".to_string()); users.insert("user".to_string(), "password".to_string()); let auth = BasicAuthBuilder::new() .users(users) .realm("My Application") .build() .expect("Failed to configure authentication"); web::App::new() .route( web::scope("/protected") .wrap(auth) .route("/", web::get().to(protected_handler)), ) .route("/public", web::get().to(public_handler)) }) .bind("127.0.0.1:8080")? .run() .await } async fn protected_handler() -> &'static str { "This is protected content!" } async fn public_handler() -> &'static str { "This is public content" }
use ntex_basicauth::{BasicAuthBuilder, PathFilter}; use std::collections::HashMap; let mut users = HashMap::new(); users.insert("admin".to_string(), "secret".to_string()); let filter = PathFilter::new() .skip_prefix("/public/") .skip_exact("/health") .skip_suffix(".css"); let auth = BasicAuthBuilder::new() .users(users) .realm("My Application") .path_filter(filter) .log_failures(true) .max_header_size(4096) .build() .unwrap();
Enable the regex feature in Cargo.toml:
[dependencies] ntex-basicauth = { version = "0", features = ["regex"] }
use ntex_basicauth::{BasicAuthBuilder, PathFilter}; let filter = PathFilter::new() .skip_regex(r"^/assets/.*\.(js|css|png|jpg)$").unwrap(); let auth = BasicAuthBuilder::new() .user("admin", "secret") .path_filter(filter) .build() .unwrap();
Enable the bcrypt feature in Cargo.toml:
[dependencies] ntex-basicauth = { version = "0", features = ["bcrypt"] }
use ntex_basicauth::{BasicAuthBuilder, BcryptUserValidator}; use std::sync::Arc; let mut validator = BcryptUserValidator::new(); validator.add_user_with_password("admin".to_string(), "secret").unwrap(); let auth = BasicAuthBuilder::new() .validator(Arc::new(validator)) .realm("My Application") .build() .unwrap();
use ntex_basicauth::{UserValidator, Credentials, AuthResult, BasicAuthBuilder}; use std::sync::Arc; use std::future::Future; use std::pin::Pin; struct DatabaseValidator; impl UserValidator for DatabaseValidator { fn validate<'a>( &'a self, credentials: &'a Credentials, ) -> Pin<Box<dyn Future<Output = AuthResult<bool>> + Send + 'a>> { Box::pin(async move { // Replace with your DB logic Ok(credentials.username == "admin" && credentials.password == "secret") }) } } let auth = BasicAuthBuilder::new() .validator(Arc::new(DatabaseValidator)) .realm("Custom Realm") .build() .unwrap();
Get authenticated user information in the request handler:
use ntex::web; use ntex_basicauth::{extract_credentials, get_username, is_user}; async fn handler(req: web::HttpRequest) -> web::Result<String> { if let Some(credentials) = extract_credentials(&req) { return Ok(format!("User: {}", credentials.username)); } if let Some(username) = get_username(&req) { return Ok(format!("Welcome, {}!", username)); } if is_user(&req, "admin") { return Ok("Admin access granted".to_string()); } Ok("Unknown user".to_string()) }
You can use the PathFilter builder for convenient filter creation:
use ntex_basicauth::PathFilter; let filter = PathFilter::new() .skip_exact("/health") .skip_exact("/metrics") .skip_prefix("/public/") .skip_suffix(".css") .skip_suffix(".js");
Use built-in common skip paths for typical web applications (health checks, static assets, etc.):
use ntex_basicauth::{BasicAuthBuilder, PathFilter}; // Create a filter with common web paths let common_filter = PathFilter::new() .skip_exact("/health") .skip_exact("/metrics") .skip_exact("/favicon.ico") .skip_prefix("/static/") .skip_prefix("/assets/") .skip_suffix(".css") .skip_suffix(".js") .skip_suffix(".png") .skip_suffix(".jpg") .skip_suffix(".ico"); let auth = BasicAuthBuilder::new() .user("admin", "secret") .path_filter(common_filter) .build() .unwrap();
When authentication fails, the middleware returns HTTP 401 status code and corresponding error information:
{
"code": 401,
"message": "Authentication required",
"error": "Invalid credentials"
}Error types include:
MissingHeader- Missing Authorization headerInvalidFormat- Invalid Authorization header formatInvalidBase64- Invalid Base64 encodingInvalidCredentials- Invalid user credentialsValidationFailed- User validation failed
use ntex_basicauth::{BasicAuthBuilder, CacheConfig, PathFilter}; use std::time::Duration; // Production-ready configuration with security hardening let cache_config = CacheConfig::new() .max_size(1000) .ttl_minutes(10) .cleanup_interval_seconds(300); // Common paths to skip authentication let skip_paths = PathFilter::new() .skip_exact("/health") .skip_exact("/metrics") .skip_prefix("/static/") .skip_suffix(".css") .skip_suffix(".js"); let auth = BasicAuthBuilder::new() .users_from_file("users.txt") // Load users from file .realm("Production API") .with_cache(cache_config) .max_concurrent_validations(100) // Prevent resource exhaustion .validation_timeout(Duration::from_secs(30)) .rate_limit_per_ip(10, Duration::from_secs(60)) // 10 req/min per IP .log_usernames_in_production(false) // Security: no username logging .path_filter(skip_paths) // Skip health checks, assets .build() .unwrap();
- Memory Security: The
secure-memoryfeature (enabled by default) automatically clears password data from memory after use - Cache Security: Cache keys are SHA256-hashed with application-specific salt to prevent rainbow table attacks
- Production Logging: Set
log_usernames_in_production(false)to prevent username leakage in production logs - DoS Protection: Configure rate limiting and concurrent validation limits to prevent resource exhaustion
Cache is enabled by default (unless disabled via builder/config):
use ntex_basicauth::{BasicAuthBuilder, CacheConfig}; // High-traffic configuration let cache_config = CacheConfig::new() .max_size(10000) // Large cache for busy servers .ttl_minutes(5) // Short TTL for security .cleanup_interval_seconds(60) // Frequent cleanup .enable_stats(true); // Monitor cache performance let auth = BasicAuthBuilder::new() .user("admin", "secret") .with_cache(cache_config) .build() .unwrap();
Licensed under the MIT License. See LICENSE for details.