Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

authbox

Crates.io Documentation Ko-fi

A lightweight, modular, async-first authentication framework for Rust.

authbox provides a flexible authentication system built around traits, pluggable components, and Tokio-ready async APIs.

It is designed for applications that need customizable authentication logic without being locked into a specific database, framework, or storage backend.


Features

  • Secure password hashing with Argon2
  • JWT access + refresh token authentication
  • Refresh token rotation
  • Refresh token revocation / blacklisting
  • Email verification flow
  • Password reset flow
  • One-time token (OTT) support
  • Fully async (tokio)
  • Trait-driven architecture
  • Pluggable storage backends
  • Framework agnostic
  • Test-friendly design
  • Builder API for ergonomic setup
  • Custom registration DTO support
  1. Unlike full authentication platforms, authbox does not require a specific:

    • Database
    • ORM
    • Web framework
  2. It works with:

    • Axum
    • Actix
    • Warp
    • Rocket
    • Custom applications

Support the Project

If AuthBox helps you build applications faster, consider supporting its development.

Your support helps fund:

  • New features
  • Documentation improvements
  • Bug fixes and maintenance
  • Long-term project sustainability

❤️ Ko-fi: https://ko-fi.com/nyando

Every contribution, no matter the size, is greatly appreciated.

Thank you for supporting open-source software!

Installation

Add the crate:

cargo add authbox

Import the prelude:

#![allow(unused)]
fn main() {
use authbox::prelude::*;
}

Runtime

Authbox is async-first and requires Tokio.

tokio = { version = "1", features = ["full"] }

Quick Start

Note

This quick start demonstrates the overall authentication workflow using the built-in SmtpEmailProvider. Some implementation details have been omitted for brevity. The following chapters cover complete implementations for the user store, email templates, and production-ready configuration.

This example walks through:

  1. Creating a user type
  2. Implementing AuthUser
  3. Building an AuthService
  4. Registering a user
  5. Logging in
  6. Refreshing an access token

User

#![allow(unused)]
fn main() {
#[derive(Clone, Debug)]
pub struct User {
    // Required fields
    pub id: String,
    pub email: String,
    pub password_hash: String,
    pub is_email_verified: bool,

    // Your application fields
    pub username: String,
    pub location: String,
}
}

Implement the AuthUser trait:

#![allow(unused)]
fn main() {
impl AuthUser for User {
    fn id(&self) -> String {
        self.id.clone()
    }

    fn email(&self) -> &str {
        &self.email
    }

    fn password_hash(&self) -> &str {
        &self.password_hash
    }

    fn is_email_verified(&self) -> bool {
        self.is_email_verified
    }

    fn set_email_verified(&mut self, verified: bool) {
        self.is_email_verified = verified;
    }

    fn set_password_hash(&mut self, hash: String) {
        self.password_hash = hash;
    }
}
}

Build the AuthService

#![allow(unused)]
fn main() {
pub type TestAuthService = AuthService<
    TestStore,
    DefaultHasher,
    DefaultJwtManager,
    RedisBlacklistStore,
    SmtpEmailProvider,
    MockTemplates,
    RedisOttStore,
>;

pub fn build_test_auth() -> TestAuthService {
    let client = redis::Client::open("redis://127.0.0.1/")
        .expect("failed to connect to redis");

    AuthService::builder()
        .store(TestStore::new())
        .hasher(DefaultHasher)
        .tokens(DefaultJwtManager::new("secret"))
        .blacklist(RedisBlacklistStore::new(client.clone()))
        .email_sender(smtp_email_provider())
        .email_templates(MockTemplates)
        .ott_store(RedisOttStore::new(client))
        .build()
}
}

The smtp_email_provider() function creates a SmtpEmailProvider using the following environment variables:

SMTP_HOST=smtp.gmail.com
SMTP_USERNAME=your-email@gmail.com
SMTP_PASSWORD=your-app-password
SMTP_FROM_EMAIL=AuthBox <your-email@gmail.com>

Register a User

#![allow(unused)]
fn main() {
let user = auth.register(dto).await?;
}

Registering a user will:

  • Create the account
  • Hash the password
  • Generate an email verification token
  • Send a verification email using the configured SmtpEmailProvider

Login

#![allow(unused)]
fn main() {
let tokens = auth
    .login(email, password)
    .await?;
}

On success, AuthBox returns an access token and a refresh token.


Refresh Tokens

#![allow(unused)]
fn main() {
let refreshed = auth
    .refresh_token(&tokens.refresh_token)
    .await?;
}

A new access token (and refresh token, if rotation is enabled) is generated while the previous refresh token is revoked.


Next Steps

The following chapters explain how to customize:

  • UserStore
  • EmailTemplateConfig
  • SmtpEmailProvider
  • JWT configuration
  • One-time token storage
  • Redis integration

authbox overview

authbox is a lightweight, modular, async-first authentication framework for Rust.

It provides a complete authentication workflow while remaining fully framework-agnostic and storage-agnostic. Every major component is trait-driven, allowing applications to integrate their own databases, token systems, email providers, and storage backends.

At the center of the library is AuthService, which orchestrates authentication operations such as registration, login, token refresh, email verification, password recovery and is token valid.


Core Philosophy

AuthBox follows three principles:

Modular

Every authentication component is replaceable through traits.

You can use:

  • PostgreSQL
  • MySQL
  • SQLite
  • MongoDB
  • Redis
  • In-memory stores
  • Custom backends

without changing authentication logic.

Async First

All operations are designed around Rust’s async ecosystem and work seamlessly with Tokio.

Framework Agnostic

AuthBox does not depend on:

  • Axum
  • Actix
  • Rocket
  • Warp

This allows integration into any Rust application or service architecture.


Building an AuthService

Authentication is configured using the builder API.

#![allow(unused)]
fn main() {
let auth = AuthService::builder()
    .store(user_store)
    .hasher(password_hasher)
    .tokens(token_manager)
    .blacklist(blacklist_store)
    .email_sender(email_provider)
    .email_templates(email_templates)
    .ott_store(one_time_token_store)
    .build();
}

The builder assembles all authentication dependencies into a single AuthService instance.


AuthService Components

An AuthService consists of seven pluggable components:

ComponentResponsibility
UserStoreUser persistence and retrieval
PasswordHasherPassword hashing and verification
TokenManagerAccess and refresh token generation
TokenBlacklistStoreRefresh token revocation
EmailProviderSending authentication emails
EmailTemplateConfigGenerating email content
OneTimeTokenStoreTemporary token storage
#![allow(unused)]
fn main() {
pub struct AuthService<S, P, T, B, E, M, V> {
    pub store: S,
    pub hasher: P,
    pub tokens: T,
    pub blacklist: B,
    pub email_sender: E,
    pub email_templates: M,
    pub ott_store: V,
}
}

Authentication Flow

A typical authentication lifecycle looks like:

Register
   │
   ▼
Email Verification
   │
   ▼
Login
   │
   ▼
Access Token + Refresh Token
   │
   ▼
Refresh Token Rotation
   │
   ▼
Token Revocation

Password recovery is available at any point through the password reset flow.


AuthService API

After creating an AuthService, the following authentication operations become available.

register()

Creates a new user account.

#![allow(unused)]
fn main() {
pub async fn register(
    &mut self,
    input: S::RegisterDto,
) ->  Result<S::User, AuthError<S::Error, T::Error, B::Error>>

}

What happens internally?

  1. Check email if exists
  2. Password is hashed.
  3. User is persisted through UserStore.
  4. A email verification token is generated.
  5. The token is stored in OneTimeTokenStore.
  6. A verification email is generated.
  7. The email is sent through EmailProvider.
  8. The created user is returned.

Flow

Register Request
        │
        ▼
Check if email already exists
        │--- email exists --> EmailAlreadyExists  Error
        ▼
Hash Password
        │
        ▼
Create User
        │
        ▼
Generate Verification Token
        │
        ▼
Store Token
        │
        ▼
Send Verification Email
        │
        ▼
Return User

login()

Authenticates a user and issues tokens.

#![allow(unused)]
fn main() {
pub async fn login(
    &self,
    email: &str,
    password: &str,
) -> Result<T::Token, LoginError<T::Error, S::Error>>
}

Validation Steps

  1. Find user by email.
  2. Verify email has been confirmed.
  3. Verify password hash.
  4. Generate authentication tokens.

Possible Errors

#![allow(unused)]
fn main() {
pub enum LoginError<T, S> {
    InvalidCredentials,
    EmailNotVerified,
    Store(S),
    Token(T),
}
}

Login Flow

Find User
    │
    ▼
Email Verified?
    │
    ├── No → EmailNotVerified
    │
    ▼
Verify Password
    │
    ├── Invalid → InvalidCredentials
    │
    ▼
Generate Tokens
    │
    ▼
Return Tokens

logout()

Invalidate your refresh token and log the user out.

#![allow(unused)]
fn main() {
 pub async fn logout(
        &self,
        refresh_token: &str,
    ) -> Result<(), AuthError<(), T::Error, B::Error, ()>>

}

What happens internally?

  1. Verify refresh token.
  2. Extract token claims.
  3. Blacklist current refresh token.

Refresh Flow

Verify Refresh Token
          │
          ▼
Check Blacklist
          │
          ├── Blacklisted
          │         │
          │         ▼
          │   BlacklistedToken
          │
          ▼
Blacklist Current Token
          │
          ▼
Generate New Tokens
          │
          ▼
Return New Token Pair

refresh_token()

Rotates a refresh token and issues a new token pair.

#![allow(unused)]
fn main() {
pub async fn refresh_token(
    &self,
    refresh_token: &str,
) -> Result<T::Token, AuthError<(), T::Error, B::Error>>
}

What happens internally?

  1. Verify refresh token.
  2. Extract token claims.
  3. Check blacklist.
  4. Blacklist current refresh token.
  5. Generate new token pair.

Possible Errors

#![allow(unused)]
fn main() {

pub enum AuthError<S, T, B> {
    Store(S),
    Token(T),
    Blacklist(B),

    BlacklistedToken,
    EmailAlreadyExists,

    NotFound,
    InvalidToken,
    EmailAlreadyVerified,
}


}

Refresh Flow

Verify Refresh Token
          │
          ▼
Check Blacklist
          │
          ├── Blacklisted
          │         │
          │         ▼
          │   BlacklistedToken
          │
          ▼
Blacklist Current Token
          │
          ▼
Generate New Tokens
          │
          ▼
Return New Token Pair

verify_email()

Marks a user’s email address as verified.

#![allow(unused)]
fn main() {
pub async fn verify_email(&self, token: &str) -> Result<(), AuthError<S::Error, (), ()>>
}

What happens internally?

  1. Lookup token in OneTimeTokenStore.
  2. Find associated user.
  3. Mark email as verified.
  4. Persist updated user.
  5. Remove verification token.

Flow

Verification Token
         │
         ▼
Find User
         │
         ▼
Set Email Verified
         │
         ▼
Update User
         │
         ▼
Delete Token

Possible Errors

#![allow(unused)]
fn main() {
AuthError::InvalidToken
AuthError::NotFound
AuthError::Store
AuthError::EmailAlreadyVerified
}

request_password_reset()

Starts the password reset process.

#![allow(unused)]
fn main() {
  pub async fn request_password_reset(
        &self,
        email: &str,
    ) -> Result<(), AuthError<S::Error, (), (), V::Error>>
}

What happens internally?

  1. Find user by email.
  2. Generate reset token.
  3. Store token in OneTimeTokenStore.
  4. Generate reset email.
  5. Send email.

Flow

Find User
    │
    ▼
Generate Reset Token
    │
    ▼
Store Token
    │
    ▼
Send Reset Email

reset_password()

Updates a user’s password using a reset token.

#![allow(unused)]
fn main() {
  pub async fn reset_password(
        &self,
        token: &str,
        new_password: &str,
    ) -> Result<(), AuthError<S::Error, (), ()>>
)
}

What happens internally?

  1. Validate reset token.
  2. Find associated user.
  3. Hash new password.
  4. Update stored password hash.
  5. Delete reset token.

Flow

Validate Reset Token
          │
          ▼
Find User
          │
          ▼
Hash New Password
          │
          ▼
Update User
          │
          ▼
Delete Token

Possible Errors

#![allow(unused)]
fn main() {
AuthError::InvalidToken
AuthError::NotFound
AuthError::Store
}

is_token_valid()

Checks if access token is valid

is_token_valid() should be used in your authentication middlewares to verify access token

#![allow(unused)]
fn main() {
   pub async fn is_token_valid(
        &self,
        token: &str,
    ) -> Result<T::Claims, AuthError<(), T::Error, B::Error>>
}

What happens internally?

  1. Validate access token.
  2. Check token if blaclisted
  3. Returns token claims

Flow

Validate Access Token
          │
          ▼
Check  token blacklisted
          │
          ▼
Returns token claims

Possible Errors

#![allow(unused)]
fn main() {
AuthError::Token
AuthError::Blacklist
AuthError::BlacklistedToken
}

Error Types

LoginError

Returned by login().

#![allow(unused)]
fn main() {
pub enum LoginError<T, S> {
    InvalidCredentials,
    EmailNotVerified,
    Store(S),
    Token(T),
}
}
VariantDescription
InvalidCredentialsEmail or password is incorrect
EmailNotVerifiedUser has not verified their email
Store(S)Storage layer failure
Token(T)Token generation failure

AuthError

Returned by refresh_token().

#![allow(unused)]
fn main() {
#[derive(Debug)]
pub enum AuthError<S, T, B, O> {
    Store(S),
    Token(T),
    Blacklist(B),
    Ott(O),

    BlacklistedToken,
    EmailAlreadyExists,

    NotFound,
    InvalidToken,
    EmailAlreadyVerified,
}
}
VariantDescription
Token(T)Token verification or refresh failure
Blacklist(B)Blacklist store failure
Ott(O)One Time Token store failure
BlacklistedTokenRefresh token has already been revoked
Store(S)Database store implementation erros
EmailAlreadyExistsEmail already exists
NotFoundUser not found
InvalidTokenOTT for blacklists has already been revoked
EmailAlreadyVerifiedUser email already verified

Security Features

AuthBox includes several security mechanisms by default:

  • Password hashing through PasswordHasher
  • Email verification before login
  • Refresh token rotation
  • Refresh token revocation
  • One-time token workflows
  • Password reset protection
  • Token blacklisting support
  • Stateless JWT authentication

Extensibility

Every authentication subsystem is trait-based.

You can provide custom implementations for:

  • User storage
  • Password hashing
  • Token generation
  • Email delivery
  • Email templates
  • Refresh token revocation
  • One-time token storage

This allows AuthBox to adapt to virtually any application architecture while keeping authentication logic centralized in AuthService.


Architecture Overview

                ┌─────────────────┐
                │   AuthService   │
                └────────┬────────┘
                         │
      ┌──────────────────┼──────────────────┐
      │                  │                  │
      ▼                  ▼                  ▼

 UserStore      PasswordHasher      TokenManager
      │
      ▼

OneTimeTokenStore

      │
      ▼

EmailProvider
      +
EmailTemplateConfig

      │
      ▼

TokenBlacklistStore

Next Steps

Continue with:

  1. Creating a User Model
  2. Implementing UserStore
  3. Configuring JWT Authentication
  4. Email Verification
  5. Password Reset Flow
  6. Refresh Token Rotation
  7. Production Deployment

Creating a Registration DTO

AuthBox does not force a specific registration payload.

Instead, applications provide their own registration DTO and implement the RegisterUserInput trait.

This allows your application to collect any additional information during registration while still providing the fields required by AuthBox.


Required Fields

AuthBox only requires:

  • Email
  • Password

These fields are accessed through the RegisterUserInput trait.

Example DTO

#![allow(unused)]
fn main() {
#[derive(Clone, Debug)]
pub struct SignUpUserPayload {
    // Required fileds
    pub email: String,
    pub password: String,
    
    // Your Custom fileds
    pub username: Option<String>,
    pub phone: Option<String>,
    pub country: Option<String>,
    ...
}
}

Implement RegisterUserInput

#![allow(unused)]
fn main() {
impl RegisterUserInput for SignUpUserPayload  {
    fn email(&self) -> &str {
        &self.email
    }

    fn password(&self) -> &str {
        &self.password
    }
}
}

How AuthBox Uses It

During registration:

#![allow(unused)]
fn main() {
auth.register(dto).await?;
}

AuthBox reads:

#![allow(unused)]
fn main() {
input.email()
input.password()
}

and passes the DTO to your UserStore.

This allows you to persist both authentication fields and application-specific fields.

Creating a User Type

AuthBox stores and authenticates users through the AuthUser trait.

Your user model can contain any fields required by your application.


Required Fields

AuthBox needs access to:

  • User ID
  • Email
  • Password Hash
  • Email Verification Status

Example User

#![allow(unused)]
fn main() {
#[derive(Clone, Debug)]
pub struct User {
    // Required fields
    pub id: String,
    pub email: String,
    pub password_hash: String,
    pub is_email_verified: bool,
    
    // Your Custom fields
    pub username: Option<String>,
    pub phone: Option<String>,
    ...
}
}

Implement AuthUser

Your User type must Implement the AuthUser trait

#![allow(unused)]
fn main() {
impl AuthUser for User {
    fn id(&self) -> String {
        self.id.clone()
    }

    fn email(&self) -> &str {
        &self.email
    }

    fn password_hash(&self) -> &str {
        &self.password_hash
    }

    fn is_email_verified(&self) -> bool {
        self.is_email_verified
    }

    fn set_email_verified(
        &mut self,
        verified: bool,
    ) {
        self.is_email_verified = verified;
    }

    fn set_password_hash(
        &mut self,
        hash: String,
    ) {
        self.password_hash = hash;
    }
}
}

Why Email Verification Matters

The login flow checks:

#![allow(unused)]
fn main() {
is_email_verified() -> bool
}

before generating tokens.

Unverified users cannot authenticate.

Implementing a User Store

The UserStore trait is responsible for user persistence.

AuthBox delegates all user-related database operations to this trait, allowing you to integrate any storage backend while keeping the authentication layer storage-agnostic.


Responsibilities

A user store implementation must support:

  • Finding users by ID
  • Finding users by email
  • Creating users
  • Updating users
  • Deleting users
  • Checking email verification status

Trait Overview

#![allow(unused)]
fn main() {
#[async_trait]
pub trait UserStore {
    type Error;
    type User: AuthUser;
    type RegisterDto: RegisterUserInput;

    async fn find_by_id(
        &self,
        user_id: &str,
    ) -> Option<Self::User>;

    async fn find_by_email(
        &self,
        email: &str,
    ) -> Option<Self::User>;

    async fn create_user(
        &self,
        input: Self::RegisterDto,
        password_hash: String,
    ) -> Result<Self::User, Self::Error>;

    async fn update_user(
        &self,
        user: Self::User,
    ) -> Result<Self::User, Self::Error>;

    async fn delete_user(
        &self,
        user_id: &str,
    ) -> Result<(), Self::Error>;

    async fn check_email_verified(
        &self,
        user_id: &str,
    ) -> Result<bool, Self::Error>;
}
}

Associated Types

User

The user model used by AuthBox.

This type must implement the AuthUser trait.

#![allow(unused)]
fn main() {
type User: AuthUser;
}

RegisterDto

The input type used when registering a new user.

This type must implement the RegisterUserInput trait.

#![allow(unused)]
fn main() {
type RegisterDto: RegisterUserInput;
}

Error

The error type returned by your storage backend.

#![allow(unused)]
fn main() {
type Error;
}

This can be:

  • sqlx::Error
  • mongodb::error::Error
  • diesel::result::Error
  • A custom application error type
  • Any other backend-specific error

Supported Backends

AuthBox does not require a specific database.

You can use any storage backend, including:

  • PostgreSQL
  • MySQL
  • SQLite
  • MongoDB
  • Redis
  • In-memory stores
  • REST APIs
  • gRPC services
  • Custom storage layers

Required Methods

find_by_id

Finds a user by their unique identifier.

#![allow(unused)]
fn main() {
async fn find_by_id(
    &self,
    user_id: &str,
) -> Option<Self::User>;
}

Returns:

  • Some(user) if found
  • None if the user does not exist

find_by_email

Finds a user by email address.

#![allow(unused)]
fn main() {
async fn find_by_email(
    &self,
    email: &str,
) -> Option<Self::User>;
}

Returns:

  • Some(user) if found
  • None if the user does not exist

create_user

Creates a new user record.

#![allow(unused)]
fn main() {
async fn create_user(
    &self,
    input: Self::RegisterDto,
    password_hash: String,
) -> Result<Self::User, Self::Error>;
}

Parameters:

  • input — Registration data
  • password_hash — Already-hashed password generated by AuthBox

Returns the newly created user.

update_user

Updates an existing user.

#![allow(unused)]
fn main() {
async fn update_user(
    &self,
    user: Self::User,
) -> Result<Self::User, Self::Error>;
}

Returns the updated user.

delete_user

Deletes a user from storage.

#![allow(unused)]
fn main() {
async fn delete_user(
    &self,
    user_id: &str,
) -> Result<(), Self::Error>;
}

Returns:

#![allow(unused)]
fn main() {
Ok(())
}

when the operation succeeds.

check_email_verified

Checks whether a user has verified their email address.

#![allow(unused)]
fn main() {
async fn check_email_verified(
    &self,
    user_id: &str,
) -> Result<bool, Self::Error>;
}

Returns:

  • Ok(true) if verified
  • Ok(false) if not verified
  • Err(_) if the backend operation fails

Example: In-Memory User Store

The following example demonstrates a simple in-memory implementation using a HashMap.

#![allow(unused)]
fn main() {
use async_trait::async_trait;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};

#[derive(Clone)]
pub struct UserStoreImpl {
    pub users: Arc<Mutex<HashMap<String, User>>>,
}

impl UserStoreImpl {
    pub fn new() -> Self {
        Self {
            users: Arc::new(Mutex::new(HashMap::new())),
        }
    }
}

#[async_trait]
impl UserStore for UserStoreImpl {
    type Error = String;
    type User = User;

    // Custom registration DTO
    type RegisterDto = RegisterDto;

    async fn find_by_id(
        &self,
        user_id: &str,
    ) -> Option<User> {
        let users = self.users.lock().unwrap();

        users.get(user_id).cloned()
    }

    async fn find_by_email(
        &self,
        email: &str,
    ) -> Option<User> {
        let users = self.users.lock().unwrap();

        users
            .values()
            .find(|u| u.email == email)
            .cloned()
    }

    async fn create_user(
        &self,
        input: RegisterDto,
        password_hash: String,
    ) -> Result<User, Self::Error> {
        let mut users = self.users.lock().unwrap();

        let user = User {
            id: input.email.clone(),
            email: input.email,
            password_hash,
            is_email_verified: false,

            username: input.username,
            phone: input.phone,
            country: input.country,
            city: input.city,
            age: input.age,
        };

        users.insert(user.id.clone(), user.clone());

        Ok(user)
    }

    async fn update_user(
        &self,
        user: User,
    ) -> Result<User, Self::Error> {
        let mut users = self.users.lock().unwrap();

        users.insert(user.id.clone(), user.clone());

        Ok(user)
    }

    async fn delete_user(
        &self,
        user_id: &str,
    ) -> Result<(), Self::Error> {
        let mut users = self.users.lock().unwrap();

        users.remove(user_id);

        Ok(())
    }

    async fn check_email_verified(
        &self,
        user_id: &str,
    ) -> Result<bool, Self::Error> {
        let users = self.users.lock().unwrap();

        Ok(
            users
                .get(user_id)
                .map(|u| u.is_email_verified)
                .unwrap_or(false)
        )
    }
}
}

Notes

  • AuthBox never directly interacts with your database.
  • All user persistence is handled through the UserStore trait.
  • You are free to use any ORM, query builder, or database driver.
  • The store implementation should be thread-safe if used in a concurrent environment.
  • Password hashing is performed by AuthBox before create_user is called.
  • update_user should persist any modifications made to a user record.
  • is_email_verified is used internally to determine whether a user has completed email verification.

By implementing UserStore, AuthBox can operate on top of virtually any persistence layer without requiring database-specific integrations.

Configuring Password Hashing

AuthBox handles password hashing through the PasswordHasher trait.

Passwords are never stored in plaintext. During registration, AuthBox hashes the user’s password before persisting it, and during login it verifies the provided password against the stored hash.


PasswordHasher Trait

To support different hashing algorithms, AuthBox defines the following trait:

#![allow(unused)]
fn main() {
pub trait PasswordHasher {
    fn hash(&self, password: &str) -> String;
    fn verify(&self, password: &str, hash: &str) -> bool;
}
}
  • hash generates a secure hash of the password.
  • verify checks a password against a stored hash.

Built-in Hasher

AuthBox ships with a default Argon2 implementation.

#![allow(unused)]
fn main() {
let hasher = DefaultHasher;
}
  • This is ready to use out of the box.
  • Handles salt generation, hashing, and verification.

Registration Flow

During user registration:

#![allow(unused)]
fn main() {
let hash = self.hasher.hash(input.password());
}

The resulting hash is stored in your UserStore instead of the plaintext password.

Login Flow

During login:

#![allow(unused)]
fn main() {
let valid = self.hasher.verify(password, user.password_hash());
}

Authentication only proceeds if valid is true.

Using a Custom Hasher

You can implement your own password hashing strategy by implementing the PasswordHasher trait:

#![allow(unused)]
fn main() {
pub struct MyHasher;

impl PasswordHasher for MyHasher {
    fn hash(&self, password: &str) -> String {
        // custom hashing logic
    }

    fn verify(&self, password: &str, hash: &str) -> bool {
        // custom verification logic
    }
}
}

You can then configure AuthBox to use your hasher instead of the default.


Summary

  • Default option: Use DefaultHasher (Argon2) — simple and secure.
  • Custom option: Implement your own PasswordHasher for Bcrypt, Scrypt, HSM-backed hashing, or any custom algorithm.

AuthBox works with any implementation of PasswordHasher, giving you flexibility without changing the core authentication logic.

Configuring JWT Tokens

AuthBox handles token generation, verification, and refresh through the TokenManager trait.

You can use the built-in JWT implementation DefaultJwtManager or provide your own custom token strategy.


TokenManager Trait

#![allow(unused)]
fn main() {
use async_trait::async_trait;

#[async_trait]
pub trait TokenManager {
    type Token;
    type Claims;
    type Error;

    async fn generate(&self, user_id: &str) -> Result<Self::Token, Self::Error>;
    async fn verify(&self, token: &str) -> Result<Self::Claims, Self::Error>;
    async fn refresh(&self, refresh_token: &str) -> Result<Self::Token, Self::Error>;
}
}

Responsibilities of a token manager:

  • Generate tokens
  • Verify tokens
  • Refresh tokens

Built-in JWT Manager

AuthBox provides a default JWT manager:

#![allow(unused)]
fn main() {
let tokens = DefaultJwtManager::new("super-secret-key");
}
  • Generates access and refresh tokens
  • Access tokens expire after 15 minutes
  • Refresh tokens expire after 1 day
  • Tokens use the standard Bearer type
  • Supports token blacklisting

Login Flow

After successful authentication:

#![allow(unused)]
fn main() {
let auth_tokens = token_manager.generate(&user.id()).await?;
}

Returned tokens:

#![allow(unused)]
fn main() {
pub struct AuthTokens {
    pub access_token: String,
    pub refresh_token: String,
    pub access_expires_at: i64,
    pub token_type: String,
}
}

Refresh Flow

Refreshing tokens:

#![allow(unused)]
fn main() {
token_manager.verify(refresh_token).await?;
token_manager.refresh(refresh_token).await?;
}
  • Verifies the refresh token
  • Returns a new set of access and refresh tokens

Claims Type

Each TokenManager defines a claims type:

#![allow(unused)]
fn main() {
type Claims;
}

The built-in JWT manager uses:

#![allow(unused)]
fn main() {
pub struct JwtClaims {
    pub sub: String,
    pub exp: i64,
    pub jti: String,
    pub token_type: TokenType,
}
}

Blacklist Support

To use AuthBox’s token blacklist, your claims type must implement BlacklistableClaims:

#![allow(unused)]
fn main() {
pub trait BlacklistableClaims {
    fn jti(&self) -> &str;
    fn exp(&self) -> i64;
}
}

The built-in claims already implement it:

#![allow(unused)]
fn main() {
impl BlacklistableClaims for JwtClaims {
    fn jti(&self) -> &str {
        &self.jti
    }

    fn exp(&self) -> i64 {
        self.exp
    }
}
}
  • jti() identifies the token uniquely
  • exp() determines when it can be removed from the blacklist

Using a Custom Token Manager

You can implement your own:

#![allow(unused)]
fn main() {
pub struct MyTokenManager;

#[async_trait]
impl TokenManager for MyTokenManager {
    type Token = AuthTokens;
    type Claims = MyClaims;
    type Error = MyError;

    async fn generate(&self, user_id: &str) -> Result<Self::Token, Self::Error> { todo!() }
    async fn verify(&self, token: &str) -> Result<Self::Claims, Self::Error> { todo!() }
    async fn refresh(&self, refresh_token: &str) -> Result<Self::Token, Self::Error> { todo!() }
}
}

If you want blacklist support, implement BlacklistableClaims on your claims type:

#![allow(unused)]
fn main() {
impl BlacklistableClaims for MyClaims {
    fn jti(&self) -> &str { &self.jti }
    fn exp(&self) -> i64 { self.exp }
}
}

Summary

  • Default option: DefaultJwtManager for standard JWT tokens.
  • Custom option: Implement TokenManager for any token strategy.
  • Blacklist support: Claims must implement BlacklistableClaims.
  • Fully flexible and backend-agnostic.

Configuring Token Blacklisting

Refresh token rotation requires a blacklist store.

The blacklist ensures that rotated refresh tokens cannot be reused, preventing token replay attacks.


How It Works

When a refresh token is exchanged:

  1. The current token is verified.
  2. The current token is blacklisted.
  3. A new token pair (access + refresh) is generated.

The original token is invalidated immediately and can never be used again.


Built-in RedisBlacklistStore

AuthBox ships with a built-in Redis implementation of TokenBlacklistStore.

#![allow(unused)]
fn main() {
let client = redis::Client::open("redis://127.0.0.1/")?;

let blacklist = RedisBlacklistStore::new(client);
}

Why Redis?

  • Production-ready
  • Automatically expires blacklisted tokens using Redis TTL
  • Works across multiple application instances
  • No manual cleanup required

The blacklist entry is stored until the refresh token’s expiration time, after which Redis automatically removes it.


TokenBlacklistStore Trait

#![allow(unused)]
fn main() {
use async_trait::async_trait;

#[async_trait]
pub trait TokenBlacklistStore {
    type Error;

    async fn is_blacklisted(
        &self,
        jti: &str,
    ) -> Result<bool, Self::Error>;

    async fn blacklist_token(
        &self,
        jti: &str,
        expires_at: i64,
    ) -> Result<bool, Self::Error>;
}
}

Methods

is_blacklisted(jti)

Checks whether a token identifier has already been blacklisted.

#![allow(unused)]
fn main() {
let blacklisted = store.is_blacklisted(jti).await?;
}

blacklist_token(jti, expires_at)

Adds a token identifier to the blacklist until its expiration time.

#![allow(unused)]
fn main() {
store
    .blacklist_token(jti, expires_at)
    .await?;
}

Default Redis Implementation

AuthBox includes the following implementation:

#![allow(unused)]
fn main() {
use async_trait::async_trait;
use authbox_core::traits::TokenBlacklistStore;
use redis::AsyncCommands;
use std::time::{SystemTime, UNIX_EPOCH};

#[derive(Clone)]
pub struct RedisBlacklistStore {
    pub client: redis::Client,
}

impl RedisBlacklistStore {
    pub fn new(client: redis::Client) -> Self {
        Self { client }
    }
}

#[derive(Debug)]
pub enum RedisBlacklistError {
    Redis(redis::RedisError),
}

#[async_trait]
impl TokenBlacklistStore for RedisBlacklistStore {
    type Error = RedisBlacklistError;

    async fn is_blacklisted(&self, jti: &str) -> Result<bool, Self::Error> {
        let mut conn = self
            .client
            .get_multiplexed_async_connection()
            .await
            .map_err(RedisBlacklistError::Redis)?;

        let exists: bool = conn
            .exists(jti)
            .await
            .map_err(RedisBlacklistError::Redis)?;

        Ok(exists)
    }

    async fn blacklist_token(
        &self,
        jti: &str,
        expires_at: i64,
    ) -> Result<bool, Self::Error> {
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs() as i64;

        if expires_at <= now {
            return Ok(true);
        }

        let ttl = (expires_at - now) as u64;

        let mut conn = self
            .client
            .get_multiplexed_async_connection()
            .await
            .map_err(RedisBlacklistError::Redis)?;

        let _: () = conn
            .set_ex(jti, "1", ttl)
            .await
            .map_err(RedisBlacklistError::Redis)?;

        Ok(true)
    }
}
}

Using RedisBlacklistStore

#![allow(unused)]
fn main() {
pub type TestAuthService = AuthService<
    TestStore,
    DefaultHasher,
    DefaultJwtManager,
    RedisBlacklistStore,
    MockEmailSender,
    MockTemplates,
    RedisOttStore,
>;

pub fn build_test_auth() -> TestAuthService {
    let client = redis::Client::open("redis://127.0.0.1/")
        .expect("failed to connect to redis");

    AuthService::builder()
        .store(TestStore::new())
        .hasher(DefaultHasher)
        .tokens(DefaultJwtManager::new("secret"))
        .blacklist(RedisBlacklistStore::new(client.clone()))
        .email_sender(MockEmailSender)
        .email_templates(MockTemplates)
        .ott_store(RedisOttStore::new(client))
        .build()
}
}

Custom Implementations

You can provide your own blacklist backend by implementing TokenBlacklistStore.

Common choices include:

  • Redis
  • PostgreSQL
  • MySQL
  • MongoDB
  • DynamoDB
  • In-memory HashSet (testing only)

Summary

  • Refresh token rotation requires token blacklisting.
  • AuthBox includes a built-in RedisBlacklistStore.
  • Blacklisted tokens are automatically removed when they expire.
  • Custom blacklist backends can be implemented through TokenBlacklistStore.
  • Redis is the recommended choice for production deployments.

Configuring One-Time Token Storage

AuthBox uses a one-time token store for temporary authentication tokens used in verification and recovery workflows.

These tokens are short-lived and automatically expire after a configured TTL.


Used By

One-time tokens are used for:

  • Email verification
  • Password reset
  • Magic links
  • One-time authentication flows

Built-in RedisOttStore

AuthBox ships with a built-in Redis implementation of OneTimeTokenStore.

#![allow(unused)]
fn main() {
let client = redis::Client::open("redis://127.0.0.1/")?;

let ott_store = RedisOttStore::new(client);
}

Why Redis?

  • Production-ready
  • Native TTL support
  • Automatic token expiration
  • Works across multiple application instances
  • No manual cleanup required

AuthBox stores one-time tokens in Redis with an expiration time, and Redis automatically removes them when the TTL expires.


OneTimeTokenStore Trait

#![allow(unused)]
fn main() {
use async_trait::async_trait;

#[async_trait]
pub trait OneTimeTokenStore {
    type Error;

    async fn set(
        &self,
        key: &str,
        value: &str,
        ttl_seconds: u64,
    ) -> Result<(), Self::Error>;

    async fn get(
        &self,
        key: &str,
    ) -> Result<Option<String>, Self::Error>;

    async fn delete(
        &self,
        key: &str,
    ) -> Result<(), Self::Error>;
}
}

Methods

set

Stores a token value with a time-to-live.

#![allow(unused)]
fn main() {
store
    .set(
        key,
        value,
        ttl_seconds,
    )
    .await?;
}

get

Retrieves a stored token.

#![allow(unused)]
fn main() {
let value = store.get(key).await?;
}

delete

Removes a token from storage.

#![allow(unused)]
fn main() {
store.delete(key).await?;
}

Token Lifetimes

AuthBox uses different expiration periods depending on the workflow.

Email Verification

#![allow(unused)]
fn main() {
60 * 60 * 24
}

24 hours.

Password Reset

#![allow(unused)]
fn main() {
60 * 15
}

15 minutes.

You may configure different expiration times depending on your application’s requirements.


Default Redis Implementation

AuthBox includes the following Redis-backed implementation:

#![allow(unused)]
fn main() {
use async_trait::async_trait;
use authbox_core::traits::OneTimeTokenStore;
use redis::AsyncCommands;

#[derive(Clone)]
pub struct RedisOttStore {
    pub client: redis::Client,
}

impl RedisOttStore {
    pub fn new(client: redis::Client) -> Self {
        Self { client }
    }
}

#[async_trait]
impl OneTimeTokenStore for RedisOttStore {
    type Error = redis::RedisError;

    async fn set(
        &self,
        key: &str,
        value: &str,
        ttl_seconds: u64,
    ) -> Result<(), Self::Error> {
        let mut conn = self
            .client
            .get_multiplexed_async_connection()
            .await?;

        conn.set_ex::<_, _, ()>(
            key,
            value,
            ttl_seconds,
        )
        .await?;

        Ok(())
    }

    async fn get(
        &self,
        key: &str,
    ) -> Result<Option<String>, Self::Error> {
        let mut conn = self
            .client
            .get_multiplexed_async_connection()
            .await?;

        let value: Option<String> =
            conn.get(key).await?;

        Ok(value)
    }

    async fn delete(
        &self,
        key: &str,
    ) -> Result<(), Self::Error> {
        let mut conn = self
            .client
            .get_multiplexed_async_connection()
            .await?;

        conn.del::<_, ()>(key).await?;

        Ok(())
    }
}
}

Using RedisOttStore

#![allow(unused)]
fn main() {
pub type TestAuthService = AuthService<
    TestStore,
    DefaultHasher,
    DefaultJwtManager,
    RedisBlacklistStore,
    MockEmailSender,
    MockTemplates,
    RedisOttStore,
>;

pub fn build_test_auth() -> TestAuthService {
    let client = redis::Client::open(
        "redis://127.0.0.1/"
    )
    .expect("failed to connect to redis");

    AuthService::builder()
        .store(TestStore::new())
        .hasher(DefaultHasher)
        .tokens(DefaultJwtManager::new("secret"))
        .blacklist(
            RedisBlacklistStore::new(
                client.clone(),
            ),
        )
        .email_sender(MockEmailSender)
        .email_templates(MockTemplates)
        .ott_store(
            RedisOttStore::new(client),
        )
        .build()
}
}

Custom Implementations

You can provide your own token storage backend by implementing OneTimeTokenStore.

Common choices include:

  • Redis
  • KeyDB
  • Memcached
  • PostgreSQL
  • MySQL
  • MongoDB
  • DynamoDB
  • In-memory HashMap (testing only)

Summary

  • AuthBox requires a OneTimeTokenStore for temporary authentication tokens.
  • AuthBox includes a built-in RedisOttStore.
  • Tokens automatically expire using Redis TTL support.
  • Used for email verification, password reset, magic links, and similar flows.
  • Custom storage backends can be implemented through OneTimeTokenStore.
  • Redis is the recommended choice for production deployments.

Configuring Email Delivery

AuthBox sends emails through the EmailProvider trait.

The system is provider-agnostic, meaning you can:

  • Use built-in providers for quick setup
  • Implement your own custom provider
  • Integrate with any email service

AuthBox does not depend on any specific email provider.


Quick Start (Built-in Providers)

AuthBox includes ready-to-use providers for common email services.

SendGrid

#![allow(unused)]
fn main() {
use authbox::prelude::SendGridEmailProvider;

let provider = SendGridEmailProvider::new(
    std::env::var("SENDGRID_API_KEY").unwrap(),
    std::env::var("SENDGRID_FROM_EMAIL").unwrap(),
    std::env::var("SENDGRID_FROM_NAME").unwrap(),
);
}

Resend

#![allow(unused)]
fn main() {
use authbox::prelude::ResendEmailProvider;

let provider = ResendEmailProvider::new(
    std::env::var("RESEND_API_KEY").unwrap(),
    std::env::var("RESEND_FROM_EMAIL").unwrap(),
);
}

SMTP

#![allow(unused)]
fn main() {
use authbox::prelude::SmtpEmailProvider;

let provider = SmtpEmailProvider::new(
    &std::env::var("SMTP_HOST").unwrap(),
    &std::env::var("SMTP_USERNAME").unwrap(),
    &std::env::var("SMTP_PASSWORD").unwrap(),
    &std::env::var("SMTP_FROM_EMAIL").unwrap(),
);
}

The EmailProvider Trait

All email providers implement the same interface:

#![allow(unused)]
fn main() {
use async_trait::async_trait;

#[async_trait]
pub trait EmailProvider {
    type Error;

    async fn send_email(
        &self,
        to: &str,
        subject: &str,
        body: &str,
    ) -> Result<(), Self::Error>;
}
}

This abstraction allows AuthBox to remain fully decoupled from any email service.


Creating a Custom Email Provider

You can integrate any email service by implementing the EmailProvider trait.

Example

#![allow(unused)]
fn main() {
use async_trait::async_trait;
use authbox::email::EmailProvider;

pub struct MyEmailProvider;

#[async_trait]
impl EmailProvider for MyEmailProvider {
    type Error = std::io::Error;

    async fn send_email(
        &self,
        to: &str,
        subject: &str,
        body: &str,
    ) -> Result<(), Self::Error> {
        println!("Sending email to {to}: {subject}");
        Ok(())
    }
}
}

Mock Provider (Testing & Development)

For local development and testing, you can use a mock provider that prints emails to the console.

#![allow(unused)]
fn main() {
use async_trait::async_trait;
use authbox::email::EmailProvider;

#[derive(Clone)]
pub struct MockEmailProvider;

#[async_trait]
impl EmailProvider for MockEmailProvider {
    type Error = ();

    async fn send_email(
        &self,
        to: &str,
        subject: &str,
        body: &str,
    ) -> Result<(), Self::Error> {
        println!(
            "EMAIL TO: {}\nSUBJECT: {}\nBODY: {}",
            to, subject, body
        );

        Ok(())
    }
}
}

Usage in AuthBox

Email providers are used throughout AuthBox for:

  • Email verification
  • Password reset emails
  • Magic login links
  • Account notifications

Summary

  • EmailProvider is a pluggable abstraction for email delivery.
  • Built-in providers are available for SendGrid, Resend, and SMTP.
  • You can integrate any email service by implementing the EmailProvider trait.
  • Mock providers make local development and testing straightforward.
  • AuthBox remains completely independent of any specific email service.

Configuring Email Templates

AuthBox separates email delivery from email content.

The EmailProvider is responsible for sending emails, while the EmailTemplateConfig trait generates the email subjects and bodies used throughout the authentication workflow.

This separation allows you to change your email branding and messaging without modifying how emails are delivered.


Responsibilities

EmailTemplateConfig is used to generate:

  • Email verification emails
  • Password reset emails

Each template receives the authenticated user and any required token, allowing personalized messages and application-specific URLs.


The EmailTemplateConfig Trait

Implement the following trait to customize email content:

#![allow(unused)]
fn main() {
pub trait EmailTemplateConfig<U: AuthUser> {
    fn verify_email_subject(&self, user: &U) -> String;

    fn verify_email_body(
        &self,
        user: &U,
        token: &str,
    ) -> String;

    fn reset_password_subject(&self, user: &U) -> String;

    fn reset_password_body(
        &self,
        user: &U,
        token: &str,
    ) -> String;
}
}

Trait Methods

verify_email_subject

Returns the subject line for email verification messages.

#![allow(unused)]
fn main() {
fn verify_email_subject(&self, user: &U) -> String;
}

verify_email_body

Returns the body of the email verification message.

#![allow(unused)]
fn main() {
fn verify_email_body(
    &self,
    user: &U,
    token: &str,
) -> String;
}

reset_password_subject

Returns the subject line for password reset emails.

#![allow(unused)]
fn main() {
fn reset_password_subject(&self, user: &U) -> String;
}

reset_password_body

Returns the body of the password reset email.

#![allow(unused)]
fn main() {
fn reset_password_body(
    &self,
    user: &U,
    token: &str,
) -> String;
}

Example Implementation

#![allow(unused)]
fn main() {
#[derive(Clone)]
pub struct MockTemplates;

impl EmailTemplateConfig<TestUser> for MockTemplates {
    fn verify_email_subject(&self, _: &TestUser) -> String {
        "Verify your email".to_string()
    }

    fn verify_email_body(
        &self,
        user: &TestUser,
        token: &str,
    ) -> String {
        format!(
            "Hello {},

Please verify your email by clicking the link below:

http://localhost:8080/verify-email/{}

If you did not create this account, you can ignore this email.",
            user.email,
            token
        )
    }

    fn reset_password_subject(&self, _: &TestUser) -> String {
        "Reset your password".to_string()
    }

    fn reset_password_body(
        &self,
        user: &TestUser,
        token: &str,
    ) -> String {
        format!(
            "Hello {},

You requested a password reset.

Reset your password using the link below:

http://localhost:8080/reset-password/{}

If you did not request this, ignore this email.",
            user.email,
            token
        )
    }
}
}

Personalizing Emails

Because each method receives the authenticated user, templates can include personalized information such as:

  • User name or email address
  • Verification or reset links
  • Company branding
  • Support contact information
  • Localized content
  • HTML or plain text email bodies

For production applications, replace the example URLs with your application’s frontend or API endpoints.


Design

AuthBox intentionally separates responsibilities:

ComponentResponsibility
EmailProviderDelivers emails using SMTP, SendGrid, Resend, or a custom provider
EmailTemplateConfigGenerates email subjects and message bodies

This design provides several benefits:

  • Clean separation of concerns
  • Easy branding customization
  • Localization (i18n) support
  • HTML or plain-text templates
  • Ability to switch email providers without changing templates

Summary

  • Implement EmailTemplateConfig to customize authentication emails.
  • Templates are used for email verification and password reset workflows.
  • The authenticated user and token are available when generating email content.
  • Email templates are completely independent from the configured EmailProvider.
  • Works with any supported email provider, including SMTP, SendGrid, Resend, or your own custom implementation.

Building the Auth Service

After configuring all required components, assemble them into an AuthService.

The AuthService is the main entry point into AuthBox and coordinates authentication, email delivery, token management, and user persistence.


Required Components

An AuthService is built from the following pluggable components:

  • UserStore — persistence layer for user accounts
  • PasswordHasher — hashes and verifies passwords
  • TokenManager — creates and validates access/refresh tokens
  • TokenBlacklistStore — stores revoked refresh tokens
  • EmailProvider — sends authentication emails
  • EmailTemplateConfig — generates email subjects and bodies
  • OneTimeTokenStore — stores temporary tokens for email verification, password resets, and magic links

Each component can be replaced with your own implementation.


Building an AuthService

The example below uses:

  • TestStore
  • DefaultHasher
  • DefaultJwtManager
  • RedisBlacklistStore
  • SmtpEmailProvider
  • MockTemplates
  • RedisOttStore
#![allow(unused)]
fn main() {
use authbox::prelude::*;

let client = redis::Client::open("redis://127.0.0.1/")
    .expect("failed to connect to redis");

let auth = AuthService::builder()
    .store(TestStore::new())
    .hasher(DefaultHasher)
    .tokens(DefaultJwtManager::new("secret"))
    .blacklist(RedisBlacklistStore::new(client.clone()))
    .email_sender(smtp_email_provider())
    .email_templates(MockTemplates)
    .ott_store(RedisOttStore::new(client))
    .build();
}

SMTP Configuration

The SMTP provider can be created from environment variables.

#![allow(unused)]
fn main() {
use std::env;
use authbox::prelude::SmtpEmailProvider;

pub fn smtp_email_provider() -> SmtpEmailProvider {
    let host = env::var("SMTP_HOST")
        .expect("SMTP_HOST missing");

    let username = env::var("SMTP_USERNAME")
        .expect("SMTP_USERNAME missing");

    let password = env::var("SMTP_PASSWORD")
        .expect("SMTP_PASSWORD missing");

    let from_email = env::var("SMTP_FROM_EMAIL")
        .expect("SMTP_FROM_EMAIL missing");

    SmtpEmailProvider::new(
        &host,
        &username,
        &password,
        &from_email,
    )
}
}

Example environment variables:

SMTP_HOST=smtp.gmail.com
SMTP_USERNAME=your-email@gmail.com
SMTP_PASSWORD=your-app-password
SMTP_FROM_EMAIL=AuthBox <your-email@gmail.com>

Note: For Gmail, use an App Password instead of your account password.


Available Operations

Once constructed, AuthService provides the complete authentication workflow.

#![allow(unused)]
fn main() {
auth.register(...).await?;
auth.login(...).await?;
auth.logout(...).await?;
auth.refresh_token(...).await?;
auth.verify_email(...).await?;
auth.request_password_reset(...).await?;
auth.reset_password(...).await?;
auth.is_token_valid(...).await?;
}

These methods automatically coordinate the configured storage, password hashing, token management, one-time tokens, email templates, and email provider.


Example Service Type

#![allow(unused)]
fn main() {
pub type TestAuthService = AuthService<
    TestStore,
    DefaultHasher,
    DefaultJwtManager,
    RedisBlacklistStore,
    SmtpEmailProvider,
    MockTemplates,
    RedisOttStore,
>;
}

Complete Builder Example

#![allow(unused)]
fn main() {
pub fn build_test_auth() -> TestAuthService {
    let client = redis::Client::open("redis://127.0.0.1/")
        .expect("failed to connect to redis");

    AuthService::builder()
        .store(TestStore::new())
        .hasher(DefaultHasher)
        .tokens(DefaultJwtManager::new("secret"))
        .blacklist(RedisBlacklistStore::new(client.clone()))
        .email_sender(smtp_email_provider())
        .email_templates(MockTemplates)
        .ott_store(RedisOttStore::new(client))
        .build()
}
}

Summary

  • AuthService is the central interface for AuthBox.
  • Every dependency is pluggable and can be replaced with your own implementation.
  • SMTP, SendGrid, and Resend providers can be used interchangeably through the EmailProvider abstraction.
  • Authentication workflows automatically integrate storage, hashing, JWTs, email delivery, templates, and one-time tokens.
  • The builder pattern makes it easy to switch implementations for development, testing, and production.

Architecture

AuthBox is designed as a modular, trait-driven authentication framework where every core responsibility is abstracted behind a pluggable interface.


Core Design Principles

  • Separation of concerns – authentication logic is separated from storage, email, and token handling
  • Dependency injection – everything is composed via AuthService::builder()
  • Backend agnostic – works with any database or external service
  • Testability – all components can be mocked or replaced
  • Zero hard dependencies – no required database, email provider, or token system

High-Level Flow

A typical authentication request flows through these layers:

  1. AuthService receives request
  2. Calls PasswordHasher (login/register)
  3. Uses UserStore for persistence
  4. Uses TokenManager for JWT generation
  5. Uses EmailProvider + EmailTemplateConfig for email flows
  6. Uses OneTimeTokenStore for temporary tokens
  7. Uses TokenBlacklistStore for refresh rotation & logout

Component Architecture

AuthBox is built from interchangeable components:

  • User layer → UserStore
  • Security layer → PasswordHasher
  • Session layer → TokenManager
  • Revocation layer → TokenBlacklistStore
  • Communication layer → EmailProvider
  • Template layer → EmailTemplateConfig
  • Temporary state layer → OneTimeTokenStore

Key Insight

AuthBox does not implement authentication directly.

Instead, it orchestrates a set of independent components to build a complete authentication system.

Custom User Types

AuthBox is fully generic over user models, allowing you to define your own domain-specific user structure.


AuthUser Trait

All user types must implement the AuthUser trait. This ensures AuthBox can:

  • Identify users
  • Perform authentication lookups
  • Manage passwords
  • Track email verification status
#![allow(unused)]
fn main() {
/// Full Auth user model contract
pub trait AuthUser {
    fn id(&self) -> String;
    fn email(&self) -> &str;
    fn password_hash(&self) -> &str;
    fn is_email_verified(&self) -> bool;
    fn set_email_verified(&mut self, verified: bool);
    fn set_password_hash(&mut self, hash: String);
}
}

Create Your User Type

Your user model must implement the AuthUser trait. You can also add custom fields as needed.

#![allow(unused)]
fn main() {
#[derive(Clone, Debug)]
pub struct YourUserModelType {
    // Required fields
    pub id: String,
    pub email: String,
    pub password_hash: String,
    pub is_email_verified: bool,

    // Custom fields
    pub username: Option<String>,
    pub phone: Option<String>,
    pub country: Option<String>,
    pub city: Option<String>,
    pub age: Option<u32>,
}

impl AuthUser for YourUserModelType {
    fn id(&self) -> String {
        self.id.clone()
    }

    fn email(&self) -> &str {
        &self.email
    }

    fn password_hash(&self) -> &str {
        &self.password_hash
    }

    fn is_email_verified(&self) -> bool {
        self.is_email_verified
    }

    fn set_email_verified(&mut self, verified: bool) {
        self.is_email_verified = verified;
    }

    fn set_password_hash(&mut self, hash: String) {
        self.password_hash = hash;
    }
}
}

Custom Registration DTO

You can define your own registration input structure to match your application’s needs.

RegisterUserInput Trait

AuthBox only requires two core fields for registration:

  • Email
  • Password

These are accessed through the RegisterUserInput trait.

#![allow(unused)]
fn main() {
pub trait RegisterUserInput {
    fn email(&self) -> &str;
    fn password(&self) -> &str;
}
}

Example DTO

You can extend the registration payload with any custom fields you need.

#![allow(unused)]
fn main() {
#[derive(Clone, Debug)]
pub struct SignUpUserPayload {
    // Required fields
    pub email: String,
    pub password: String,

    // Custom fields
    pub username: Option<String>,
    pub phone: Option<String>,
    pub country: Option<String>,
    pub city: Option<String>,
    pub age: Option<u32>,
}
}

Implement RegisterUserInput

Only the required fields are exposed to AuthBox.

#![allow(unused)]
fn main() {
impl RegisterUserInput for SignUpUserPayload {
    fn email(&self) -> &str {
        &self.email
    }

    fn password(&self) -> &str {
        &self.password
    }
}
}

Key Points

  • AuthBox only depends on email and password
  • You are free to add any extra fields for your domain
  • Extra fields are passed through to your UserStore implementation
  • Keeps authentication core simple and flexible

Key Benefits

By implementing your own user type, you fully control:

  • User structure
  • Registration fields
  • Domain-specific metadata

AuthBox only requires minimal traits to function, making it highly flexible.

Custom Storage Backends

AuthBox uses the UserStore trait to abstract all persistence logic.

This allows integration with any database or storage system.


Supported Backends

You can implement storage using:

  • PostgreSQL
  • MySQL
  • SQLite
  • MongoDB
  • Redis
  • REST APIs
  • gRPC services
  • In-memory storage

UserStore Trait

#![allow(unused)]
fn main() {
#[async_trait]
pub trait UserStore {
    type Error;
    type User: AuthUser;
    type RegisterDto: RegisterUserInput;

    async fn find_by_id(&self, user_id: &str) -> Option<Self::User>;

    async fn find_by_email(&self, email: &str) -> Option<Self::User>;

    async fn create_user(
        &self,
        input: Self::RegisterDto,
        password_hash: String,
    ) -> Result<Self::User, Self::Error>;

    async fn update_user(
        &self,
        user: Self::User,
    ) -> Result<Self::User, Self::Error>;

    async fn delete_user(
        &self,
        user_id: &str,
    ) -> Result<(), Self::Error>;

    async fn  check_email_verified(
        &self,
        user_id: &str,
    ) -> Result<bool, Self::Error>;
}
}

Implementation Guidelines

When implementing a custom store:

  • Ensure thread safety (Arc, connection pools, etc.)
  • Avoid blocking operations inside async methods
  • Handle errors using your backend’s error type
  • Keep queries efficient and indexed
  • Ensure email lookup is fast (commonly indexed)

Example Use Cases

  • SQL-backed authentication system
  • Multi-tenant user storage
  • Microservice-based auth backend
  • High-performance cache layer (Redis)

Key Benefit

AuthBox does not dictate your database.

You control persistence entirely.

Production Deployment

This guide covers best practices for deploying AuthBox in production environments.


For production systems, use:

  • PostgreSQL (UserStore)
  • Redis (TokenBlacklistStore + OneTimeTokenStore)
  • Argon2 (PasswordHasher)
  • JWT with strong secret rotation (TokenManager)
  • Real email provider (SendGrid / SES / Mailgun)

Security Configuration

Password Hashing

Use strong Argon2 settings:

  • High memory cost
  • Adequate iteration count
  • Unique salt per password (handled automatically)

JWT Tokens

  • Use a long, random secret key
  • Rotate secrets periodically
  • Keep access tokens short-lived (15 min recommended)
  • Use refresh token rotation + blacklist

Token Blacklisting

Always enable blacklist storage in production:

  • Prevents reuse of refresh tokens
  • Protects against token theft
  • Works best with Redis or distributed cache

One-Time Tokens

Use TTL-based storage (Redis recommended):

  • Email verification: 24 hours
  • Password reset: 10–15 minutes

Email Delivery

Use a real provider:

  • AWS SES (recommended for scale)
  • SendGrid
  • Mailgun
  • SMTP (small apps)

Ensure:

  • SPF/DKIM configured
  • Domain verified
  • Rate limits handled

Scaling Considerations

AuthBox is stateless by design:

  • Multiple instances can run behind a load balancer
  • Shared Redis required for:
    • blacklists
    • one-time tokens

Deployment Checklist

  • Secure JWT secret
  • Enable refresh token rotation
  • Use Redis for shared state
  • Configure real email provider
  • Use Argon2 hashing
  • Enable HTTPS only
  • Rate limit auth endpoints

Summary

AuthBox is production-ready when combined with:

  • Strong cryptography settings
  • Shared state storage (Redis)
  • Real email delivery system
  • Proper secret management

It is designed to scale horizontally without changing core logic.

Roadmap

The AuthBox roadmap outlines features and improvements planned for future releases:

  • Async architecture
  • JWT authentication
  • Refresh token rotation
  • Refresh token revocation
  • Email verification
  • Password reset flow
  • One-time token support
  • Pluggable storage backends
  • Custom registration DTO support
  • Middleware usage ready

License

AuthBox is licensed under Apache-2.0.