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¶
- RBAC - Role-based access control
- Multi-Tenancy - Tenant isolation
- Security - Security best practices
- Audit Logging - Compliance and audit trails