⚠️ 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.
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.
The authentication system consists of the following classes:
Neuron\Cms\Services\Auth\Authentication): Authentication orchestration, login/logout, credential verificationNeuron\Cms\Auth\SessionManager): Session lifecycle management, secure session configurationNeuron\Cms\Auth\PasswordHasher): Password hashing using Argon2id/Bcrypt, strength validationNeuron\Cms\Services\Auth\CsrfToken): CSRF token generation and validationRoute protection is implemented via filters:
Neuron\Cms\Auth\Filters\AuthenticationFilter): Protects routes requiring authenticationNeuron\Cms\Auth\Filters\MemberAuthenticationFilter): Member-specific route protectionNeuron\Cms\Auth\Filters\CsrfFilter): CSRF token validation for state-changing requestsGlobal helper functions provide convenient authentication access (defined in src/Cms/Auth/helpers.php):
auth(): Get authenticated useruser(): Alias for auth()user_id(): Get authenticated user IDis_logged_in(): Check if authenticatedis_guest(): Check if not authenticatedis_admin(): Check admin roleis_editor(): Check editor roleis_author(): Check author rolehas_role($role): Check specific rolecsrf_token(): Get CSRF tokencsrf_field(): Generate CSRF hidden inputAuthentication configuration is defined in config/auth.yaml.
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:
cookie_httponly: Prevents XSS attacks by blocking JavaScript accesscookie_secure: Requires HTTPS when enabled; auto-detected by SessionManagercookie_samesite: Provides CSRF protection (Lax recommended for most cases)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:
argon2id: Preferred algorithm (most secure, resistant to GPU attacks)bcrypt: Fallback if Argon2id unavailablePasswordHasher automatically selects Argon2id if available, otherwise falls back to Bcrypt.
auth:
remember:
enabled: true
lifetime: 43200 # 30 days in minutes
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.
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 IDs are regenerated on login to prevent session fixation attacks:
$sessionManager->regenerate();
This is automatically handled by AuthManager during the login process.
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' );
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 );
PasswordHasher validates passwords against configured requirements:
$hasher = new PasswordHasher();
if( !$hasher->meetsRequirements( $password ) )
{
// Password does not meet strength requirements
}
Requirements checked:
require_uppercase: true)require_lowercase: true)require_numbers: true)require_special_chars: true)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.
The complete login workflow:
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 clears session and remember tokens:
$authentication->logout();
This performs:
if( $authentication->check() )
{
// User is authenticated
$user = $authentication->user();
}
Authentication checks both session and remember token for authentication.
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.
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...">
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.
Regenerate token after sensitive operations:
$csrfToken->regenerate();
Authentication tracks failed login attempts per user:
// Failed login increments counter
$user->incrementFailedLoginAttempts();
// Successful login resets counter
$user->resetFailedLoginAttempts();
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).
if ($user->isLockedOut()) {
// Account is currently locked
}
Lockout automatically expires after the duration elapses.
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 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 );
}
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.
The system supports four predefined roles (defined in UserRole enum):
UserRole::Admin: Full system accessUserRole::Editor: Content management accessUserRole::Author: Own content managementUserRole::Subscriber: Read-only accessUsing 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
}
Register AuthenticationFilter in route configuration:
admin_dashboard:
method: GET
route: /admin/dashboard
controller: Neuron\Cms\Controllers\Admin\Dashboard@index
filter: auth
In application bootstrap:
use Neuron\Cms\Auth\Filters\AuthenticationFilter;
$authFilter = new AuthenticationFilter( $authentication, '/login' );
$router->registerFilter( 'auth', $authFilter );
AuthenticationFilter stores the intended URL and redirects after login:
/admin/dashboard → /login?redirect=/admin/dashboard → /admin/dashboard
Apply multiple filters to a route:
admin_users:
method: GET
route: /admin/users
controller: Neuron\Cms\Controllers\Admin\Users@index
filter: auth,csrf
Bypass credential verification and log user in directly:
$user = $userRepository->findById( 1 );
$authentication->login( $user, $remember = false );
// Using Authentication
$user = $authentication->user();
// Using helper function
$user = auth();
// Using alternative helper
$user = user();
All methods return null if not authenticated.
$userId = user_id(); // Returns int or null
// Check if authenticated
if (is_logged_in()) {
// User is authenticated
}
// Check if guest
if (is_guest()) {
// User is not authenticated
}
Always use HTTPS in production:
auth:
session:
cookie_secure: true
Without HTTPS, session cookies can be intercepted.
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
Enforce strong passwords:
auth:
passwords:
min_length: 12
require_uppercase: true
require_lowercase: true
require_numbers: true
require_special_chars: true
Balance security and usability:
auth:
throttle:
max_attempts: 3 # Stricter limit
lockout_duration: 30 # Longer lockout
Symptoms: User logged out immediately after login
Solutions:
Check cookie settings:
cookie_secure: false for HTTP (development)Check session storage:
# Verify session directory writable
ls -la /tmp # or session.save_path
Check SameSite setting:
Lax for general compatibilityNone only with Secure flag for cross-siteSymptoms: Form submissions rejected with CSRF error
Solutions:
Verify token in form:
<?= csrf_field() ?>
Check token generation:
var_dump(csrf_token()); // Should output 64-character string
Verify session started:
$sessionManager->start();
Symptoms: Cannot login with valid credentials
Solutions:
Check lockout status:
# Via database
SELECT locked_until FROM users WHERE username = 'admin';
Manual unlock:
$user->setLockedUntil(null);
$user->resetFailedLoginAttempts();
$userRepository->update($user);
Wait for lockout expiration (default: 15 minutes)
Symptoms: User must login every session despite checking "remember me"
Solutions:
Verify remember me enabled:
auth:
remember:
enabled: true
Check cookie creation:
var_dump($_COOKIE['remember_token']);
Verify cookie security settings match HTTPS availability
The User model includes database fields for two-factor authentication support:
two_factor_secret: Field for storing TOTP secret (nullable)two_factor_recovery_codes: Field for storing recovery codes (nullable)two_factor_enabled_at: Timestamp field (nullable)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: