> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/trailbaseio/trailbase/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> Setting up authentication, user registration and login, OAuth providers in TrailBase

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:

```sql theme={null}
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())
);
```

<Warning>
  Do not modify the `_user` table directly. Use TrailBase's auth APIs and CLI commands instead.
</Warning>

## Password Authentication

### Enable Password Auth

Password authentication is enabled by default. Configure it in `traildepot/config.textproto`:

```protobuf traildepot/config.textproto theme={null}
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: "your-email@gmail.com"
  smtp_password: "your-app-password"
  from_address: "noreply@yourdomain.com"
}
```

### User Registration

<Tabs>
  <Tab title="TypeScript Client">
    ```typescript theme={null}
    import { initClient } from "trailbase";

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

    // Register new user
    try {
      await client.register({
        email: "user@example.com",
        password: "SecurePass123",
        password_repeat: "SecurePass123",
      });
      
      console.log("Registration successful! Check email for verification.");
    } catch (error) {
      console.error("Registration failed:", error);
    }
    ```
  </Tab>

  <Tab title="Dart/Flutter">
    ```dart theme={null}
    import 'package:trailbase/trailbase.dart';

    final client = Client("http://localhost:4000");

    // Register new user
    try {
      await client.register(
        email: "user@example.com",
        password: "SecurePass123",
        passwordRepeat: "SecurePass123",
      );
      
      print("Registration successful! Check email for verification.");
    } catch (e) {
      print("Registration failed: $e");
    }
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    curl -X POST http://localhost:4000/_/auth/register \
      -H "Content-Type: application/json" \
      -d '{
        "email": "user@example.com",
        "password": "SecurePass123",
        "password_repeat": "SecurePass123"
      }'
    ```
  </Tab>
</Tabs>

<Info>
  If email verification is enabled, users must click the verification link in their email before logging in.
</Info>

### User Login

<Tabs>
  <Tab title="TypeScript Client">
    ```typescript theme={null}
    import { initClient } from "trailbase";

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

    // Login
    try {
      const response = await client.login({
        email: "user@example.com",
        password: "SecurePass123",
      });
      
      // Tokens are automatically stored by the client
      console.log("Logged in:", client.user());
      // { id: "...", email: "user@example.com", admin: false }
    } catch (error) {
      console.error("Login failed:", error);
    }
    ```
  </Tab>

  <Tab title="Dart/Flutter">
    ```dart theme={null}
    import 'package:trailbase/trailbase.dart';

    final client = Client("http://localhost:4000");

    // Login
    try {
      final response = await client.login(
        email: "user@example.com",
        password: "SecurePass123",
      );
      
      print("Logged in: ${client.user()}");
    } catch (e) {
      print("Login failed: $e");
    }
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    curl -X POST http://localhost:4000/_/auth/login \
      -H "Content-Type: application/json" \
      -d '{
        "email": "user@example.com",
        "password": "SecurePass123"
      }'

    # Response:
    {
      "auth_token": "eyJ0eXAiOiJKV1QiLCJhbGc...",
      "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGc...",
      "csrf_token": "random-csrf-token"
    }
    ```
  </Tab>
</Tabs>

### Token Management

The client automatically handles token storage and refresh:

```typescript theme={null}
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):

```typescript theme={null}
// 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

<Steps>
  <Step title="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](https://console.cloud.google.com)
    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`
  </Step>

  <Step title="Add Provider to Config">
    Edit `traildepot/config.textproto`:

    ```protobuf traildepot/config.textproto theme={null}
    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"
    }
    ```
  </Step>

  <Step title="Restart TrailBase">
    ```bash theme={null}
    trail run
    ```
  </Step>
</Steps>

### OAuth Login Flow

<Tabs>
  <Tab title="Web (Redirect)">
    ```typescript theme={null}
    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());
    }
    ```
  </Tab>

  <Tab title="Mobile (PKCE)">
    ```dart theme={null}
    import 'package:trailbase/trailbase.dart';
    import 'package:uni_links/uni_links.dart';

    final client = Client("http://localhost:4000");

    // Start OAuth flow with PKCE
    Future<void> loginWithGitHub() async {
      // Generate PKCE challenge
      final pkce = await client.generatePkce();
      
      // Open OAuth URL in browser
      final url = client.oauthUrl(
        "github",
        redirectUri: "myapp://auth/callback",
        pkceChallenge: pkce.challenge,
      );
      
      await launchUrl(Uri.parse(url));
      
      // Listen for deep link callback
      final uri = await getInitialUri();
      if (uri != null && uri.queryParameters.containsKey('code')) {
        final code = uri.queryParameters['code']!;
        
        // Exchange code for tokens
        await client.oauthCallback(
          code: code,
          codeVerifier: pkce.verifier,
        );
        
        print("Logged in: ${client.user()}");
      }
    }
    ```
  </Tab>

  <Tab title="Direct URL">
    ```html theme={null}
    <!-- Login button that redirects to OAuth provider -->
    <a href="http://localhost:4000/_/auth/login?provider=github&redirect_uri=http://localhost:3000/callback">
      Login with GitHub
    </a>

    <!-- Or using the login UI -->
    <a href="http://localhost:4000/_/auth/login">
      Login
    </a>
    ```
  </Tab>
</Tabs>

<Note>
  **Mobile Apps:** Use PKCE (Proof Key for Code Exchange) for secure OAuth flows without exposing client secrets.
</Note>

### Custom URI Schemes (Mobile)

For mobile apps, configure custom URI schemes:

```protobuf traildepot/config.textproto theme={null}
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:

```protobuf traildepot/config.textproto theme={null}
auth {
  require_email_verification: true
}

email {
  smtp_host: "smtp.gmail.com"
  smtp_port: 587
  smtp_username: "your-email@gmail.com"
  smtp_password: "your-app-password"
  from_address: "noreply@yourdomain.com"
}
```

### Verify Email Manually

Users can request a new verification email:

```typescript theme={null}
await client.requestVerificationEmail();
```

### CLI Verification

For development, verify users via CLI:

```bash theme={null}
# Verify a user
trail user verify user@example.com true

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

## Password Reset

### Request Password Reset

```typescript theme={null}
import { initClient } from "trailbase";

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

// Send password reset email
await client.requestPasswordReset("user@example.com");
```

### Reset Password with Token

The reset email contains a link with a token. Users submit a new password:

```typescript theme={null}
// 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

```bash theme={null}
# Add a new verified admin user
trail user add admin@example.com SecurePass123
trail admin promote admin@example.com
```

### List Admin Users

```bash theme={null}
trail admin list
```

### Promote/Demote Users

```bash theme={null}
# Promote user to admin
trail admin promote user@example.com

# Demote admin to regular user
trail admin demote admin@example.com
```

### Change User Password

```bash theme={null}
trail user change-password user@example.com NewPassword123
```

### Change User Email

```bash theme={null}
trail user change-email old@example.com new@example.com
```

### Delete User

```bash theme={null}
trail user delete user@example.com
```

### Invalidate Sessions

Force a user to re-login:

```bash theme={null}
trail user invalidate-session user@example.com
```

### Mint Auth Token

Generate an auth token for a user (useful for automation):

```bash theme={null}
trail user mint-token user@example.com
```

## Access Control

Protect your Record APIs with authentication:

```protobuf traildepot/config.textproto theme={null}
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:

```protobuf traildepot/config.textproto theme={null}
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:

```protobuf traildepot/config.textproto theme={null}
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"
  }
]
```

<Info>
  **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)
</Info>

## User Profiles

Extend user data with a profiles table:

```sql migrations/main/U1234567890__create_profiles.sql theme={null}
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:

```protobuf traildepot/config.textproto theme={null}
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:

```typescript theme={null}
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

<CardGroup cols={2}>
  <Card title="First App" icon="rocket" href="/guides/first-app">
    Add auth to your first app
  </Card>

  <Card title="Database Setup" icon="database" href="/guides/database-setup">
    Link users with your tables
  </Card>

  <Card title="File Uploads" icon="upload" href="/guides/file-uploads">
    Handle user file uploads
  </Card>

  <Card title="CLI Usage" icon="terminal" href="/guides/cli-usage">
    Manage users via CLI
  </Card>
</CardGroup>
