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.

Key Features

  • Data Sovereignty: Your user data stays in your own database
  • Multiple Authentication Methods: Support for passwords, OAuth, passkeys, and magic links
  • Flexible Storage: Store user data in SQLite, PostgreSQL, or MySQL
  • Extensible Plugin System: Add custom authentication methods or storage backends

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.1", features = ["password", "oauth", "passkey", "magic-link", "sqlite"] }
tokio = { version = "1", features = ["full"] }

The features you choose will depend on your authentication needs:

  • password: Email/password authentication
  • oauth: OAuth/social login support
  • passkey: WebAuthn/passkey authentication
  • magic-link: Email magic link authentication
  • Storage backend: sqlite, postgres, or one of the seaorm-* variants (e.g. seaorm-sqlite, seaorm-postgres or seaorm-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_storage_sqlite::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?);

    // Create a Torii instance with password authentication
    let torii = Torii::new(storage.clone(), storage.clone())
        .with_password_plugin();

    // Now torii is ready to use for password-based authentication
    Ok(())
}

Adding Multiple Authentication Methods

Torii's plugin system makes it easy to add multiple authentication methods:

use torii::Torii;
use torii_storage_sqlite::SqliteStorage;
use torii_auth_oauth::providers::Provider;
use std::sync::Arc;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let storage = Arc::new(SqliteStorage::connect("sqlite:auth.db").await?);

    let torii = Torii::new(storage.clone(), storage.clone())
        // 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(())
}

User Registration

To register a new user with password authentication:

#![allow(unused)]
fn main() {
async fn register_user(
    torii: &Torii<impl UserStorage + PasswordStorage>,
    email: &str,
    password: &str
) -> Result<(), Box<dyn std::error::Error>> {
    // 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() {
async fn login_user(
    torii: &Torii<impl UserStorage + PasswordStorage + SessionStorage>,
    email: &str,
    password: &str
) -> Result<(), Box<dyn std::error::Error>> {
    // Authenticate user
    let (user, session) = torii.login_user_with_password(email, password).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() {
async fn verify_session(
    torii: &Torii<impl UserStorage + SessionStorage>,
    session_token: &str
) -> Result<(), Box<dyn std::error::Error>> {
    // Verify and get session data
    let session = torii.get_session(session_token).await?;

    // Get the user associated with this session
    let user = torii.get_user(&session.user_id).await?;

    println!("Session verified for user: {}", user.id);
    Ok(())
}
}

OAuth Authentication Flow

For OAuth authentication, you'll need to implement these steps:

  1. Generate an authorization URL:
#![allow(unused)]
fn main() {
async fn start_oauth_flow(
    torii: &Torii<impl UserStorage + OAuthStorage>,
    provider: &str
) -> Result<String, Box<dyn std::error::Error>> {
    // 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)
}
}
  1. Handle the OAuth callback:
#![allow(unused)]
fn main() {
async fn handle_oauth_callback(
    torii: &Torii<impl UserStorage + OAuthStorage + SessionStorage>,
    provider: &str,
    code: &str,
    state: &str
) -> Result<(), Box<dyn std::error::Error>> {
    // Exchange the code for tokens and authenticate the user
    let (user, session) = torii.exchange_oauth_code(provider, code, state).await?;

    println!("OAuth user authenticated: {}", user.id);
    println!("Session token: {}", session.token);

    Ok(())
}
}

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 User and Session models
  • Explore each authentication method in depth
  • Configure a Storage Backend for production use

Check out the remaining documentation for more detailed information on each aspect of Torii.

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.

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:

FieldTypeDescription
idUserIdThe unique identifier for the user
nameOption<String>The user's name (optional)
emailStringThe user's email address
email_verified_atOption<DateTime<Utc>>Timestamp when the email was verified (if any)
created_atDateTime<Utc>Timestamp when the user was created
updated_atDateTime<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 string rather than a specific format (though it uses UUIDs internally by default)
  • Used to link user accounts to authentication methods, sessions, and application data

OAuth Accounts

For OAuth-based authentication, Torii maintains OAuthAccount records that link a user to their external provider identities:

FieldTypeDescription
user_idUserIdThe Torii user ID
providerStringThe OAuth provider (e.g., "google")
subjectStringThe unique ID from the provider
created_atDateTime<Utc>Timestamp when the link was created
updated_atDateTime<Utc>Timestamp when the link was last updated

This allows a single user to authenticate with multiple OAuth providers while maintaining a unified identity within your application.

Sessions

Sessions represent authenticated user sessions and are created when a user successfully logs in.

Session Structure

The Session struct contains the following fields:

FieldTypeDescription
tokenSessionTokenThe unique token identifying the session
user_idUserIdThe ID of the authenticated user
user_agentOption<String>The user agent of the client that created the session
ip_addressOption<String>The IP address of the client that created the session
created_atDateTime<Utc>Timestamp when the session was created
updated_atDateTime<Utc>Timestamp when the session was last updated
expires_atDateTime<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. HTTPS)
  • Has an expiration time after which it will no longer be valid
  • Can be revoked to force a user to log out

Session Management

Torii provides several methods for managing sessions:

  • Creating sessions: Generate new sessions when users log in
  • Verifying sessions: Validate session tokens on subsequent requests
  • Expiring sessions: Sessions automatically expire after their configured lifetime
  • Revoking sessions: Explicitly invalidate sessions to force logout
    • Cleaning up: Remove expired sessions to maintain database hygiene

Relationship Between Users and Sessions

In Torii:

  • One-to-many relationship: A user can have multiple active sessions (login from different devices)
  • Session verification: When a session token is verified, Torii returns both the session and associated user
  • Session cleanup: When a user is deleted, all associated sessions are automatically removed
  • User lookup: Sessions store the user ID for efficient user lookup during authentication Understanding these core concepts provides the foundation for working with Torii's authentication flows, which we'll explore in the subsequent sections.