Authentication Guide

⚠️ Documentation Under Review: This documentation is currently being updated and verified against the actual implementation. Some information may be incorrect or incomplete. Please verify all code examples against the actual source code before use.

Overview

The Neuron CMS provides a comprehensive authentication system with session-based login, CSRF protection, brute force prevention, and "remember me" functionality. The system is designed with security best practices and supports role-based access control.

Architecture

Core Components

The authentication system consists of the following classes:

Authentication Filters

Route protection is implemented via filters:

Helper Functions

Global helper functions provide convenient authentication access (defined in src/Cms/Auth/helpers.php):

Configuration

Authentication configuration is defined in config/auth.yaml.

Session Configuration

auth:
  session:
    lifetime: 120              # Session lifetime in minutes (2 hours)
    expire_on_close: false     # Expire session when browser closes
    cookie_name: neuron_session
    cookie_httponly: true      # Prevent JavaScript access to cookie
    cookie_secure: true        # Require HTTPS (set false for local dev)
    cookie_samesite: Lax       # CSRF protection (Lax|Strict|None)

Session lifetime: Sessions expire after 120 minutes (2 hours) of inactivity.

Cookie security:

Password Configuration

auth:
  passwords:
    min_length: 8                  # Minimum password length
    require_uppercase: true        # Require uppercase letter
    require_lowercase: true        # Require lowercase letter
    require_numbers: true          # Require number
    require_special_chars: false   # Require special character
    hash_algorithm: argon2id       # argon2id or bcrypt

Hash algorithm:

PasswordHasher automatically selects Argon2id if available, otherwise falls back to Bcrypt.

Remember Me Configuration

auth:
  remember:
    enabled: true
    lifetime: 43200    # 30 days in minutes

Brute Force Protection

auth:
  throttle:
    enabled: true
    max_attempts: 5         # Maximum failed login attempts
    lockout_duration: 15    # Account lockout duration in minutes

After 5 failed login attempts, the account is locked for 15 minutes.

Session Management

Secure Session Initialization

SessionManager automatically configures secure session settings:

use Neuron\Cms\Auth\SessionManager;

$sessionManager = new SessionManager( [
    'lifetime' => 7200,        // 2 hours in seconds
    'cookie_httponly' => true,
    'cookie_secure' => true,   // Auto-detects HTTPS
    'cookie_samesite' => 'Lax',
    'use_strict_mode' => true
] );

$sessionManager->start();

Auto-detection: SessionManager automatically detects HTTPS and sets cookie_secure appropriately.

Strict mode: Prevents session fixation by rejecting uninitialized session IDs.

Session Regeneration

Session IDs are regenerated on login to prevent session fixation attacks:

$sessionManager->regenerate();

This is automatically handled by AuthManager during the login process.

Flash Messages

Session manager supports flash messages for one-time notifications:

// Set flash message
$sessionManager->flash( 'success', 'Login successful' );

// Retrieve flash message (automatically cleared after retrieval)
$message = $sessionManager->getFlash( 'success' );

Password Security

Password Hashing

Passwords are hashed using PHP's native password_hash() with Argon2id or Bcrypt:

use Neuron\Cms\Auth\PasswordHasher;

$hasher = new PasswordHasher();

// Hash password
$hash = $hasher->hash( 'SecurePassword123' );

// Verify password
$valid = $hasher->verify( 'SecurePassword123', $hash );

Password Strength Validation

PasswordHasher validates passwords against configured requirements:

$hasher = new PasswordHasher();

if( !$hasher->meetsRequirements( $password ) )
{
    // Password does not meet strength requirements
}

Requirements checked:

Automatic Password Rehashing

When stronger hash algorithms become available, Authentication automatically rehashes passwords during login:

if ($hasher->needsRehash( $user->getPasswordHash())) {
    $user->setPasswordHash( $hasher->hash($password ));
}

This occurs transparently during successful authentication without user intervention.

Authentication Workflow

Login Process

The complete login workflow:

  1. User submits credentials
  2. AuthManager retrieves user by username
  3. Account status is verified (active, not locked)
  4. Password is verified against hash
  5. Failed attempts are tracked; account locked after max attempts
  6. On success: session regenerated, user logged in, remember token set (if requested)
  7. Password automatically rehashed if algorithm upgraded
use Neuron\Cms\Services\Auth\Authentication;
use Neuron\Cms\Auth\SessionManager;
use Neuron\Cms\Auth\PasswordHasher;
use Neuron\Cms\Repositories\DatabaseUserRepository;

$sessionManager = new SessionManager();
$sessionManager->start();

$passwordHasher = new PasswordHasher();
$userRepository = new DatabaseUserRepository( $settingManager );

$authentication = new Authentication( $userRepository,
    $sessionManager,
    $passwordHasher );

// Attempt login
$remember = isset( $_POST['remember'] ) && $_POST['remember'] === '1';

if( $authentication->attempt( $_POST['username'], $_POST['password'], $remember ) )
{
    // Login successful
    header( 'Location: /admin/dashboard' );
}
else
{
    // Login failed
    $errors[] = 'Invalid credentials or account locked';
}

Logout Process

Logout clears session and remember tokens:

$authentication->logout();

This performs:

  1. Clears remember token from database
  2. Destroys session
  3. Deletes remember cookie

Checking Authentication Status

if( $authentication->check() )
{
    // User is authenticated
    $user = $authentication->user();
}

Authentication checks both session and remember token for authentication.

CSRF Protection

Token Generation

CSRF tokens are 64-character hex strings generated from 32 random bytes:

use Neuron\Cms\Services\Auth\CsrfToken;

$csrfToken = new CsrfToken( $sessionManager );
$token = $csrfToken->getToken();

Tokens are stored in the session and persist across requests.

Form Integration

Include CSRF token in forms using the helper function:

<form method="POST" action="/admin/posts">
    <?= csrf_field() ?>
    <!-- Form fields -->
</form>

This generates:

<input type="hidden" name="csrf_token" value="abc123...">

Token Validation

CSRF tokens are validated using timing-attack-safe comparison:

if( !$csrfToken->validate( $_POST['csrf_token'] ) )
{
    // CSRF validation failed
    http_response_code( 403 );
    exit( 'CSRF token validation failed' );
}

The CsrfFilter automatically validates tokens for POST/PUT/DELETE requests.

Token Regeneration

Regenerate token after sensitive operations:

$csrfToken->regenerate();

Brute Force Protection

Failed Attempt Tracking

Authentication tracks failed login attempts per user:

// Failed login increments counter
$user->incrementFailedLoginAttempts();

// Successful login resets counter
$user->resetFailedLoginAttempts();

Account Lockout

After exceeding max_attempts (default: 5), the account is locked:

if ($user->getFailedLoginAttempts() >= 5) {
    $lockedUntil = (new DateTimeImmutable())->add( new DateInterval( 'PT15M' ));
    $user->setLockedUntil( $lockedUntil );
}

Account remains locked for lockout_duration minutes (default: 15).

Checking Lockout Status

if ($user->isLockedOut()) {
    // Account is currently locked
}

Lockout automatically expires after the duration elapses.

Timing Attack Prevention

Authentication performs dummy hash verification for non-existent users to normalize response time:

if( !$user )
{
    // Perform dummy hash to prevent timing attack
    $passwordHasher->verify( $password, '$2y$10$dummyhashtopreventtimingattack1234567890' );
    return false;
}

This prevents attackers from determining valid usernames through response timing analysis.

Remember Me Functionality

Remember Token Generation

Remember tokens are secure random strings stored hashed in the database:

private function setRememberToken( User $user ): void
{
    $token = bin2hex( random_bytes(32 )); // 64-character hex string
    $user->setRememberToken( hash('sha256', $token ));

    // Set cookie with token (30 days)
    setcookie( 'remember_token',
        $token,
        time() + (30 * 24 * 60 * 60),
        '/',
        '',
        true,  // Secure
        true   // HttpOnly );
}

Token Validation

On subsequent requests, remember token is validated:

if (isset( $_COOKIE['remember_token'] )) {
    $authentication->loginUsingRememberToken( $_COOKIE['remember_token'] );
}

If valid, user is automatically logged in without credentials.

Security Considerations

Role-Based Access Control

User Roles

The system supports four predefined roles (defined in UserRole enum):

Role Checking

Using User model methods:

use Neuron\Cms\Enums\UserRole;

$user = auth();

if( $user->isAdmin() )
{
    // Administrator access
}

if( $user->isEditor() )
{
    // Editor access
}

if( $user->isAuthor() )
{
    // Author access
}

if( $user->getRole() === UserRole::Admin )
{
    // Direct role comparison
}

Using helper functions:

if( is_admin() )
{
    // Current user is administrator
}

if( is_editor() )
{
    // Current user is editor
}

if( is_author() )
{
    // Current user is author
}

if( has_role( 'admin' ) )
{
    // Current user has specified role
}

Route Protection

Applying Authentication Filter

Register AuthenticationFilter in route configuration:

admin_dashboard:
  method: GET
  route: /admin/dashboard
  controller: Neuron\Cms\Controllers\Admin\Dashboard@index
  filter: auth

Filter Registration

In application bootstrap:

use Neuron\Cms\Auth\Filters\AuthenticationFilter;

$authFilter = new AuthenticationFilter( $authentication, '/login' );
$router->registerFilter( 'auth', $authFilter );

Post-Login Redirect

AuthenticationFilter stores the intended URL and redirects after login:

/admin/dashboard → /login?redirect=/admin/dashboard → /admin/dashboard

Multiple Filters

Apply multiple filters to a route:

admin_users:
  method: GET
  route: /admin/users
  controller: Neuron\Cms\Controllers\Admin\Users@index
  filter: auth,csrf

Programmatic Usage

Manual Login

Bypass credential verification and log user in directly:

$user = $userRepository->findById( 1 );
$authentication->login( $user, $remember = false );

Retrieving Authenticated User

// Using Authentication
$user = $authentication->user();

// Using helper function
$user = auth();

// Using alternative helper
$user = user();

All methods return null if not authenticated.

User ID Retrieval

$userId = user_id(); // Returns int or null

Authentication Checks

// Check if authenticated
if (is_logged_in()) {
    // User is authenticated
}

// Check if guest
if (is_guest()) {
    // User is not authenticated
}

Security Best Practices

HTTPS Enforcement

Always use HTTPS in production:

auth:
  session:
    cookie_secure: true

Without HTTPS, session cookies can be intercepted.

Session Configuration

For production environments:

auth:
  session:
    cookie_samesite: Strict   # Maximum CSRF protection
    cookie_secure: true       # Require HTTPS
    cookie_httponly: true     # Prevent XSS

For development environments:

auth:
  session:
    cookie_samesite: Lax      # Allow some cross-site requests
    cookie_secure: false      # Allow HTTP
    cookie_httponly: true     # Still prevent XSS

Password Policy

Enforce strong passwords:

auth:
  passwords:
    min_length: 12
    require_uppercase: true
    require_lowercase: true
    require_numbers: true
    require_special_chars: true

Lockout Configuration

Balance security and usability:

auth:
  throttle:
    max_attempts: 3           # Stricter limit
    lockout_duration: 30      # Longer lockout

Troubleshooting

Session Not Persisting

Symptoms: User logged out immediately after login

Solutions:

  1. Check cookie settings:

    • Set cookie_secure: false for HTTP (development)
    • Verify browser accepts cookies
    • Check cookie domain settings
  2. Check session storage:

    # Verify session directory writable
    ls -la /tmp  # or session.save_path
    
  3. Check SameSite setting:

    • Use Lax for general compatibility
    • Use None only with Secure flag for cross-site

CSRF Token Validation Failures

Symptoms: Form submissions rejected with CSRF error

Solutions:

  1. Verify token in form:

    <?= csrf_field() ?>
    
  2. Check token generation:

    var_dump(csrf_token()); // Should output 64-character string
    
  3. Verify session started:

    $sessionManager->start();
    

Account Locked Out

Symptoms: Cannot login with valid credentials

Solutions:

  1. Check lockout status:

    # Via database
    SELECT locked_until FROM users WHERE username = 'admin';
    
  2. Manual unlock:

    $user->setLockedUntil(null);
    $user->resetFailedLoginAttempts();
    $userRepository->update($user);
    
  3. Wait for lockout expiration (default: 15 minutes)

Remember Me Not Working

Symptoms: User must login every session despite checking "remember me"

Solutions:

  1. Verify remember me enabled:

    auth:
      remember:
        enabled: true
    
  2. Check cookie creation:

    var_dump($_COOKIE['remember_token']);
    
  3. Verify cookie security settings match HTTPS availability

Two-Factor Authentication Support

The User model includes database fields for two-factor authentication support:

Note: While the database schema supports 2FA, the CMS does not currently include a built-in two-factor authentication service implementation. These fields are available for custom 2FA implementations using libraries like:

Additional Resources