Better Auth in Rust

SeaORM Hooks

Intercept SeaORM-backed auth operations with lifecycle hooks.

SeaORM hooks let you run custom logic before and after auth writes. Use them for validation, auditing, provisioning, or rejecting operations while keeping the public auth runtime separate from your persistence integration.

Setup

These examples assume you've already defined AppAuthSchema and a database: DatabaseConnection. If you want a complete setup from entity definitions through Axum mounting, start with the Axum integration guide.

use std::sync::Arc;
use async_trait::async_trait;
use better_auth::AuthSchema;
use better_auth::seaorm::{HookControl, SeaOrmHookContext, SeaOrmHooks, SeaOrmStore};

struct AuditHook;

#[async_trait]
impl SeaOrmHooks<AppAuthSchema> for AuditHook {
    async fn after_create_user(
        &self,
        user: &<AppAuthSchema as AuthSchema>::User,
        _ctx: &SeaOrmHookContext<'_>,
    ) -> better_auth::AuthResult<()> {
        println!("User created: {:?}", user.email());
        Ok(())
    }
}

let store = SeaOrmStore::<AppAuthSchema>::new(config.clone(), database)
    .hook(AuditHook);

let auth = BetterAuth::<AppAuthSchema>::new(config)
    .store(store)
    .build()
    .await?;

The SeaOrmHooks Trait

All methods have default no-op implementations. Override only what you need.

#[async_trait]
pub trait SeaOrmHooks<S: AuthSchema>: Send + Sync {
    async fn before_create_user(
        &self,
        user: &mut CreateUser,
        ctx: &SeaOrmHookContext<'_>,
    ) -> AuthResult<HookControl>;

    async fn after_create_user(
        &self,
        user: &S::User,
        ctx: &SeaOrmHookContext<'_>,
    ) -> AuthResult<()>;

    async fn before_update_user(
        &self,
        id: &str,
        update: &mut UpdateUser,
        ctx: &SeaOrmHookContext<'_>,
    ) -> AuthResult<HookControl>;

    async fn after_update_user(
        &self,
        user: &S::User,
        ctx: &SeaOrmHookContext<'_>,
    ) -> AuthResult<()>;

    async fn before_delete_user(
        &self,
        user: &S::User,
        ctx: &SeaOrmHookContext<'_>,
    ) -> AuthResult<HookControl>;

    async fn after_delete_user(
        &self,
        user: &S::User,
        ctx: &SeaOrmHookContext<'_>,
    ) -> AuthResult<()>;

    async fn before_create_session(
        &self,
        session: &mut CreateSession,
        ctx: &SeaOrmHookContext<'_>,
    ) -> AuthResult<HookControl>;

    async fn after_create_session(
        &self,
        session: &S::Session,
        ctx: &SeaOrmHookContext<'_>,
    ) -> AuthResult<()>;
}

Rejecting Operations

Return HookControl::Cancel or an error from a before_* hook to abort the operation:

#[async_trait]
impl SeaOrmHooks<AppAuthSchema> for BlockDisposableEmails {
    async fn before_create_user(
        &self,
        user: &mut CreateUser,
        _ctx: &SeaOrmHookContext<'_>,
    ) -> AuthResult<HookControl> {
        if let Some(email) = &user.email {
            if email.ends_with("@disposable.com") {
                return Ok(HookControl::Cancel);
            }
        }
        Ok(HookControl::Continue)
    }
}

Modifying Data

before_* hooks receive mutable references, so you can normalize or enrich values before they are persisted:

#[async_trait]
impl SeaOrmHooks<AppAuthSchema> for NormalizeEmail {
    async fn before_create_user(
        &self,
        user: &mut CreateUser,
        _ctx: &SeaOrmHookContext<'_>,
    ) -> AuthResult<HookControl> {
        if let Some(email) = &mut user.email {
            *email = email.to_lowercase();
        }
        Ok(HookControl::Continue)
    }
}

Provisioning After Signup

For app-specific side effects such as creating a workspace or seeding related records, prefer after_create_user:

use async_trait::async_trait;
use better_auth::AuthSchema;
use better_auth::seaorm::sea_orm::{ConnectionTrait, Statement};
use better_auth::seaorm::{DatabaseConnection, SeaOrmHookContext, SeaOrmHooks, SeaOrmStore};

#[derive(Clone)]
struct ProvisionWorkspaceHook {
    db: DatabaseConnection,
}

#[async_trait]
impl SeaOrmHooks<AppAuthSchema> for ProvisionWorkspaceHook {
    async fn after_create_user(
        &self,
        user: &<AppAuthSchema as AuthSchema>::User,
        _ctx: &SeaOrmHookContext<'_>,
    ) -> better_auth::AuthResult<()> {
        let sql = Statement::from_string(
            self.db.get_database_backend(),
            format!(
                "insert into app_workspaces (user_id, name) values ('{}', 'Default Workspace')",
                user.id()
            ),
        );
        let _ = self.db.execute(sql).await?;
        Ok(())
    }
}

let store = SeaOrmStore::<AppAuthSchema>::new(config.clone(), database.clone())
    .hook(ProvisionWorkspaceHook { db: database });

let auth = BetterAuth::<AppAuthSchema>::new(config)
    .store(store)
    .build()
    .await?;

Multiple Hooks

Multiple hooks can be registered. They execute in registration order:

let store = SeaOrmStore::<AppAuthSchema>::new(config.clone(), database)
    .hook(AuditHook)
    .hook(NormalizeEmail)
    .hook(BlockDisposableEmails);

let auth = BetterAuth::<AppAuthSchema>::new(config)
    .store(store)
    .build()
    .await?;

If any before_* hook returns HookControl::Cancel or an error, subsequent hooks and the write itself are skipped.

On this page