A Basic Authentication middleware for the ntex web framework.
- 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
json
feature) - Custom Validator - Support for custom user validation logic
- Regex Paths - Regular expression path matching (requires
regex
feature) - Safety - Timing-safe password comparison (
timing-safe
, enabled by default). Automatic password memory cleanup usingzeroize
crate (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-memory
feature (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.