Files
ai/doc/auth.md

13 KiB

Authentication System Setup

This document describes the multi-channel authentication system for the Dexorder AI Gateway.

Overview

The gateway now implements a comprehensive authentication system using Better Auth with support for:

  • Email/Password authentication
  • Passkey/WebAuthn (passwordless biometric auth)
  • JWT token-based sessions
  • Multi-channel support (WebSocket, Telegram, REST API)
  • PostgreSQL-based user management
  • Secure password hashing with Argon2

Architecture

┌─────────────────────────────────────────────────────────────┐
│                        Client Apps                          │
│  (Web, Mobile, CLI, Telegram, etc.)                        │
└────────────┬────────────────────────────────┬───────────────┘
             │                                │
             │ HTTP/REST                      │ WebSocket
             │                                │
┌────────────▼────────────────────────────────▼───────────────┐
│                    Gateway (Fastify)                        │
│                                                             │
│  ┌──────────────┐  ┌──────────────┐  ┌─────────────────┐  │
│  │ Auth Routes  │  │ WebSocket    │  │ Telegram        │  │
│  │ /auth/*      │  │ Handler      │  │ Handler         │  │
│  └──────┬───────┘  └──────┬───────┘  └────────┬────────┘  │
│         │                 │                    │           │
│         └─────────────────┴────────────────────┘           │
│                           │                                │
│                  ┌────────▼──────────┐                     │
│                  │   Auth Service    │                     │
│                  │  (Better Auth)    │                     │
│                  └────────┬──────────┘                     │
│                           │                                │
└───────────────────────────┼────────────────────────────────┘
                            │
                   ┌────────▼────────┐
                   │   PostgreSQL    │
                   │  - users        │
                   │  - sessions     │
                   │  - passkeys     │
                   │  - credentials  │
                   └─────────────────┘

Database Schema

The authentication system uses the following PostgreSQL tables:

Core Tables

  1. users - Core user accounts

    • id (PRIMARY KEY)
    • email (UNIQUE)
    • email_verified
    • name
    • created_at, updated_at
  2. user_credentials - Password hashes

    • user_id (FOREIGN KEY → users.id)
    • password_hash (Argon2)
  3. sessions - JWT sessions

    • id (PRIMARY KEY)
    • user_id (FOREIGN KEY → users.id)
    • expires_at
    • ip_address, user_agent
  4. passkeys - WebAuthn credentials

    • id (PRIMARY KEY)
    • user_id (FOREIGN KEY → users.id)
    • credential_id (UNIQUE)
    • credential_public_key
    • counter, transports
  5. verification_tokens - Email verification, password reset

    • identifier, token, expires_at

Integration Tables

  1. user_licenses - User authorization & feature flags

    • user_id (FOREIGN KEY → users.id)
    • license_type (free, pro, enterprise)
    • features (JSONB)
    • resource_limits (JSONB)
  2. user_channel_links - Multi-channel support

    • user_id (FOREIGN KEY → users.id)
    • channel_type (telegram, slack, discord, websocket)
    • channel_user_id

Installation

  1. Install dependencies:
cd gateway
npm install

The following packages are added:

  • better-auth - Main authentication framework
  • @simplewebauthn/server - WebAuthn/passkey support
  • @simplewebauthn/browser - Client-side passkey helpers
  • @fastify/jwt - JWT utilities
  • argon2 - Secure password hashing
  1. Apply database schema:
psql $DATABASE_URL -f schema.sql
  1. Configure secrets:

Copy secrets.example.yaml to your actual secrets file and update:

auth:
  secret: "YOUR-SUPER-SECRET-KEY-HERE"  # Generate with: openssl rand -base64 32
  1. Configure server:

Update config.yaml:

server:
  base_url: http://localhost:3000  # Or your production URL
  trusted_origins:
    - http://localhost:3000
    - http://localhost:5173  # Your web app
    - https://yourdomain.com

API Endpoints

Authentication Routes

All Better Auth automatic routes are available at /api/auth/*:

  • POST /api/auth/sign-up/email - Register with email/password
  • POST /api/auth/sign-in/email - Sign in with email/password
  • POST /api/auth/sign-out - Sign out
  • GET /api/auth/session - Get current session
  • POST /api/auth/passkey/register - Register passkey
  • POST /api/auth/passkey/authenticate - Authenticate with passkey

Custom Routes (Simplified)

  • POST /auth/register - Register and auto sign-in
  • POST /auth/login - Sign in
  • POST /auth/logout - Sign out
  • GET /auth/session - Get session
  • GET /auth/health - Auth system health check

Example Usage

Register a new user

curl -X POST http://localhost:3000/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com",
    "password": "SecurePassword123!",
    "name": "John Doe"
  }'

Response:

{
  "success": true,
  "userId": "user_1234567890_abc123",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Sign in

curl -X POST http://localhost:3000/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com",
    "password": "SecurePassword123!"
  }'

Response:

{
  "success": true,
  "userId": "user_1234567890_abc123",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Connect to WebSocket with JWT

const ws = new WebSocket('ws://localhost:3000/ws/chat');
ws.addEventListener('open', () => {
  // Send auth token in initial message
  ws.send(JSON.stringify({
    type: 'auth',
    token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
  }));
});

Or use Authorization header:

const ws = new WebSocket('ws://localhost:3000/ws/chat', {
  headers: {
    'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
  }
});

Get current session

curl http://localhost:3000/auth/session \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Passkey (WebAuthn) Support

Server Setup

Passkeys are automatically configured in better-auth-config.ts:

passkey({
  rpName: 'Dexorder AI',
  rpID: new URL(config.baseUrl).hostname,
  origin: config.baseUrl,
})

Client-Side Integration

import { startRegistration, startAuthentication } from '@simplewebauthn/browser';

// 1. Register a passkey (user must be logged in)
async function registerPasskey(token: string) {
  // Get options from server
  const optionsResponse = await fetch('/auth/passkey/register/options', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`
    }
  });
  const options = await optionsResponse.json();

  // Start WebAuthn registration
  const credential = await startRegistration(options);

  // Send credential to server
  const response = await fetch('/api/auth/passkey/register', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`
    },
    body: JSON.stringify({ credential })
  });

  return response.json();
}

// 2. Authenticate with passkey
async function authenticateWithPasskey() {
  // Get challenge from server
  const optionsResponse = await fetch('/api/auth/passkey/authenticate/options');
  const options = await optionsResponse.json();

  // Start WebAuthn authentication
  const credential = await startAuthentication(options);

  // Verify with server
  const response = await fetch('/auth/passkey/authenticate', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ credential })
  });

  const { token, userId } = await response.json();
  return { token, userId };
}

Multi-Channel Support

WebSocket Authentication

The WebSocket handler (websocket-handler.ts) now properly verifies JWT tokens:

// User connects with JWT token in Authorization header
const authContext = await authenticator.authenticateWebSocket(request);
// Returns: { userId, sessionId, license, ... }

Telegram Bot Authentication

Users link their Telegram account via the user_channel_links table:

INSERT INTO user_channel_links (user_id, channel_type, channel_user_id)
VALUES ('user_1234567890_abc123', 'telegram', '987654321');

The authenticator.authenticateTelegram() method resolves the user from their Telegram ID.

API Authentication

All REST API calls use the Authorization: Bearer <token> header.

Security Considerations

Production Checklist

  • Generate a strong random secret: openssl rand -base64 32
  • Enable email verification: Set requireEmailVerification: true
  • Configure HTTPS only in production
  • Set proper trusted_origins for CORS
  • Implement rate limiting (consider adding @fastify/rate-limit)
  • Set up email service for password reset
  • Configure session expiry based on security requirements
  • Enable 2FA for sensitive operations
  • Implement audit logging for auth events
  • Set up monitoring for failed login attempts

Password Security

  • Uses Argon2 (winner of Password Hashing Competition)
  • Automatically salted and hashed by Better Auth
  • Never stored or logged in plain text

JWT Security

  • Tokens expire after 7 days (configurable)
  • Sessions update every 24 hours
  • Tokens signed with HMAC-SHA256
  • Store secret in k8s secrets, never in code

Passkey Security

  • Uses FIDO2/WebAuthn standards
  • Hardware-backed authentication
  • Phishing-resistant
  • No passwords to leak or forget

Migration Guide

If you have existing users with a different auth system:

  1. Create users in new schema:
INSERT INTO users (id, email, email_verified, name)
SELECT user_id, email, true, name FROM old_users_table;
  1. Migrate licenses:
-- Ensure user_licenses references users.id
UPDATE user_licenses SET user_id = users.id WHERE ...;
  1. User must reset password or register passkey on first login with new system

Troubleshooting

"Authentication failed" on WebSocket

  • Check that the JWT token is valid and not expired
  • Verify the Authorization header format: Bearer <token>
  • Check server logs for detailed error messages

"Invalid credentials" on login

  • Verify the user exists in the users table
  • Check that user_credentials has a password_hash for the user
  • Passwords are case-sensitive

Passkey registration fails

  • Check browser support for WebAuthn
  • Verify HTTPS is enabled (required for WebAuthn in production)
  • Check rpID matches your domain
  • Ensure user is authenticated before registering passkey

Development Tips

Testing with dev user

A development user is created automatically:

// Email: dev@example.com
// User ID: dev-user-001
// License: pro

Generate a token for testing:

curl -X POST http://localhost:3000/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"dev@example.com","password":"<set-in-db>"}'

Inspecting tokens

# Decode JWT (header and payload only, signature verification needed)
echo "eyJhbGc..." | cut -d. -f2 | base64 -d | jq

Database queries

-- List all users
SELECT id, email, name, created_at FROM users;

-- List active sessions
SELECT s.id, s.user_id, u.email, s.expires_at
FROM sessions s
JOIN users u ON s.user_id = u.id
WHERE s.expires_at > NOW();

-- List passkeys
SELECT p.id, p.name, u.email, p.created_at
FROM passkeys p
JOIN users u ON p.user_id = u.id;

Future Enhancements

Potential additions to consider:

  • OAuth providers (Google, GitHub, etc.)
  • Magic link authentication
  • Two-factor authentication (TOTP)
  • Session management dashboard
  • Audit log for security events
  • IP-based restrictions
  • Device management (trusted devices)
  • Anonymous authentication for trials

References