Skip to main content
TrailBase provides built-in authentication with support for password-based login, OAuth providers, email verification, and password reset flows.

Overview

TrailBase authentication features:
  • Password authentication - Email and password login with configurable policies
  • OAuth providers - Google, GitHub, Discord, Microsoft, and more
  • Email verification - Confirm user email addresses
  • Password reset - Secure password recovery via email
  • Session management - JWT tokens with refresh tokens
  • Admin accounts - Special privileges for administrative users

User Table

TrailBase includes a built-in _user table:
CREATE TABLE _user (
  id            BLOB PRIMARY KEY DEFAULT (uuid_v7()),
  email         TEXT NOT NULL UNIQUE,
  password_hash TEXT,
  verified      INTEGER DEFAULT 0,
  admin         INTEGER DEFAULT 0,
  created       INTEGER DEFAULT (UNIXEPOCH()),
  updated       INTEGER DEFAULT (UNIXEPOCH())
);
Do not modify the _user table directly. Use TrailBase’s auth APIs and CLI commands instead.

Password Authentication

Enable Password Auth

Password authentication is enabled by default. Configure it in traildepot/config.textproto:
traildepot/config.textproto
auth {
  # Disable password auth (optional)
  disable_password_auth: false
  
  # Password policy
  password_policy {
    min_length: 8
    require_uppercase: true
    require_lowercase: true
    require_number: true
    require_special: false
  }
  
  # Email verification settings
  require_email_verification: true
}

server {
  site_url: "http://localhost:4000"
}

email {
  # Configure SMTP for email verification
  smtp_host: "smtp.gmail.com"
  smtp_port: 587
  smtp_username: "[email protected]"
  smtp_password: "your-app-password"
  from_address: "[email protected]"
}

User Registration

import { initClient } from "trailbase";

const client = initClient("http://localhost:4000");

// Register new user
try {
  await client.register({
    email: "[email protected]",
    password: "SecurePass123",
    password_repeat: "SecurePass123",
  });
  
  console.log("Registration successful! Check email for verification.");
} catch (error) {
  console.error("Registration failed:", error);
}
If email verification is enabled, users must click the verification link in their email before logging in.

User Login

import { initClient } from "trailbase";

const client = initClient("http://localhost:4000");

// Login
try {
  const response = await client.login({
    email: "[email protected]",
    password: "SecurePass123",
  });
  
  // Tokens are automatically stored by the client
  console.log("Logged in:", client.user());
  // { id: "...", email: "[email protected]", admin: false }
} catch (error) {
  console.error("Login failed:", error);
}

Token Management

The client automatically handles token storage and refresh:
import { initClient } from "trailbase";

const client = initClient("http://localhost:4000");

// Check if user is logged in
if (client.user()) {
  console.log("User:", client.user());
}

// Get tokens
const tokens = client.tokens();
if (tokens) {
  console.log("Auth token:", tokens.auth_token);
  console.log("Refresh token:", tokens.refresh_token);
}

// Logout
await client.logout();

Session Persistence

Tokens are stored in localStorage (browser) or secure storage (mobile):
// Client automatically restores session on initialization
const client = initClient("http://localhost:4000");

// User is automatically logged in if valid tokens exist
if (client.user()) {
  console.log("Restored session for:", client.user()?.email);
}

OAuth Providers

TrailBase supports multiple OAuth providers:
  • Google
  • GitHub
  • Discord
  • Microsoft
  • GitLab
  • And more…

Configure OAuth Provider

1

Register OAuth Application

Create an OAuth app with your provider:GitHub:
  1. Go to Settings → Developer settings → OAuth Apps
  2. Click “New OAuth App”
  3. Set callback URL: http://localhost:4000/_/auth/oauth/callback/github
  4. Save Client ID and Client Secret
Google:
  1. Go to Google Cloud Console
  2. Create a new project
  3. Enable Google+ API
  4. Create OAuth 2.0 credentials
  5. Add authorized redirect URI: http://localhost:4000/_/auth/oauth/callback/google
2

Add Provider to Config

Edit traildepot/config.textproto:
traildepot/config.textproto
auth {
  oauth_providers: [
    {
      key: "github"
      value {
        client_id: "your-github-client-id"
        client_secret: "your-github-client-secret"
        provider_id: GITHUB
      }
    },
    {
      key: "google"
      value {
        client_id: "your-google-client-id.apps.googleusercontent.com"
        client_secret: "your-google-client-secret"
        provider_id: GOOGLE
      }
    }
  ]
}

server {
  # Required for OAuth redirects
  site_url: "http://localhost:4000"
}
3

Restart TrailBase

trail run

OAuth Login Flow

import { initClient } from "trailbase";

const client = initClient("http://localhost:4000");

// Redirect to OAuth provider
function loginWithGitHub() {
  window.location.href = client.oauthUrl("github", {
    redirect_uri: window.location.origin + "/callback",
  });
}

// Handle callback on return
const params = new URLSearchParams(window.location.search);
if (params.has("code")) {
  // TrailBase automatically handles the OAuth callback
  // and sets the auth tokens
  console.log("Logged in:", client.user());
}
Mobile Apps: Use PKCE (Proof Key for Code Exchange) for secure OAuth flows without exposing client secrets.

Custom URI Schemes (Mobile)

For mobile apps, configure custom URI schemes:
traildepot/config.textproto
auth {
  # Allow deep links for mobile apps
  custom_uri_schemes: ["myapp"]
  
  oauth_providers: [
    {
      key: "github"
      value {
        client_id: "your-client-id"
        client_secret: "your-client-secret"
        provider_id: GITHUB
      }
    }
  ]
}
OAuth redirects will now work with myapp://auth/callback.

Email Verification

Send Verification Email

After registration, TrailBase automatically sends a verification email if configured:
traildepot/config.textproto
auth {
  require_email_verification: true
}

email {
  smtp_host: "smtp.gmail.com"
  smtp_port: 587
  smtp_username: "[email protected]"
  smtp_password: "your-app-password"
  from_address: "[email protected]"
}

Verify Email Manually

Users can request a new verification email:
await client.requestVerificationEmail();

CLI Verification

For development, verify users via CLI:
# Verify a user
trail user verify [email protected] true

# List unverified users
sqlite3 traildepot/data/main.db "SELECT email FROM _user WHERE verified = 0;"

Password Reset

Request Password Reset

import { initClient } from "trailbase";

const client = initClient("http://localhost:4000");

// Send password reset email
await client.requestPasswordReset("[email protected]");

Reset Password with Token

The reset email contains a link with a token. Users submit a new password:
// Extract token from URL: ?token=abc123
const params = new URLSearchParams(window.location.search);
const token = params.get("token");

if (token) {
  await client.resetPassword({
    token,
    password: "NewSecurePass123",
    password_repeat: "NewSecurePass123",
  });
}

User Management CLI

Manage users via the CLI:

Create Admin User

# Add a new verified admin user
trail user add [email protected] SecurePass123
trail admin promote [email protected]

List Admin Users

trail admin list

Promote/Demote Users

# Promote user to admin
trail admin promote [email protected]

# Demote admin to regular user
trail admin demote [email protected]

Change User Password

trail user change-password [email protected] NewPassword123

Change User Email

trail user change-email [email protected] [email protected]

Delete User

trail user delete [email protected]

Invalidate Sessions

Force a user to re-login:
trail user invalidate-session [email protected]

Mint Auth Token

Generate an auth token for a user (useful for automation):
trail user mint-token [email protected]

Access Control

Protect your Record APIs with authentication:
traildepot/config.textproto
record_apis: [
  {
    name: "todos"
    table_name: "todos"
    # Only authenticated users can access
    acl_authenticated: [CREATE, READ, UPDATE, DELETE]
  },
  {
    name: "admin_settings"
    table_name: "settings"
    # Only admins can access
    acl_admin: [CREATE, READ, UPDATE, DELETE]
  }
]

Row-Level Access Control

Restrict access to specific rows:
traildepot/config.textproto
record_apis: [
  {
    name: "todos"
    table_name: "todos"
    autofill_missing_user_id_columns: true
    acl_authenticated: [CREATE, READ, UPDATE, DELETE]
    
    # Users can only see their own todos
    read_access_rule: "_ROW_.user = _USER_.id"
    
    # Users can only update their own todos
    update_access_rule: "_ROW_.user = _USER_.id"
    
    # Users can only delete their own todos
    delete_access_rule: "_ROW_.user = _USER_.id"
  }
]

Complex Access Rules

Use SQL expressions for advanced access control:
traildepot/config.textproto
record_apis: [
  {
    name: "articles"
    table_name: "articles"
    acl_authenticated: [CREATE, READ, UPDATE, DELETE]
    
    # Only editors can create articles
    create_access_rule: "EXISTS(SELECT * FROM editors WHERE user = _USER_.id)"
    
    # Authors can update their own articles
    update_access_rule: "_ROW_.author = _USER_.id AND EXISTS(SELECT * FROM editors WHERE user = _USER_.id)"
    
    # Authors can delete their own articles
    delete_access_rule: "_ROW_.author = _USER_.id"
  }
]
Access Rule Variables:
  • _USER_.id - Current user’s ID
  • _USER_.email - Current user’s email
  • _USER_.admin - Whether user is admin
  • _ROW_.* - Column values from the row being accessed
  • _REQ_.* - Values from the request body (for CREATE/UPDATE)

User Profiles

Extend user data with a profiles table:
migrations/main/U1234567890__create_profiles.sql
CREATE TABLE profiles (
    user         BLOB PRIMARY KEY NOT NULL REFERENCES _user(id) ON DELETE CASCADE,
    username     TEXT NOT NULL CHECK(username REGEXP '^[\w]{3,}$'),
    bio          TEXT,
    avatar_url   TEXT,
    created      INTEGER DEFAULT (UNIXEPOCH()) NOT NULL,
    updated      INTEGER DEFAULT (UNIXEPOCH()) NOT NULL
) STRICT;

CREATE UNIQUE INDEX _profiles__username_index ON profiles (username);

CREATE TRIGGER _profiles__updated_trigger AFTER UPDATE ON profiles FOR EACH ROW
  BEGIN
    UPDATE profiles SET updated = UNIXEPOCH() WHERE user = OLD.user;
  END;
Expose via API:
traildepot/config.textproto
record_apis: [
  {
    name: "profiles"
    table_name: "profiles"
    acl_authenticated: [CREATE, READ]
    acl_world: [READ]
    # Users can only create their own profile
    create_access_rule: "_REQ_.user = _USER_.id"
  }
]

Avatar Uploads

TrailBase provides built-in avatar support:
import { initClient } from "trailbase";

const client = initClient("http://localhost:4000");

// Upload avatar
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];

await client.uploadAvatar(file);

// Get avatar URL
const avatarUrl = client.avatarUrl();
if (avatarUrl) {
  console.log("Avatar:", avatarUrl);
  // http://localhost:4000/api/auth/avatar/user-id
}

Next Steps

First App

Add auth to your first app

Database Setup

Link users with your tables

File Uploads

Handle user file uploads

CLI Usage

Manage users via CLI

Build docs developers (and LLMs) love