Authentication System

Empire Builder uses a custom authentication system built on Supabase, providing secure user management, session handling, and role-based access control.

Overview

The authentication system handles: - User registration and login - Secure password hashing and verification - Session management with tokens - User profile management - Integration with game data (empires)

Architecture

Components: - auth_supabase.py: Core authentication logic - Supabase Database: User data storage with Row Level Security - Flask Sessions: Web session management - Environment Configuration: Secure credential management

Security Features: - PBKDF2 password hashing with salt - UUID-based user identifiers - Secure session tokens - Row Level Security (RLS) policies - Service role separation

User Registration

Registration Process

  1. Form Validation - Username: 3+ characters, unique - Email: Valid format, unique - Password: 6+ characters minimum - Password confirmation match

  2. Security Checks - Username availability check - Email format validation - Password strength requirements - Duplicate prevention

  3. Account Creation - Password hashing with PBKDF2 - UUID generation for user ID - Database record creation - Success confirmation

Registration Endpoint:

@app.route('/register', methods=['GET', 'POST'])
def register():
    # Handle both form and JSON requests
    # Validate input data
    # Create user account
    # Return success/error response

Example Registration Request:

// JSON API request
fetch('/register', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({
        username: 'player123',
        email: 'player@empire-builder.com',
        password: 'securepassword',
        confirm_password: 'securepassword'
    })
})

User Login

Login Process

  1. Credential Verification - Username/email lookup - Password hash comparison - Account status check

  2. Session Creation - Generate secure session token - Store session in database - Set browser session cookie - Update last login timestamp

  3. Redirect to Dashboard - Successful login redirects to game - Failed login shows error message

Login Endpoint:

@app.route('/login', methods=['GET', 'POST'])
def login():
    # Authenticate user credentials
    # Create session if valid
    # Redirect to dashboard

Example Login Request:

// Form submission
<form method="POST" action="/login">
    <input name="username" required>
    <input name="password" type="password" required>
    <input name="remember_me" type="checkbox">
    <button type="submit">Login</button>
</form>

Password Security

Hashing Algorithm

Empire Builder uses PBKDF2 (Password-Based Key Derivation Function 2) for secure password storage:

def hash_password(self, password: str, salt: str = None) -> tuple:
    """Hash password with salt using PBKDF2"""
    if salt is None:
        salt = secrets.token_hex(32)

    password_hash = hashlib.pbkdf2_hmac(
        'sha256',
        password.encode('utf-8'),
        salt.encode('utf-8'),
        100000  # 100,000 iterations
    ).hex()

    return password_hash, salt

Security Properties: - Salt: Unique random salt per password - Iterations: 100,000 iterations (slow brute force) - Algorithm: SHA-256 based PBKDF2 - Storage: Hash and salt stored separately

Password Verification

def verify_password(self, password: str, password_hash: str, salt: str) -> bool:
    """Verify password against stored hash"""
    test_hash, _ = self.hash_password(password, salt)
    return test_hash == password_hash

Session Management

Session Creation

When a user logs in successfully:

  1. Generate Token: Cryptographically secure random token

  2. Store Session: Save to database with expiration

  3. Set Cookie: Browser session cookie

  4. Track Metadata: IP address, user agent, timestamp

def create_session(self, user_id: str, ip_address: str = None,
                  user_agent: str = None) -> str:
    """Create a secure session token"""
    session_token = secrets.token_urlsafe(32)
    expires_at = (datetime.now() + timedelta(days=30)).isoformat()

    session_data = {
        'user_id': user_id,
        'session_token': session_token,
        'expires_at': expires_at,
        'created_at': datetime.now().isoformat(),
        'ip_address': ip_address,
        'user_agent': user_agent
    }

    # Store in database
    result = self.service_client.table('user_sessions').insert(session_data).execute()
    return session_token if result.data else None

Session Validation

For each authenticated request:

  1. Extract Token: From session cookie

  2. Database Lookup: Find active session

  3. Expiration Check: Verify not expired

  4. User Loading: Load associated user data

def get_user_by_session(self, session_token: str) -> Optional[User]:
    """Get user by session token"""
    # Query active session
    # Check expiration
    # Return user object or None

Authentication Decorators

Route Protection

The @login_required decorator protects routes that need authentication:

def login_required(f):
    """Decorator to require login for routes"""
    @wraps(f)
    def decorated_function(*args, **kwargs):
        current_user = get_current_user()
        if not current_user:
            if request.is_json:
                return jsonify({'error': 'Authentication required'}), 401
            return redirect(url_for('login'))
        return f(*args, **kwargs)
    return decorated_function

Usage Example:

@app.route('/dashboard')
@login_required
def dashboard():
    current_user = get_current_user()
    # Protected route logic
    return render_template('dashboard.html', user=current_user)

User Management

User Model

@dataclass
class User:
    id: str              # UUID primary key
    username: str        # Unique username
    email: str          # Unique email address
    password_hash: str   # Hashed password
    salt: str           # Password salt
    created_at: str     # Registration timestamp
    last_login: str     # Last login timestamp
    is_active: bool     # Account status
    empire_id: str      # Linked empire (optional)

User Operations

Get Current User:

def get_current_user() -> Optional[User]:
    """Get currently logged-in user"""
    session_token = session.get('session_token')
    if session_token:
        return supabase_auth_db.get_user_by_session(session_token)
    return None

Update User Profile:

def update_user_profile(user_id: str, updates: dict):
    """Update user profile information"""
    # Validate updates
    # Apply changes to database
    # Return success/failure

Link User to Empire:

def link_user_to_empire(self, user_id: str, empire_id: str):
    """Link user account to game empire"""
    service_client = self.service_client or self.client
    service_client.table('users').update({
        'empire_id': empire_id
    }).eq('id', user_id).execute()

Database Schema

Users Table

CREATE TABLE users (
    id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    salt VARCHAR(255) NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    last_login TIMESTAMP WITH TIME ZONE,
    is_active BOOLEAN DEFAULT TRUE,
    empire_id UUID REFERENCES empires(id)
);

User Sessions Table

CREATE TABLE user_sessions (
    id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    user_id UUID REFERENCES users(id) ON DELETE CASCADE,
    session_token VARCHAR(255) UNIQUE NOT NULL,
    expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    ip_address INET,
    user_agent TEXT
);

Row Level Security

-- Enable RLS on users table
ALTER TABLE users ENABLE ROW LEVEL SECURITY;

-- Service role can manage all users
CREATE POLICY "Service role can manage users" ON users
FOR ALL USING (auth.role() = 'service_role');

-- Users can view their own data
CREATE POLICY "Users can view own data" ON users
FOR SELECT USING (auth.uid() = id);

Security Considerations

Best Practices

Password Security: - Minimum 6 characters (consider increasing) - PBKDF2 with 100,000 iterations - Unique salt per password - Secure random salt generation

Session Security: - Cryptographically secure tokens - 30-day expiration (configurable) - IP and user agent tracking - Automatic cleanup of expired sessions

Database Security: - Row Level Security enabled - Service role for admin operations - Separate anon key for client operations - Environment variable configuration

Application Security: - Input validation and sanitization - CSRF protection (Flask-WTF recommended) - Rate limiting on auth endpoints - Secure cookie configuration

Common Issues and Solutions

Registration Problems

“Row Level Security” Error: - Ensure service key is used for user creation - Verify RLS policies are correctly configured - Check that service client is initialized

“Username already exists”: - Implement proper uniqueness checking - Provide clear error messages - Consider case-insensitive usernames

Login Problems

“Invalid username or password”: - Check password hashing consistency - Verify database connection - Ensure user account is active

Session Issues: - Check session token generation - Verify database session storage - Confirm cookie configuration

Environment Variables: - Use load_dotenv(override=True) to override system vars - Verify all required variables are set - Check for typos in variable names

Testing Authentication

Debug Scripts

The project includes debug scripts for testing:

Registration Test:

python debug_registration.py

Login Test:

python debug_login.py

Connection Test:

python test_simple_connection.py

Manual Testing

  1. Registration Flow: - Try valid registration - Test validation errors - Verify database records

  2. Login Flow: - Test valid credentials - Test invalid credentials - Verify session creation

  3. Session Management: - Test protected routes - Verify logout functionality - Check session expiration

API Integration

For external applications or mobile clients:

JSON API Endpoints: - POST /register: Create new account - POST /login: Authenticate user - POST /logout: End session - GET /api/user/profile: Get user info

Authentication Headers: - Use session tokens in Authorization header - Support both cookie and header auth - Consistent error responses

This authentication system provides a secure, scalable foundation for Empire Builder’s user management needs while maintaining flexibility for future enhancements.