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.