modo-auth 0.3.2

Authentication framework for modo
Documentation
modo-auth-0.3.2 has been yanked.

modo-auth

docs.rs

Session-based authentication and Argon2id password hashing for modo applications.

Features

Feature What it enables
templates UserContextLayer — injects the authenticated user into the minijinja template context

Key Types

Type Purpose
UserProvider Trait — implement on your user repository to load users by session ID
UserProviderService<U> Type-erased wrapper around a UserProvider; register with app.service()
Auth<U> Extractor — requires an authenticated user; returns 401 if absent
OptionalAuth<U> Extractor — resolves user if present, yields None if not authenticated
PasswordHasher Argon2id hashing service with hash_password / verify_password
PasswordConfig Argon2id tuning knobs (memory, iterations, parallelism)
UserContextLayer Tower layer (feature templates) — injects user into template context

Usage

1. Implement UserProvider

use modo_auth::{UserProvider, UserProviderService};
use serde::Serialize;

#[derive(Clone, Serialize)]
struct MyUser {
    id: String,
    name: String,
}

struct UserRepo {
    // db pool, etc.
}

impl UserProvider for UserRepo {
    type User = MyUser;

    async fn find_by_id(&self, id: &str) -> Result<Option<MyUser>, modo::Error> {
        // load from your database
        todo!()
    }
}

Note: serde::Serialize is required on the user type only when using UserContextLayer (feature templates).

2. Register services in main

use modo_auth::{UserProviderService, PasswordHasher};

#[modo::main]
async fn main(
    app: modo::app::AppBuilder,
    config: Config,
) -> Result<(), Box<dyn std::error::Error>> {
    let repo = UserRepo { /* ... */ };
    let hasher = PasswordHasher::default();

    app.service(UserProviderService::new(repo))
       .service(hasher)
       .run()
       .await
}

3. Use extractors in handlers

use modo_auth::{Auth, OptionalAuth};

// Requires authentication — returns 401 if no session / user not found
async fn profile(Auth(user): Auth<MyUser>) -> String {
    format!("Hello, {}", user.name)
}

// Optional — never rejects, yields None when not authenticated
async fn home(OptionalAuth(user): OptionalAuth<MyUser>) -> String {
    match user {
        Some(u) => format!("Welcome back, {}", u.name),
        None => "Welcome, guest".to_string(),
    }
}

4. Hash and verify passwords

use modo_auth::PasswordHasher;
use modo::Service;

// Extract the hasher in a handler
async fn register(
    Service(hasher): Service<PasswordHasher>,
) -> Result<(), modo::Error> {
    let hash = hasher.hash_password("correct-horse-battery-staple").await?;
    let valid = hasher.verify_password("correct-horse-battery-staple", &hash).await?;
    // store hash in DB...
    Ok(())
}

5. Custom Argon2id parameters

use modo_auth::{PasswordConfig, PasswordHasher};

fn build_hasher() -> Result<PasswordHasher, modo::Error> {
    let config = PasswordConfig {
        memory_cost_kib: 32768, // 32 MiB
        time_cost: 3,
        parallelism: 1,
    };
    PasswordHasher::new(config)
}

PasswordConfig implements serde::Deserialize with #[serde(default)], so you can load it from YAML with partial overrides:

password:
    memory_cost_kib: 32768
    # time_cost and parallelism fall back to defaults (2 and 1)

6. Inject user into template context (feature templates)

The user type must implement serde::Serialize for this layer.

use modo_auth::{UserContextLayer, UserProviderService};

#[modo::main]
async fn main(
    app: modo::app::AppBuilder,
    config: Config,
) -> Result<(), Box<dyn std::error::Error>> {
    let repo = UserRepo { /* ... */ };
    let user_svc = UserProviderService::new(repo);

    app.service(user_svc.clone())
       .layer(UserContextLayer::new(user_svc))
       .run()
       .await
}

The layer inserts the authenticated user as "user" into the minijinja TemplateContext, available in every template without explicit handler code. If no session exists or the user is not found, nothing is injected.

Error Behaviour

Condition Auth<U> OptionalAuth<U>
No session 401 None
Session present, user not found 401 None
Provider returns Err 500 500
Session middleware not registered 500 500
UserProviderService<U> not registered 500 500