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 is intended to demonstrate the overall authentication workflow. Some code has been omitted for brevity. Refer to the next chapters for complete implementations and production-ready examples.

This example shows:

  1. Create a user type
  2. Create a store
  3. Configure authentication
  4. Register a user
  5. Login
  6. Refresh tokens

User

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

    // Your custom fileds
    pub username : String,
    pub location: String,
    ...
    
}
}
#![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 service:

#![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()
}
}

Register:

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

Login:

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

Refresh:

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

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 7 days
  • 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 using the EmailProvider trait.

The framework is provider-agnostic and does not depend on a specific email service.


Supported Providers

Common providers you can integrate with:

  • SendGrid
  • Resend
  • Mailgun
  • AWS SES
  • SMTP
  • Custom providers

EmailProvider Trait

To send emails, implement the following trait:

#![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>;
}
}

Used By AuthBox

AuthBox sends emails for workflows such as:

  • Email verification
  • Password reset
  • One-time authentication links (magic links)

Example: Mock Email Sender

For development or testing, you can implement a simple mock provider:

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

#[derive(Clone)]
pub struct MockEmailSender;

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

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

        Ok(())
    }
}
}
  • This prints emails to the console instead of sending them.
  • Useful for local development and automated tests.

Production Email Providers

For production, implement the trait using real email services like:

  • SendGrid
  • Mailgun
  • AWS SES
  • SMTP servers

This allows AuthBox to send actual email messages without changing the core logic.


Summary

  • EmailProvider is a pluggable interface for sending emails.
  • You can implement a mock provider for testing or integrate a real provider for production.
  • AuthBox uses email for verification, password reset, and other authentication flows.

Configuring Email Templates

AuthBox separates email delivery from email content.

The EmailTemplateConfig trait is responsible for generating email subjects and bodies for different authentication workflows.


Responsibilities

Email templates are used for:

  • Email verification content
  • Password reset content

This allows full customization of email wording without changing the email provider logic.

EmailTemplateConfig Trait

To define email templates, implement the following trait:

#![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;
}
}

Methods

verify_email_subject

Generates the subject line for email verification.

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

verify_email_body

Generates the body content for email verification.

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

reset_password_subject

Generates the subject line for password reset emails.

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

reset_password_body

Generates the body content for password reset emails.

#![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<User> for MockTemplates {
    fn verify_email_subject(&self, _: &User) -> String {
        "Verify Email".to_string()
    }

    fn verify_email_body(
        &self,
        _: &User,
        token: &str,
    ) -> String {
        format!("https:://yourapp/verif_email/token={}", token)
    }

    fn reset_password_subject(
        &self,
        _: &User,
    ) -> String {
        "Reset Password".to_string()
    }

    fn reset_password_body(
        &self,
        _: &User,
        token: &str,
    ) -> String {
        format!("https:://yourapp/reset_password/token={}", token)
    }
}
}

Design Notes

  • EmailProvider handles sending emails
  • EmailTemplateConfig handles email content
  • This separation allows:
    • Different email services
    • Different branding/templates per app
    • Easy localization (i18n support)
    • Clean separation of concerns

Summary

  • Implement EmailTemplateConfig to customize email content.
  • Used for verification and password reset workflows.
  • Works together with EmailProvider but does not depend on it.
  • Fully flexible and backend-agnostic design.

Building the Auth Service

After configuring all the components, they can be assembled into an AuthService.

The AuthService is the main entry point into AuthBox and provides all authentication functionality.


Service Components

An AuthService requires the following pluggable components:

  • UserStore – persistence for user accounts
  • PasswordHasher – for hashing and verifying passwords
  • TokenManager – for access and refresh tokens
  • TokenBlacklistStore – for managing blacklisted refresh tokens
  • EmailProvider – for sending emails
  • EmailTemplateConfig – for generating email subjects and bodies
  • OneTimeTokenStore – for temporary tokens (email verification, password reset, magic links)

Building an AuthService

#![allow(unused)]
fn main() {
let auth = AuthService::builder()
    .store(UserStoreImpl::new())
    .hasher(DefaultHasher)
    .tokens(DefaultJwtManager::new("secret"))
    .blacklist(MemoryBlacklistStore::new())
    .email_sender(MockEmailSender)
    .email_templates(MockTemplates)
    .ott_store(MemoryOttStore::new())
    .build();
}
  • Each component is pluggable and can be replaced with a custom implementation.
  • This ensures the AuthBox service is flexible and backend-agnostic.

Available Operations

After constructing the service, it exposes methods for the complete authentication workflow:

#![allow(unused)]
fn main() {
auth.register(...);
auth.login(...);
auth.logout(...);
auth.refresh_token(...);
auth.verify_email(...);
auth.request_password_reset(...);
auth.reset_password(...);
auth.is_token_valid(...); // use in auth middleware to verify access tokens
}
  • These methods integrate all components automatically (user store, tokens, email, templates, etc.).

Example: Test AuthService

You can build a test instance using Redis, in-memory, and mock components:

#![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()
}
}
  • Ideal for unit testing or local development.
  • External dependencies are minimized; storage and email operations are handled by in-memory or mock components.

Summary

  • AuthService is the central interface for AuthBox.
  • All components are fully pluggable, allowing complete customization.
  • Supports registration, login, email verification, password resets, token rotation, and one-time authentication flows.
  • Testable with mock and in-memory implementations for fast local development.

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.