Basic Authentication

A middleware that provides support for Basic Auth.

Introduction to Basic Auth

Basic Auth is a simple HTTP authentication mechanism that transmits a username and password via the Authorization header in HTTP requests. The format is Authorization: Basic <base64 username:password>. While simple, since credentials are only Base64-encoded rather than encrypted, it is typically used with HTTPS to ensure security.

Comparison of Basic Auth Implementations in Common Frameworks

FrameworkLanguageBasic Auth Implementation
SalvoRustImplemented via the BasicAuth middleware and custom BasicAuthValidator
ExpressJavaScriptUses the express-basic-auth middleware package
Spring SecurityJavaConfigured via httpBasic() and implemented with UserDetailsService
ASP.NET CoreC#Uses .UseAuthentication() and AddAuthentication(AuthenticationSchemes.Basic)
GinGoImplemented via custom middleware or using the gin-contrib/auth package

Use Cases

Basic Auth is suitable for the following scenarios:

  1. Internal APIs and Tools: Management tools and APIs used within a company
  2. Development and Testing Environments: Quickly implement authentication without a complex login system
  3. Simple API Protection: When a complex user management system is not required
  4. Combined with Other Security Measures: As part of a multi-layered security architecture

In Salvo, the Basic Auth middleware can be easily integrated into routes. By implementing the BasicAuthValidator trait, custom validation logic can be defined, offering great flexibility.

Considerations

  • Always use with HTTPS to protect credential transmission
  • Not suitable for production environments storing sensitive information
  • Consider using more secure authentication methods such as JWT or OAuth for production environments
main.rs
Cargo.toml
use salvo::basic_auth::{BasicAuth, BasicAuthValidator};
use salvo::prelude::*;

// Custom validator implementing BasicAuthValidator trait
struct Validator;
impl BasicAuthValidator for Validator {
    // Validate username and password combination
    async fn validate(&self, username: &str, password: &str, _depot: &mut Depot) -> bool {
        username == "root" && password == "pwd"
    }
}

// Simple handler that returns "Hello" for authenticated requests
#[handler]
async fn hello() -> &'static str {
    "Hello"
}

// Create router with basic authentication middleware
fn route() -> Router {
    // Initialize basic authentication handler with our validator
    let auth_handler = BasicAuth::new(Validator);
    // Apply authentication middleware to the router
    Router::with_hoop(auth_handler).goal(hello)
}

#[tokio::main]
async fn main() {
    // Initialize logging
    tracing_subscriber::fmt().init();

    // Bind server to port 8698 and start serving
    let acceptor = TcpListener::new("0.0.0.0:8698").bind().await;
    Server::new(acceptor).serve(route()).await;
}

#[cfg(test)]
mod tests {
    use salvo::prelude::*;
    use salvo::test::{ResponseExt, TestClient};

    #[tokio::test]
    async fn test_basic_auth() {
        // Create a service instance from our router for testing purposes
        let service = Service::new(super::route());

        // Test case 1: Verify successful authentication with valid credentials
        let content = TestClient::get("http://0.0.0.0:8698/")
            .basic_auth("root", Some("pwd")) // Use correct username/password
            .send(&service) // Send the request to the service
            .await
            .take_string() // Extract response body as string
            .await
            .unwrap();
        // Verify response contains expected "Hello" message
        assert!(content.contains("Hello"));

        // Test case 2: Verify authentication failure with invalid password
        let content = TestClient::get("http://0.0.0.0:8698/")
            .basic_auth("root", Some("pwd2")) // Use incorrect password
            .send(&service) // Send the request to the service
            .await
            .take_string() // Extract response body as string
            .await
            .unwrap();
        // Verify response contains "Unauthorized" error
        assert!(content.contains("Unauthorized"));
    }
}