Introduction to Torii
Torii is a powerful authentication framework for Rust applications that gives you complete control over your users' data. Unlike hosted solutions like Auth0, Clerk, or WorkOS that store user information in their cloud, Torii lets you own and manage your authentication stack while providing modern auth features through a flexible plugin system.
With Torii, you get the best of both worlds - powerful authentication capabilities combined with full data sovereignty and the ability to store user data wherever you choose.
Warning: This project is in early development and is not production-ready. The API is subject to change without notice. As this project has not undergone security audits, it should not be used in production environments.
Key Features
- Data Sovereignty: Your user data stays in your own database
- Multiple Authentication Methods:
- Password-based authentication
- Social OAuth/OpenID Connect
- Passkey/WebAuthn support
- Magic Link authentication
- Flexible Storage: Store user data in SQLite, PostgreSQL, or MySQL (using SeaORM)
- JWT Support: Optional stateless JWT sessions
- Extensible Plugin System: Add custom authentication methods or storage backends
Storage Support
Plugin | SQLite | PostgreSQL | MySQL (using SeaORM) |
---|---|---|---|
Password | ✅ | ✅ | ✅ |
OAuth2/OIDC | ✅ | ✅ | ✅ |
Passkey | ✅ | ✅ | ✅ |
Magic Link | ✅ | ✅ | ✅ |
Getting Started with Torii
This guide will walk you through the process of integrating Torii into your Rust application. By the end, you'll have a fully functional authentication system supporting multiple authentication methods.
Prerequisites
Before you begin, make sure you have:
- A Rust project set up with Cargo
- Basic understanding of async Rust (Torii uses
async
/await
) - Database setup for your preferred storage backend (SQLite, PostgreSQL, or MySQL)
Installation
Add Torii to your Cargo.toml
file:
[dependencies]
torii = { version = "0.2", features = ["password", "sqlite"] }
tokio = { version = "1", features = ["full"] }
The features you choose will depend on your authentication needs:
- Authentication methods:
password
: Email/password authenticationoauth
: OAuth/social login supportpasskey
: WebAuthn/passkey authenticationmagic-link
: Email magic link authentication
- Storage backends:
sqlite
: SQLite storagepostgres
: PostgreSQL storageseaorm
: SeaORM support with additional options (seaorm-sqlite
,seaorm-postgres
, orseaorm-mysql
)
Basic Setup
Here's a minimal example to set up Torii with a SQLite database and password authentication:
use std::sync::Arc; use torii::Torii; use torii::SqliteStorage; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { // Initialize the storage backend let storage = Arc::new(SqliteStorage::connect("sqlite://auth.db?mode=rwc").await?); // Migrate the storage schema storage.migrate().await?; // Create a Torii instance with password authentication let torii = Torii::new(storage) .with_password_plugin(); // Now torii is ready to use for password-based authentication Ok(()) }
Different Initialization Options
Torii provides several ways to initialize the authentication system:
use std::sync::Arc; use torii::Torii; use torii::SqliteStorage; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let storage = Arc::new(SqliteStorage::connect("sqlite://auth.db?mode=rwc").await?); storage.migrate().await?; // 1. Simplest approach with a single storage backend let torii = Torii::new(storage.clone()) .with_password_plugin(); // 2. If you want separate storage for sessions (e.g., Redis): // let user_storage = storage.clone(); // let session_storage = Arc::new(RedisStorage::connect("redis://localhost").await?); // let torii = Torii::with_storages(user_storage, session_storage) // .with_password_plugin(); // 3. If you need custom managers for additional behavior: // use torii_core::{DefaultUserManager, DefaultSessionManager, UserManager, SessionManager}; // let user_manager: Arc<dyn UserManager + Send + Sync> = Arc::new(CustomUserManager::new(storage.clone())); // let session_manager: Arc<dyn SessionManager + Send + Sync> = Arc::new(DefaultSessionManager::new(storage.clone())); // let torii = Torii::with_managers(storage.clone(), storage.clone(), user_manager, session_manager); // 4. If your managers fully encapsulate their storage and you don't need plugins: // let user_manager: Arc<dyn UserManager + Send + Sync> = Arc::new(MyUserManager::new(my_db_conn.clone())); // let session_manager: Arc<dyn SessionManager + Send + Sync> = Arc::new(RedisSessionManager::new("redis://localhost")); // let torii = Torii::<()>::with_custom_managers(user_manager, session_manager); Ok(()) }
Adding Multiple Authentication Methods
Torii's plugin system makes it easy to add multiple authentication methods:
use torii::{Torii, Provider, SeaORMStorage}; use std::sync::Arc; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let storage = Arc::new(SeaORMStorage::connect("sqlite://auth.db?mode=rwc").await?); storage.migrate().await?; let torii = Torii::new(storage) // Password authentication .with_password_plugin() // OAuth providers .with_oauth_provider(Provider::google( "YOUR_GOOGLE_CLIENT_ID", "YOUR_GOOGLE_CLIENT_SECRET", "https://your-app.com/auth/callback/google" )) .with_oauth_provider(Provider::github( "YOUR_GITHUB_CLIENT_ID", "YOUR_GITHUB_CLIENT_SECRET", "https://your-app.com/auth/callback/github" )) // Passkey/WebAuthn .with_passkey_plugin( "your-app.com", // Relying Party ID "https://your-app.com" // Relying Party Origin ) // Magic Link authentication .with_magic_link_plugin(); // Now torii is ready to use with multiple authentication methods Ok(()) }
Optional JWT Sessions
Torii supports JWT-based sessions for stateless authentication:
use torii::{Torii, SqliteStorage, JwtConfig}; use chrono::Duration; use std::sync::Arc; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let storage = Arc::new(SqliteStorage::connect("sqlite://auth.db?mode=rwc").await?); storage.migrate().await?; // Configure with JWT sessions using HS256 signing let jwt_config = JwtConfig::hs256("your-secret-key"); let torii = Torii::new(storage) .with_password_plugin() .with_jwt_sessions(jwt_config); // Alternatively, you can configure session expiration // let torii = Torii::new(storage) // .with_password_plugin() // .with_session_config(SessionConfig::default().expires_in(Duration::days(7))); Ok(()) }
User Registration
To register a new user with password authentication:
#![allow(unused)] fn main() { use torii::{Torii, ToriiError}; async fn register_user( torii: &Torii<impl torii_core::storage::UserStorage + torii_core::storage::PasswordStorage>, email: &str, password: &str ) -> Result<(), ToriiError> { // Register a new user let user = torii.register_user_with_password(email, password).await?; println!("User registered: {}", user.id); Ok(()) } }
User Login
To authenticate a user with password:
#![allow(unused)] fn main() { use torii::{Torii, ToriiError}; async fn login_user( torii: &Torii<impl torii_core::storage::UserStorage + torii_core::storage::PasswordStorage>, email: &str, password: &str ) -> Result<(), ToriiError> { // Authenticate user - optional user_agent and ip_address for tracking let (user, session) = torii.login_user_with_password( email, password, Some("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36".to_string()), Some("127.0.0.1".to_string()) ).await?; // The session token can be stored in a cookie or returned to the client println!("User authenticated: {}", user.id); println!("Session token: {}", session.token); Ok(()) } }
Session Verification
To verify a user's session token:
#![allow(unused)] fn main() { use torii::{Torii, SessionToken, ToriiError}; async fn verify_session( torii: &Torii<impl torii_core::storage::UserStorage>, session_token: &str ) -> Result<(), ToriiError> { // Parse the session token let token = SessionToken::new(session_token.to_string()); // Verify and get session data let session = torii.get_session(&token).await?; // Get the user associated with this session let user = torii.get_user(&session.user_id).await? .ok_or_else(|| ToriiError::AuthError("User not found".to_string()))?; println!("Session verified for user: {}", user.id); Ok(()) } }
OAuth Authentication Flow
For OAuth authentication, you'll need to implement these steps:
- Generate an authorization URL:
#![allow(unused)] fn main() { use torii::{Torii, ToriiError}; async fn start_oauth_flow( torii: &Torii<impl torii_core::storage::UserStorage + torii_core::storage::OAuthStorage>, provider: &str ) -> Result<String, ToriiError> { // Get the authorization URL for the provider let auth_url = torii.get_oauth_authorization_url(provider).await?; // Store the CSRF state in your session/cookies let csrf_state = auth_url.csrf_state; // Return the URL to redirect the user to Ok(auth_url.url) } }
- Handle the OAuth callback:
#![allow(unused)] fn main() { use torii::{Torii, ToriiError}; async fn handle_oauth_callback( torii: &Torii<impl torii_core::storage::UserStorage + torii_core::storage::OAuthStorage>, provider: &str, code: &str, state: &str ) -> Result<(), ToriiError> { // Exchange the code for tokens and authenticate the user let (user, session) = torii.exchange_oauth_code( provider, code, state, Some("Browser User Agent".to_string()), Some("127.0.0.1".to_string()) ).await?; println!("OAuth user authenticated: {}", user.id); println!("Session token: {}", session.token); Ok(()) } }
Passkey Authentication
Passkey (WebAuthn) authentication is performed in two steps:
- Start registration:
#![allow(unused)] fn main() { use torii::{Torii, ToriiError}; async fn start_passkey_registration( torii: &Torii<impl torii_core::storage::UserStorage + torii_core::storage::PasskeyStorage>, email: &str ) -> Result<(), ToriiError> { // Begin the passkey registration let options = torii.begin_passkey_registration(email).await?; // Return the challenge ID and WebAuthn options to the client for processing println!("Challenge ID: {}", options.challenge_id); println!("WebAuthn Options: {}", serde_json::to_string(&options.options).unwrap()); Ok(()) } }
- Complete registration:
#![allow(unused)] fn main() { use torii::{Torii, ChallengeId, PasskeyRegistrationCompletion, ToriiError}; async fn complete_passkey_registration( torii: &Torii<impl torii_core::storage::UserStorage + torii_core::storage::PasskeyStorage>, email: &str, challenge_id: &str, response: serde_json::Value ) -> Result<(), ToriiError> { // Complete the passkey registration let completion = PasskeyRegistrationCompletion { email: email.to_string(), challenge_id: ChallengeId::new(challenge_id.to_string()), response, }; let user = torii.complete_passkey_registration(&completion).await?; println!("User registered with passkey: {}", user.id); Ok(()) } }
Magic Link Authentication
Magic link authentication is useful for passwordless email-based login:
#![allow(unused)] fn main() { use torii::{Torii, ToriiError}; async fn send_magic_link( torii: &Torii<impl torii_core::storage::UserStorage + torii_core::storage::MagicLinkStorage>, email: &str ) -> Result<(), ToriiError> { // Generate a magic token let token = torii.generate_magic_token(email).await?; // Create a magic link URL (this would typically be sent via email) let magic_link = format!("https://your-app.com/auth/magic-link?token={}", token.token); println!("Magic Link: {}", magic_link); Ok(()) } async fn verify_magic_link( torii: &Torii<impl torii_core::storage::UserStorage + torii_core::storage::MagicLinkStorage>, token: &str ) -> Result<(), ToriiError> { // Verify the magic token and create a session let (user, session) = torii.verify_magic_token( token, Some("Browser User Agent".to_string()), Some("127.0.0.1".to_string()) ).await?; println!("User authenticated via magic link: {}", user.id); println!("Session token: {}", session.token); Ok(()) } }
Example Application
You can find a complete example of a Todo application using Torii with Axum in the examples/todos directory. This example demonstrates:
- Setting up Torii with SQLite
- Adding password authentication
- Creating a web server with Axum
- Implementing user registration and login
- Managing authenticated sessions with cookies
Next Steps
Now that you have a basic understanding of how to use Torii, you can:
- Integrate Torii with your web framework (Axum, Actix, Rocket, etc.)
- Learn about the core concepts of Users and Sessions
- Explore each authentication method in more depth
- Configure a storage backend for production use
Remember that Torii is designed to give you flexibility while maintaining control over your user data.
Core Concepts
Torii is built around several core concepts that form the foundation of the authentication system. Understanding these concepts is essential for effectively implementing and extending Torii in your applications.
Main Components
The Torii framework consists of several key components:
- The Torii Coordinator: The main
Torii
struct that coordinates all authentication activities - Storage Backends: Implementations for persisting user and session data
- Authentication Plugins: Modules for different authentication methods
- User and Session Management: APIs for creating and verifying sessions
Users
Users are the central entity in the Torii authentication system. Each user represents an individual who can authenticate with your application.
User Structure
The core User
struct contains the following fields:
Field | Type | Description |
---|---|---|
id | UserId | The unique identifier for the user |
name | Option<String> | The user's name (optional) |
String | The user's email address | |
email_verified_at | Option<DateTime<Utc>> | Timestamp when the email was verified (if any) |
created_at | DateTime<Utc> | Timestamp when the user was created |
updated_at | DateTime<Utc> | Timestamp when the user was last updated |
User IDs
Each user has a unique UserId
that identifies them in the system. This ID is:
- Stable and will not change during the user's lifetime
- Treated as an opaque identifier rather than a specific format (though it uses UUIDs internally by default)
- Used to link user accounts to authentication methods, sessions, and application data
Sessions
Sessions represent authenticated user sessions and are created when a user successfully logs in.
Session Structure
The Session
struct contains the following fields:
Field | Type | Description |
---|---|---|
token | SessionToken | The unique token identifying the session |
user_id | UserId | The ID of the authenticated user |
user_agent | Option<String> | The user agent of the client that created the session |
ip_address | Option<String> | The IP address of the client that created the session |
created_at | DateTime<Utc> | Timestamp when the session was created |
expires_at | DateTime<Utc> | Timestamp when the session will expire |
Session Tokens
Each session is identified by a unique SessionToken
that:
- Functions as a bearer token or cookie for authentication
- Should be kept secret and transmitted securely (e.g., via HTTPS)
- Has an expiration time after which it will no longer be valid
- Can be revoked to force a user to log out
Session Types
Torii supports two types of sessions:
- Database Sessions (default): Sessions are stored in your database and can be individually revoked
- JWT Sessions (optional): Stateless sessions using JWT tokens that don't require database lookups but cannot be individually revoked
Authentication Methods
Torii provides several authentication methods through its plugin system:
Password Authentication
Traditional email/password authentication with secure password hashing.
Key features:
- Argon2id password hashing
- Email verification capabilities
- Password reset functionality
OAuth Authentication
Social login and OpenID Connect support for external identity providers.
Supported providers:
- GitHub
- More providers can be added
Passkey Authentication (WebAuthn)
Passwordless authentication using the Web Authentication API (WebAuthn).
Key features:
- FIDO2-compliant
- Supports hardware security keys, platform authenticators (Windows Hello, Touch ID, etc.)
- Challenge-response authentication flow
Magic Link Authentication
Email-based passwordless authentication using one-time tokens.
Key features:
- Generates secure tokens
- Time-limited validation
- Simple user experience
Storage System
Torii abstracts the storage layer through traits, allowing different storage backend implementations:
Available Storage Backends
- SQLite: For development, testing, or small applications
- PostgreSQL: For production-ready applications requiring a robust database
- SeaORM: Supporting SQLite, PostgreSQL, and MySQL through the SeaORM ORM
Each storage backend implements the following core storage traits:
UserStorage
: For user managementSessionStorage
: For session managementPasswordStorage
: For password authenticationOAuthStorage
: For OAuth accountsPasskeyStorage
: For WebAuthn credentialsMagicLinkStorage
: For magic link tokens
Initialization Patterns
Torii provides several ways to initialize the system based on your application's needs:
-
Single Storage: Use the same storage for users and sessions
#![allow(unused)] fn main() { Torii::new(storage) }
-
Split Storage: Use different storage backends for users and sessions
#![allow(unused)] fn main() { Torii::with_storages(user_storage, session_storage) }
-
Custom Managers: Provide custom user and session managers
#![allow(unused)] fn main() { Torii::with_managers(user_storage, session_storage, user_manager, session_manager) }
-
Stateless Managers: Use custom managers without storage
#![allow(unused)] fn main() { Torii::with_custom_managers(user_manager, session_manager) }
Error Handling
Torii uses a structured error system with the ToriiError
enum that includes:
PluginNotFound
: When an authentication plugin is not availableAuthError
: When authentication failsStorageError
: When there's an issue with the storage backend
Understanding these core concepts provides the foundation for working with Torii's authentication flows in your applications.