๐ Logging, Events & Monitoring๏
Daycry Auth provides two independent logging systems that complement each other:
CodeIgniter Events โ lightweight hooks fired at authentication milestones; listened to in your app code
Database Logs โ persistent records of login attempts, access logs, and rate limiting stored in dedicated tables
๐ Table of Contents๏
CodeIgniter Events๏
Daycry Auth fires CI4 events at key authentication moments. Your application code listens to these events in app/Config/Events.php โ no modifications to the library are needed.
Register Event Listeners๏
<?php
// app/Config/Events.php
use CodeIgniter\Events\Events;
// Runs after a successful login
Events::on('login', static function (object $user): void {
log_message('info', "User {$user->email} logged in.");
});
// Runs after a failed login attempt
Events::on('failedLogin', static function (array $credentials): void {
log_message('warning', "Failed login attempt for: " . ($credentials['email'] ?? 'unknown'));
});
// Runs after logout
Events::on('logout', static function (object $user): void {
log_message('info', "User {$user->email} logged out.");
});
// Runs after successful registration
Events::on('registered', static function (object $user): void {
// Send a welcome email, assign a default group, etc.
service('email')
->setTo($user->email)
->setSubject('Welcome!')
->setMessage("Thanks for signing up!")
->send();
});
Available Events๏
Event |
When Fired |
Arguments |
|---|---|---|
|
Before credentials are checked |
|
|
After successful login |
|
|
After a failed login attempt |
|
|
After logout |
|
|
Before registration is processed |
|
|
After successful registration |
|
|
After password successfully reset |
|
|
After magic link login |
|
|
After successful OAuth login |
|
|
After profile fields resolved from OAuth provider |
|
|
After a successful login flagged as anomalous (when |
|
Listening to Events๏
Security Alert on Multiple Failed Logins๏
Events::on('failedLogin', static function (array $credentials): void {
$email = $credentials['email'] ?? null;
if ($email === null) {
return;
}
// Count recent failures from the auth_logins table
$recentFailures = model(\Daycry\Auth\Models\LoginModel::class)
->where('identifier', $email)
->where('success', 0)
->where('created_at >', date('Y-m-d H:i:s', strtotime('-15 minutes')))
->countAllResults();
if ($recentFailures >= 5) {
// Alert the security team
log_message('critical', "Brute-force suspected for account: {$email}");
}
});
Welcome Email on Registration๏
Events::on('registered', static function (object $user): void {
$emailService = service('email');
$emailService->setTo($user->email)
->setSubject('Welcome to ' . config('App')->appName)
->setMessage(view('emails/welcome', ['user' => $user]))
->send();
});
Audit Trail for Password Resets๏
Events::on('passwordReset', static function (object $user): void {
log_message('notice', "Password reset completed for user ID {$user->id} ({$user->email}).");
// Write to an audit log table
db_connect()->table('audit_log')->insert([
'user_id' => $user->id,
'action' => 'password_reset',
'created_at' => date('Y-m-d H:i:s'),
]);
});
OAuth Login Tracking๏
// Log all OAuth logins with provider name
Events::on('oauth-login', static function (object $user, string $provider): void {
log_message('info', "OAuth login via {$provider} for user {$user->email}");
});
// Sync profile data when fetched from OAuth provider
Events::on('oauth-profile-fetched', static function (object $user, string $provider, array $profileData): void {
log_message('info', "Profile fetched from {$provider}: " . json_encode(array_keys($profileData)));
});
See OAuth 2.0 & Social Login for more details on OAuth events.
Pre-Authentication Events๏
pre-login and pre-register fire before any database check. Listeners can inspect or enrich the data. They cannot cancel the operation directly, but can redirect via redirect().
Log All Login Attempts๏
Events::on('pre-login', static function (array $credentials): void {
$ip = service('request')->getIPAddress();
log_message('debug', "Login attempt for '{$credentials['email']}' from IP {$ip}");
});
Block Specific Domains from Registering๏
Events::on('pre-register', static function (array $data): void {
$email = $data['email'] ?? '';
$domain = substr(strrchr($email, '@'), 1);
$blockedDomains = ['tempmail.com', 'throwaway.email'];
if (in_array($domain, $blockedDomains, true)) {
// Redirect before processing โ effectively cancels registration
redirect()->to('/register')->with('error', 'Disposable email addresses are not allowed.')->send();
exit;
}
});
Suspicious Login Event๏
When AuthSecurity::$suspiciousLoginAlerts = true, every successful login runs SuspiciousLoginDetector and fires the suspicious-login event whenever the IP / User-Agent does not match the userโs recent history.
Listener โ email the user๏
use CodeIgniter\Events\Events;
use CodeIgniter\I18n\Time;
use Daycry\Auth\Entities\User;
Events::on('suspicious-login', static function (User $user, array $flags, string $ip, string $ua): void {
helper('email');
$email = emailer()
->setFrom(setting('Email.fromEmail'), (string) setting('Email.fromName'))
->setTo($user->email)
->setSubject(lang('Auth.suspiciousLoginSubject'))
->setMessage(view('Daycry\\Auth\\Views\\Email\\suspicious_login_alert', [
'user' => $user,
'flags' => $flags,
'ipAddress' => $ip,
'userAgent' => $ua,
'date' => Time::now()->toDateTimeString(),
]));
$email->send(false);
});
Possible flag values๏
Flag |
Meaning |
|---|---|
|
The IP has not appeared in this userโs successful logins for the last 30 days. |
|
The User-Agent has not been seen on any device session for this user. |
The flags list is forward-compatible โ additional signals (geo-IP mismatch, ASN reputation, time-of-day anomaly) can be added without breaking existing listeners.
Every flagged login also writes an
EVENT_SUSPICIOUS_LOGINrow to the audit log โ see Audit Log below.
See Audit & Compliance โ Suspicious Login Detection for the full reference.
Database Logging๏
Enable Activity Logs๏
// app/Config/Auth.php
public bool $enableLogs = true;
When enabled, authentication events are written to the auth_logs table. The log includes the user ID, action type, IP address, and timestamp.
Query Logs๏
// Get recent login events for a user
$logs = model(\Daycry\Auth\Models\LogModel::class)
->where('user_id', $userId)
->orderBy('created_at', 'DESC')
->limit(20)
->findAll();
foreach ($logs as $entry) {
echo "[{$entry->created_at}] {$entry->action} from {$entry->ip_address}" . PHP_EOL;
}
Login Attempt Logging๏
Track all login attempts (success and failure) in the auth_logins table.
Configuration๏
// app/Config/Auth.php
// Options:
// Auth::RECORD_LOGIN_ATTEMPT_NONE (0) - Don't record anything
// Auth::RECORD_LOGIN_ATTEMPT_FAILURE (1) - Only record failures
// Auth::RECORD_LOGIN_ATTEMPT_ALL (2) - Record everything (default)
public int $recordLoginAttempt = \Daycry\Auth\Config\Auth::RECORD_LOGIN_ATTEMPT_ALL;
What Gets Stored๏
Column |
Description |
|---|---|
|
IP address of the request |
|
The email (or username) used |
|
Type of credential (e.g., |
|
|
|
Browser/client string |
|
Timestamp |
Query Login Attempts๏
use Daycry\Auth\Models\LoginModel;
$loginModel = model(LoginModel::class);
// All failures in the last hour from a specific IP
$suspiciousAttempts = $loginModel
->where('ip_address', '203.0.113.42')
->where('success', 0)
->where('created_at >', date('Y-m-d H:i:s', strtotime('-1 hour')))
->countAllResults();
Failed Attempt Blocking๏
Daycry Auth can automatically block an IP address that fails login too many times in a row.
Configuration๏
// app/Config/Auth.php
// Enable IP-based failed attempt blocking
public bool $enableInvalidAttempts = true;
// Maximum failed attempts before blocking
public int $maxAttempts = 10;
// How long to block the IP (seconds)
public int $timeBlocked = 3600; // 1 hour
When an IP address exceeds $maxAttempts, all subsequent requests from that IP receive a โtoo many attemptsโ error until $timeBlocked seconds have passed.
Note: This is IP-level blocking. For per-user account lockout, see the section below.
Per-User Account Lockout๏
Independent of IP blocking, you can lock individual user accounts after too many failed password attempts. This is stored on the users table (failed_login_count, locked_until).
Configuration๏
// app/Config/Auth.php
// Maximum failed password attempts before locking the account
// Set to 0 to disable per-user lockout
public int $userMaxAttempts = 5;
// How long to lock the account (seconds)
public int $userLockoutTime = 3600; // 1 hour
How It Works๏
A user fails to log in โ
failed_login_countincrementsWhen
failed_login_count >= userMaxAttemptsโlocked_untilis setAny login attempt before
locked_untilreturns an error with minutes remainingAfter lockout expires โ counter resets automatically on next attempt
After a successful login โ counter resets to 0
Unlocking a User Manually๏
// In an admin controller or command
$user = model(\Daycry\Auth\Models\UserModel::class)->find($userId);
model(\Daycry\Auth\Models\UserModel::class)->update($user->id, [
'failed_login_count' => 0,
'locked_until' => null,
]);
Rate Limiting๏
Rate limiting controls how many requests a client can make in a time window, independent of login failures.
Configuration๏
// app/Config/Auth.php
// How to identify the rate limit subject
// Options: 'IP_ADDRESS', 'USER', 'METHOD_NAME', 'ROUTED_URL'
public string $limitMethod = 'IP_ADDRESS';
// Maximum requests per window
public int $requestLimit = 60;
// Time window (seconds)
public int $timeLimit = MINUTE;
Apply Rate Limiting to Routes๏
// app/Config/Routes.php
// Limit all auth routes
$routes->group('auth', ['filter' => 'auth-rates'], static function ($routes) {
$routes->post('login', 'LoginController::loginAction');
$routes->post('register', 'RegisterController::registerAction');
});
// app/Config/Filters.php
public array $aliases = [
'auth-rates' => \Daycry\Auth\Filters\AuthRatesFilter::class,
];
Audit Log (auth_audit_logs)๏
A second log table โ auth_audit_logs โ captures account-level events that need long-term traceability, distinct from request-level activity (auth_logs) and login attempts (auth_logins):
Table |
Granularity |
Use |
|---|---|---|
|
Per request |
Request-level activity log (controller, URI, response code) |
|
Per login attempt |
Successful + failed login attempts |
|
Per account event |
Sensitive account changes (2FA, password, role, lockout) |
Built-in events๏
The \Daycry\Auth\Services\AuditLogger service records 22 canonical event types โ see the Audit & Compliance reference for the full list. Highlights:
TOTP enable/disable, admin reset
Password change, password reset
User lockout / unlock
Group / permission grant / revoke
Token / refresh-token revoke
Trusted device added
Suspicious login
User anonymization (GDPR)
Recording your own events๏
use Daycry\Auth\Services\AuditLogger;
(new AuditLogger())->record(
AuditLogger::EVENT_PASSWORD_CHANGED,
userId: $user->id,
metadata: ['source' => 'profile_form'],
);
Querying๏
# CLI
php spark auth:audit --user=alice@example.com --since=30d
php spark auth:audit --type=login.suspicious --limit=200
// Code
use Daycry\Auth\Models\AuditLogModel;
$audit = model(AuditLogModel::class);
$entries = $audit->recentForUser($userId, 50);
foreach ($entries as $entry) {
echo $entry->event_type . ' at ' . $entry->created_at . "\n";
var_dump($entry->getMetadata());
}
Failures inside
AuditLogger::record()are caught and logged atwarningโ audit failure must never break the user-facing flow.
See Audit & Compliance for the full feature documentation.
Monitoring & Querying Logs๏
Dashboard Statistics๏
use Daycry\Auth\Models\LoginModel;
$loginModel = model(LoginModel::class);
$stats = [
'total_logins_today' => $loginModel
->where('success', 1)
->where('created_at >', date('Y-m-d 00:00:00'))
->countAllResults(),
'failed_today' => $loginModel
->where('success', 0)
->where('created_at >', date('Y-m-d 00:00:00'))
->countAllResults(),
'unique_ips_today' => $loginModel
->select('ip_address')
->where('created_at >', date('Y-m-d 00:00:00'))
->distinct()
->countAllResults(),
];
Find Locked Accounts๏
$lockedUsers = model(\Daycry\Auth\Models\UserModel::class)
->where('locked_until >', date('Y-m-d H:i:s'))
->findAll();
foreach ($lockedUsers as $user) {
echo "{$user->email} โ locked until {$user->locked_until}" . PHP_EOL;
}
CI4 Log Files๏
Beyond the database, standard CI4 log files in writable/logs/ capture log messages written with log_message():
Events::on('failedLogin', static function (array $credentials): void {
log_message('warning', 'Failed login: ' . json_encode($credentials));
});
Set the log threshold in app/Config/Logger.php:
public int $threshold = 4; // 0=disabled, 1=emergency, 4=warning+, 7=info+, 8=debug
๐ See also:
Configuration โ All logging configuration options
Per-User Lockout & Password Reset โ Security features
Filters โ
auth-ratesfilter