Skip to content

Authentication & Authorization

Enterprise-grade authentication system with JWT, MFA, API keys, and OAuth2 support.

Overview

The Authentication module (src/auth/) provides comprehensive security features:

  • JWT Authentication: Token-based auth with refresh
  • Password Hashing: Argon2 for secure storage
  • Multi-Factor Authentication: TOTP, SMS, Email 2FA
  • API Keys: Long-lived credentials with rotation
  • OAuth2: Social login integration
  • Session Management: Secure session handling
  • RBAC Integration: Role-based access control

Architecture

┌──────────────┐
│    Client    │
└──────┬───────┘
┌──────────────────────────────────────┐
│      AuthService                     │
│  (mod.rs - Core Authentication)     │
└──────┬───────────────────────────────┘
   ┌───┴────────────────────────────┐
   │                                │
┌──▼──────┐  ┌──────────┐  ┌───────▼──┐
│  JWT    │  │   MFA    │  │ API Keys │
│(Tokens) │  │(2-Factor)│  │          │
└─────────┘  └──────────┘  └──────────┘
       │            │             │
       └────────────┴─────────────┘
            ┌──────────────┐
            │  PostgreSQL  │
            │  (Users DB)  │
            └──────────────┘

Core Authentication

Main authentication service with JWT and password management:

use stratarouter_enterprise::auth::{AuthService, AuthConfig, Credentials};

// Configure authentication
let config = AuthConfig {
    jwt_secret: "your-secret-key".to_string(),
    access_token_expiry: 3600,        // 1 hour
    refresh_token_expiry: 604800,     // 7 days
    password_min_length: 12,
    require_mfa: false,
    max_failed_attempts: 5,
    lockout_duration: 900,            // 15 minutes
};

// Create auth service
let auth_service = AuthService::new(config, db_pool);

// Authenticate user
let token = auth_service.authenticate(Credentials {
    email: "user@example.com".to_string(),
    password: "SecurePassword123!".to_string(),
    tenant_id: Some("tenant_123".to_string()),
}).await?;

println!("Access token: {}", token.access_token);
println!("Refresh token: {}", token.refresh_token);
println!("Expires in: {}s", token.expires_in);

JWT Configuration

pub struct AuthConfig {
    /// JWT secret key
    pub jwt_secret: String,

    /// Access token expiry in seconds
    pub access_token_expiry: i64,

    /// Refresh token expiry in seconds
    pub refresh_token_expiry: i64,

    /// Minimum password length
    pub password_min_length: usize,

    /// Require MFA for all users
    pub require_mfa: bool,

    /// Max failed login attempts before lockout
    pub max_failed_attempts: u32,

    /// Account lockout duration in seconds
    pub lockout_duration: i64,
}

JWT Claims

Token payload structure:

pub struct JwtClaims {
    /// Subject (user ID)
    pub sub: String,

    /// Tenant ID
    pub tenant_id: String,

    /// User email
    pub email: String,

    /// User roles
    pub roles: Vec<String>,

    /// Issued at (timestamp)
    pub iat: i64,

    /// Expiration time (timestamp)
    pub exp: i64,

    /// JWT ID (unique token identifier)
    pub jti: String,
}

Token Response

pub struct AuthToken {
    /// JWT access token
    pub access_token: String,

    /// JWT refresh token
    pub refresh_token: String,

    /// Token type (always "Bearer")
    pub token_type: String,

    /// Expiry duration in seconds
    pub expires_in: i64,
}

Password Security

Argon2-based password hashing:

// Hash password on registration
let password_hash = auth_service.hash_password("UserPassword123!").await?;

// Store in database
db.execute(
    "INSERT INTO users (email, password_hash) VALUES ($1, $2)",
    &[email, password_hash]
).await?;

// Verify on login
let stored_hash = db.query_one(
    "SELECT password_hash FROM users WHERE email = $1",
    &[email]
).await?.get(0);

if auth_service.verify_password("UserPassword123!", &stored_hash)? {
    // Password correct
} else {
    // Password incorrect
}

Password Requirements

// Validate password strength
let password = "UserPassword123!";

if password.len() < config.password_min_length {
    return Err("Password too short");
}

// Additional checks (recommended):
if !has_uppercase(password) {
    return Err("Password must contain uppercase letter");
}

if !has_lowercase(password) {
    return Err("Password must contain lowercase letter");
}

if !has_digit(password) {
    return Err("Password must contain digit");
}

if !has_special_char(password) {
    return Err("Password must contain special character");
}

Argon2 Parameters

// Default Argon2 configuration
use argon2::{
    Argon2,
    password_hash::{PasswordHasher, SaltString},
    Params,
};

let params = Params::new(
    16384,  // Memory cost (16 MB)
    2,      // Time cost (iterations)
    1,      // Parallelism
    None,   // Output length (default)
)?;

let argon2 = Argon2::new(
    Algorithm::Argon2id,  // Recommended variant
    Version::V0x13,       // Latest version
    params,
);

Token Verification

Validate and decode JWT tokens:

// Verify access token
match auth_service.verify_token(&access_token) {
    Ok(claims) => {
        println!("User ID: {}", claims.sub);
        println!("Email: {}", claims.email);
        println!("Roles: {:?}", claims.roles);
        println!("Tenant: {}", claims.tenant_id);
    }
    Err(e) => {
        return Err(format!("Invalid token: {}", e));
    }
}

// Middleware example
async fn authenticate_request(
    req: Request,
    auth: AuthService,
) -> Result<User> {
    let auth_header = req.headers().get("Authorization")?;
    let token = auth_header.strip_prefix("Bearer ")?;

    let claims = auth.verify_token(token)?;

    // Load user from database
    let user = load_user(&claims.sub).await?;

    Ok(user)
}

Token Refresh

Renew access tokens without re-authentication:

// Refresh expired access token
let new_token = auth_service.refresh_token(&refresh_token).await?;

println!("New access token: {}", new_token.access_token);
println!("New refresh token: {}", new_token.refresh_token);

Token Revocation

Invalidate tokens:

// Revoke token (add to blacklist)
auth_service.revoke_token(&jti).await?;

// Check if token is revoked
let is_revoked = db.query_one(
    "SELECT EXISTS(SELECT 1 FROM revoked_tokens WHERE jti = $1)",
    &[jti]
).await?.get(0);

if is_revoked {
    return Err("Token has been revoked");
}

User Management

Create and manage users:

// Create new user
let user = auth_service.create_user(
    tenant_id: Uuid::parse_str("tenant_123")?,
    email: "newuser@example.com".to_string(),
    password: "SecurePassword123!".to_string(),
    roles: vec!["user".to_string()],
).await?;

println!("Created user: {}", user.id);
println!("Email: {}", user.email);
println!("Roles: {:?}", user.roles);

User Model

pub struct User {
    /// Unique user ID
    pub id: Uuid,

    /// Tenant ID
    pub tenant_id: Uuid,

    /// Email address
    pub email: String,

    /// Password hash (never exposed in API)
    #[serde(skip_serializing)]
    pub password_hash: String,

    /// User roles
    pub roles: Vec<String>,

    /// MFA enabled
    pub mfa_enabled: bool,

    /// MFA secret (never exposed in API)
    #[serde(skip_serializing)]
    pub mfa_secret: Option<String>,

    /// Created timestamp
    pub created_at: DateTime<Utc>,

    /// Updated timestamp
    pub updated_at: DateTime<Utc>,

    /// Last login timestamp
    pub last_login: Option<DateTime<Utc>>,

    /// Account active status
    pub active: bool,
}

Account Lockout

Protect against brute force attacks:

// Failed login attempt
if !verify_password(password, &user.password_hash) {
    // Record failed attempt
    auth_service.record_failed_attempt(&user.id).await?;

    // Check if should lock account
    let failed_attempts = get_failed_attempts(&user.id).await?;

    if failed_attempts >= config.max_failed_attempts {
        // Lock account
        lock_account(&user.id, config.lockout_duration).await?;
        return Err("Account locked due to too many failed attempts");
    }

    return Err("Invalid credentials");
}

// Successful login
reset_failed_attempts(&user.id).await?;

Multi-Factor Authentication

TOTP, SMS, and Email 2FA support:

use stratarouter_enterprise::auth::mfa::MfaService;

let mfa_service = MfaService::new();

// Enable MFA for user
let secret = mfa_service.enable_mfa(&user.id).await?;

// Generate QR code for authenticator app
let qr_code_url = mfa_service.generate_qr_code(
    &user.email,
    &secret,
    "StrataRouter",
).await?;

println!("Scan this QR code: {}", qr_code_url);
println!("Or enter secret manually: {}", secret);

TOTP Verification

// Verify TOTP code during login
let totp_code = "123456"; // From authenticator app

if mfa_service.verify_totp(&user.id, totp_code).await? {
    // MFA successful, issue token
    let token = auth_service.generate_token(&user)?;
    Ok(token)
} else {
    Err("Invalid MFA code")
}

SMS/Email 2FA

// Send SMS code
mfa_service.send_sms_code(&user.phone_number).await?;

// Send email code
mfa_service.send_email_code(&user.email).await?;

// Verify code
if mfa_service.verify_code(&user.id, &code).await? {
    // MFA successful
} else {
    Err("Invalid verification code")
}

Backup Codes

Generate one-time recovery codes:

// Generate backup codes
let backup_codes = mfa_service.generate_backup_codes(&user.id, 10).await?;

for code in &backup_codes {
    println!("Backup code: {}", code);
}

// Use backup code
if mfa_service.verify_backup_code(&user.id, &code).await? {
    // Backup code valid, mark as used
    mfa_service.consume_backup_code(&user.id, &code).await?;
} else {
    Err("Invalid backup code")
}

API Keys

Long-lived credentials for programmatic access:

use stratarouter_enterprise::auth::api_keys::ApiKeyService;

let api_key_service = ApiKeyService::new();

// Create API key
let api_key = api_key_service.create_api_key(CreateApiKeyRequest {
    user_id: user.id,
    name: "Production Server".to_string(),
    scopes: vec!["routing:read".to_string(), "routing:write".to_string()],
    expires_at: Some(Utc::now() + Duration::days(365)),
}).await?;

println!("API Key: {}", api_key.key);
println!("Store this securely - it won't be shown again!");

API Key Authentication

// Authenticate with API key
async fn authenticate_api_key(
    key: &str,
    api_key_service: &ApiKeyService,
) -> Result<User> {
    let api_key_record = api_key_service.verify_api_key(key).await?;

    // Check expiration
    if let Some(expires_at) = api_key_record.expires_at {
        if Utc::now() > expires_at {
            return Err("API key has expired");
        }
    }

    // Check if revoked
    if api_key_record.revoked {
        return Err("API key has been revoked");
    }

    // Load user
    let user = load_user(&api_key_record.user_id).await?;

    Ok(user)
}

Key Rotation

// Rotate API key
let new_key = api_key_service.rotate_api_key(&old_key_id).await?;

println!("Old key revoked");
println!("New key: {}", new_key.key);

// List user's API keys
let keys = api_key_service.list_api_keys(&user.id).await?;

for key in keys {
    println!("{}: {}", key.name, key.key_prefix);
    println!("  Created: {}", key.created_at);
    println!("  Last used: {:?}", key.last_used_at);
}

OAuth2 Integration

Social login support:

use stratarouter_enterprise::auth::oauth::OAuthService;

let oauth_service = OAuthService::new(OAuthConfig {
    google_client_id: "your-google-client-id".to_string(),
    google_client_secret: "your-google-secret".to_string(),
    github_client_id: "your-github-client-id".to_string(),
    github_client_secret: "your-github-secret".to_string(),
    redirect_url: "https://yourapp.com/oauth/callback".to_string(),
});

// Generate OAuth URL
let auth_url = oauth_service.get_authorization_url(
    OAuthProvider::Google,
    "state_token_123",
).await?;

println!("Redirect user to: {}", auth_url);

OAuth Callback

// Handle OAuth callback
async fn oauth_callback(
    code: String,
    state: String,
    provider: OAuthProvider,
) -> Result<AuthToken> {
    // Exchange code for access token
    let oauth_token = oauth_service.exchange_code(provider, &code).await?;

    // Get user info from provider
    let user_info = oauth_service.get_user_info(provider, &oauth_token).await?;

    // Find or create user
    let user = match find_user_by_email(&user_info.email).await {
        Some(user) => user,
        None => create_user_from_oauth(user_info).await?,
    };

    // Generate JWT token
    let token = auth_service.generate_token(&user)?;

    Ok(token)
}

Session Management

Server-side session tracking:

use stratarouter_enterprise::auth::sessions::SessionService;

let session_service = SessionService::new();

// Create session
let session = session_service.create_session(CreateSessionRequest {
    user_id: user.id,
    ip_address: "192.168.1.1".to_string(),
    user_agent: "Mozilla/5.0 ...".to_string(),
    expires_at: Utc::now() + Duration::hours(24),
}).await?;

println!("Session ID: {}", session.id);

// Validate session
if session_service.validate_session(&session_id).await? {
    // Session valid
} else {
    // Session expired or invalid
    return Err("Session expired, please login again");
}

// Terminate session (logout)
session_service.terminate_session(&session_id).await?;

Complete Integration

Full authentication flow:

use stratarouter_enterprise::auth::*;

#[tokio::main]
async fn main() -> Result<()> {
    // 1. Configure authentication
    let config = AuthConfig {
        jwt_secret: env::var("JWT_SECRET")?,
        access_token_expiry: 3600,
        refresh_token_expiry: 604800,
        password_min_length: 12,
        require_mfa: true,
        max_failed_attempts: 5,
        lockout_duration: 900,
    };

    // 2. Create services
    let auth_service = AuthService::new(config, db_pool.clone());
    let mfa_service = MfaService::new();
    let api_key_service = ApiKeyService::new();

    // 3. Registration
    let user = auth_service.create_user(
        tenant_id,
        "user@example.com".to_string(),
        "SecurePassword123!".to_string(),
        vec!["user".to_string()],
    ).await?;

    // 4. Enable MFA
    let mfa_secret = mfa_service.enable_mfa(&user.id).await?;
    let qr_code = mfa_service.generate_qr_code(
        &user.email,
        &mfa_secret,
        "StrataRouter",
    ).await?;

    // 5. Login
    let token = auth_service.authenticate(Credentials {
        email: "user@example.com".to_string(),
        password: "SecurePassword123!".to_string(),
        tenant_id: Some(tenant_id.to_string()),
    }).await?;

    // 6. Verify MFA
    let totp_code = "123456"; // From authenticator
    if !mfa_service.verify_totp(&user.id, totp_code).await? {
        return Err("Invalid MFA code");
    }

    // 7. Use token
    let claims = auth_service.verify_token(&token.access_token)?;
    println!("Authenticated as: {}", claims.email);

    // 8. Create API key for automation
    let api_key = api_key_service.create_api_key(CreateApiKeyRequest {
        user_id: user.id,
        name: "CI/CD Pipeline".to_string(),
        scopes: vec!["routing:read".to_string()],
        expires_at: Some(Utc::now() + Duration::days(90)),
    }).await?;

    Ok(())
}

See Also