โ๏ธ Configuration Reference๏
This guide covers every configuration option available in Daycry Auth.
The Configuration Files๏
Configuration is split across three files to keep concerns separate. After running php spark auth:setup, each is published to app/Config/:
File |
Base class |
Contains |
|---|---|---|
|
|
Authenticators, actions, views, tables, routes, session/remember-me |
|
|
Passwords, lockout, rate-limit, token lifetimes, TOTP issuer, permission cache |
|
|
OAuth provider definitions |
Override only what you need in each file:
// app/Config/Auth.php
namespace Config;
use Daycry\Auth\Config\Auth as BaseAuth;
class Auth extends BaseAuth { /* ... */ }
// app/Config/AuthSecurity.php
namespace Config;
use Daycry\Auth\Config\AuthSecurity as BaseAuthSecurity;
class AuthSecurity extends BaseAuthSecurity { /* ... */ }
// app/Config/AuthOAuth.php
namespace Config;
use Daycry\Auth\Config\AuthOAuth as BaseAuthOAuth;
class AuthOAuth extends BaseAuthOAuth { /* ... */ }
Database๏
Database Group๏
// Use the default database connection (recommended)
public ?string $DBGroup = null;
// Use a dedicated auth database
public ?string $DBGroup = 'auth_db';
Table Names๏
Customize any table name to avoid conflicts with your existing schema:
public array $tables = [
'users' => 'users', // Main users table
'identities' => 'auth_users_identities', // Passwords, tokens, TOTP secrets
'logins' => 'auth_logins', // Login attempt log
'remember_tokens' => 'auth_remember_tokens', // "Remember me" cookies
'groups' => 'auth_groups', // Group definitions
'groups_users' => 'auth_groups_users', // User โ group assignments
'permissions' => 'auth_permissions', // Permission definitions
'permissions_users' => 'auth_permissions_users', // User direct permissions
'permissions_groups' => 'auth_permissions_groups', // Group permissions
'logs' => 'auth_logs', // Activity log
'apis' => 'auth_apis', // API registry (discovery)
'controllers' => 'auth_controllers', // Controller registry
'endpoints' => 'auth_endpoints', // Endpoint registry
'attempts' => 'auth_attempts', // IP-based failed attempts
'rates' => 'auth_rates', // Rate limit counters
'device_sessions' => 'auth_device_sessions', // Active device sessions
];
Authenticators๏
Available Authenticators๏
use Daycry\Auth\Authentication\Authenticators\AccessToken;
use Daycry\Auth\Authentication\Authenticators\Session;
use Daycry\Auth\Authentication\Authenticators\JWT;
use Daycry\Auth\Authentication\Authenticators\Guest;
public array $authenticators = [
'access_token' => AccessToken::class,
'session' => Session::class,
'jwt' => JWT::class,
'guest' => Guest::class,
];
// The authenticator used when you call auth() with no argument
public string $defaultAuthenticator = 'session';
JWT Adapter๏
use Daycry\Auth\Authentication\JWT\Adapters\DaycryJWTAdapter;
public string $jwtAdapter = DaycryJWTAdapter::class;
Authentication Chain๏
The chain filter tries authenticators in order and stops at the first success:
public array $authenticationChain = [
'session', // Try session first (web users)
'access_token', // Then access tokens (API keys)
'jwt', // Finally JWT (Bearer tokens)
];
Session Authenticator๏
public array $sessionConfig = [
'field' => 'user', // Session key name
'allowRemembering' => true, // Enable "remember me"
'rememberCookieName' => 'remember', // Cookie name for remember me
'rememberLength' => 30 * DAY, // Remember me duration
// Track active device sessions (login from each device/browser)
'trackDeviceSessions' => false, // Set true to enable
];
User Settings๏
// Allow public registration
public bool $allowRegistration = true;
// Default group assigned to every new user (must exist in auth_groups table)
public string $defaultGroup = 'user';
// Class responsible for finding users in the database
public string $userProvider = \Daycry\Auth\Models\UserModel::class;
// Which fields can be used to log in
public array $validFields = [
'email',
// 'username', // Uncomment to allow username login
];
Password Settings๏
File:
app/Config/AuthSecurity.php
// Minimum password length
public int $minimumPasswordLength = 8;
// Validators that run on every password
public array $passwordValidators = [
\Daycry\Auth\Authentication\Passwords\CompositionValidator::class, // Requires mixed chars
\Daycry\Auth\Authentication\Passwords\NothingPersonalValidator::class, // No personal info
\Daycry\Auth\Authentication\Passwords\DictionaryValidator::class, // No dictionary words
// \Daycry\Auth\Authentication\Passwords\PwnedValidator::class, // HaveIBeenPwned check
];
// HaveIBeenPwned API URL and HTTP timeouts (used when PwnedValidator is enabled).
// Short timeouts prevent registration / password-change flows from hanging when
// the API is slow or unreachable.
public string $pwnedPasswordsApiUrl = 'https://api.pwnedpasswords.com/';
public float $pwnedPasswordsConnectTimeout = 1.0; // seconds
public float $pwnedPasswordsTimeout = 3.0; // seconds
// Hash algorithm (PASSWORD_DEFAULT, PASSWORD_BCRYPT, PASSWORD_ARGON2I, PASSWORD_ARGON2ID)
public string $hashAlgorithm = PASSWORD_DEFAULT;
public int $hashCost = 12; // bcrypt cost (4โ31)
// argon2 options
public int $hashMemoryCost = 65536; // KB
public int $hashTimeCost = 4; // Iterations
public int $hashThreads = 1; // Threads
// Personal data that passwords should not resemble
public array $personalFields = ['firstname', 'lastname', 'phone'];
// 0โ100: max allowed similarity between password and personal fields (0 = disabled)
public int $maxSimilarity = 50;
Password Reset๏
File:
app/Config/AuthSecurity.php
// How long a password reset token remains valid
public int $passwordResetLifetime = HOUR; // Default: 1 hour
The reset flow is handled by PasswordResetController. Users request a reset link, receive it by email, and click it within this window. See Controllers for the full setup.
Per-User Account Lockout๏
File:
app/Config/AuthSecurity.php
Independent of IP-based blocking, this locks an individual account after too many failed password attempts:
// Max failed attempts before locking the account (0 = disabled)
public int $userMaxAttempts = 5;
// How long to keep the account locked (seconds)
public int $userLockoutTime = 3600; // 1 hour
When the lockout expires, the counter resets automatically on the next login attempt. See Logging & Monitoring for details and admin unlock instructions.
Magic Links๏
File:
app/Config/AuthSecurity.php
public bool $allowMagicLinkLogins = true;
public int $magicLinkLifetime = HOUR; // Token validity in seconds
Access Tokens๏
File:
app/Config/AuthSecurity.php
public bool $accessTokenEnabled = false;
// How long an unused access token remains valid
public int $unusedAccessTokenLifetime = YEAR;
// Force both API key AND session authentication (strict mode)
public bool $strictApiAndAuth = false;
// Minimum seconds between two consecutive `last_used_at` writes for the
// same access token. Prevents one DB UPDATE per request on high-traffic
// API tokens. Set to 0 to disable throttling (write on every request).
public int $tokenLastUsedThrottle = 60;
// Request headers for each authenticator
public array $authenticatorHeader = [
'access_token' => 'X-API-KEY',
'jwt' => 'Authorization',
];
JWT Refresh Tokens๏
File:
app/Config/AuthSecurity.php
When using JwtController for stateless JWT authentication, refresh tokens allow issuing new access tokens without re-entering credentials:
// How long a JWT refresh token remains valid
public int $jwtRefreshLifetime = 30 * DAY; // Default: 30 days
Refresh tokens are stored in auth_users_identities (type: jwt_refresh) and are one-time use (rotated on each refresh). See Authentication โ JWT Refresh Tokens.
Logging & Monitoring๏
File:
app/Config/AuthSecurity.php
Activity Logs๏
// Write auth events to auth_logs table
public bool $enableLogs = false;
// Login attempt recording
// 0 = none, 1 = failures only, 2 = all attempts
public int $recordLoginAttempt = 2;
IP-Based Failed Attempt Blocking๏
public bool $enableInvalidAttempts = false;
public int $maxAttempts = 10; // Attempts before blocking
public int $timeBlocked = 3600; // Seconds
Rate Limiting๏
// Identify rate limit subject: 'IP_ADDRESS', 'USER', 'METHOD_NAME', 'ROUTED_URL'
public string $limitMethod = 'IP_ADDRESS';
public int $requestLimit = 60; // Max requests per window
public int $timeLimit = MINUTE; // Window size in seconds
Views๏
Override any built-in view with your own:
public array $views = [
// Core
'login' => '\Daycry\Auth\Views\login',
'register' => '\Daycry\Auth\Views\register',
'layout' => '\Daycry\Auth\Views\layout',
// Email 2FA
'action_email_2fa' => '\Daycry\Auth\Views\email_2fa_show',
'action_email_2fa_verify' => '\Daycry\Auth\Views\email_2fa_verify',
'action_email_2fa_email' => '\Daycry\Auth\Views\Email\email_2fa_email',
// Email Activation
'action_email_activate_show' => '\Daycry\Auth\Views\email_activate_show',
'action_email_activate_email' => '\Daycry\Auth\Views\Email\email_activate_email',
// Magic Link
'magic-link-login' => '\Daycry\Auth\Views\magic_link_form',
'magic-link-message' => '\Daycry\Auth\Views\magic_link_message',
'magic-link-email' => '\Daycry\Auth\Views\Email\magic_link_email',
// Password Reset
'password-reset-request' => '\Daycry\Auth\Views\password_reset_request',
'password-reset-message' => '\Daycry\Auth\Views\password_reset_message',
'password-reset-form' => '\Daycry\Auth\Views\password_reset_form',
'password-reset-email' => '\Daycry\Auth\Views\Email\password_reset_email',
// Force Password Reset
'force-password-reset' => '\Daycry\Auth\Views\force_password_reset',
// Email Change Confirmation
'email-change-email' => '\Daycry\Auth\Views\Email\email_change_email',
];
Redirects๏
public array $redirects = [
'register' => '/', // After successful registration
'login' => '/', // After successful login
'logout' => 'login', // After logout (route name or URL)
'force_reset' => '/', // After forced password reset
'permission_denied' => '/', // GroupFilter/PermissionFilter denial
'group_denied' => '/', // GroupFilter denial
];
Override with a method for dynamic redirects:
public function loginRedirect(): string
{
$user = auth()->user();
return $user->inGroup('admin') ? site_url('admin') : site_url('dashboard');
}
Routes๏
All auth routes are defined here. Each entry is [method, uri, controller::method, routeName?]:
public array $routes = [
'register' => [
['get', 'register', 'RegisterController::registerView', 'register'],
['post', 'register', 'RegisterController::registerAction'],
],
'login' => [
['get', 'login', 'LoginController::loginView', 'login'],
['post', 'login', 'LoginController::loginAction'],
],
'magic-link' => [
['get', 'login/magic-link', 'MagicLinkController::loginView', 'magic-link'],
['post', 'login/magic-link', 'MagicLinkController::loginAction'],
['get', 'login/verify-magic-link', 'MagicLinkController::verify', 'verify-magic-link'],
],
'logout' => [
['get', 'logout', 'LoginController::logoutAction', 'logout'],
],
'auth-actions' => [
['get', 'auth/a/show', 'ActionController::show', 'auth-action-show'],
['post', 'auth/a/handle', 'ActionController::handle', 'auth-action-handle'],
['post', 'auth/a/verify', 'ActionController::verify', 'auth-action-verify'],
],
// Password Reset
'password-reset' => [
['get', 'password-reset', 'PasswordResetController::requestView', 'password-reset'],
['post', 'password-reset', 'PasswordResetController::requestAction'],
['get', 'password-reset/message', 'PasswordResetController::messageView', 'password-reset-message'],
['get', 'password-reset/(:any)', 'PasswordResetController::resetView', 'password-reset-form'],
['post', 'password-reset/(:any)', 'PasswordResetController::resetAction'],
],
// Force Password Reset
'force-reset' => [
['get', 'force-reset', 'ForcePasswordResetController::showView', 'force-reset'],
['post', 'force-reset', 'ForcePasswordResetController::resetAction'],
],
// JWT (stateless, for APIs)
'jwt' => [
['post', 'auth/jwt/login', 'JwtController::login', 'jwt-login'],
['post', 'auth/jwt/refresh', 'JwtController::refresh', 'jwt-refresh'],
['post', 'auth/jwt/logout', 'JwtController::logout', 'jwt-logout'],
],
];
Post-Authentication Actions๏
Run additional verification steps after login or registration:
use Daycry\Auth\Authentication\Actions\Email2FA;
use Daycry\Auth\Authentication\Actions\EmailActivator;
use Daycry\Auth\Authentication\Actions\Totp2FA;
public array $actions = [
'register' => EmailActivator::class, // Require email confirmation on signup
'login' => Email2FA::class, // Require email 2FA on login
// 'login' => Totp2FA::class, // Or: require TOTP on login (per-user)
];
Action |
What It Does |
|---|---|
|
Sends a 6-digit code to the userโs email; required to complete login |
|
Sends an activation link; user must click before first login |
|
Requires a valid TOTP code (only for users who have enrolled) |
Field Validation Rules๏
public array $usernameValidationRules = [
'label' => 'Auth.username',
'rules' => [
'required',
'max_length[30]',
'min_length[3]',
'regex_match[/\A[a-zA-Z0-9\.]+\z/]',
],
];
public array $emailValidationRules = [
'label' => 'Auth.email',
'rules' => [
'required',
'max_length[254]',
'valid_email',
],
];
OAuth Providers๏
File:
app/Config/AuthOAuth.php
public array $providers = [
'google' => [
'clientId' => env('OAUTH_GOOGLE_CLIENT_ID'),
'clientSecret' => env('OAUTH_GOOGLE_CLIENT_SECRET'),
'redirectUri' => 'https://yourapp.com/oauth/google/callback',
'scopes' => ['openid', 'email', 'profile'],
],
'github' => [
'clientId' => env('OAUTH_GITHUB_CLIENT_ID'),
'clientSecret' => env('OAUTH_GITHUB_CLIENT_SECRET'),
'redirectUri' => 'https://yourapp.com/oauth/github/callback',
'scopes' => ['user:email'],
],
'azure' => [
'clientId' => env('OAUTH_AZURE_CLIENT_ID'),
'clientSecret' => env('OAUTH_AZURE_CLIENT_SECRET'),
'redirectUri' => 'https://yourapp.com/oauth/azure/callback',
'tenant' => 'common',
'scopes' => ['openid', 'profile', 'email', 'offline_access', 'User.Read'],
'fields' => ['department', 'jobTitle'], // Extra profile data from Graph API
],
// Generic provider with custom profile resolver:
// 'my_provider' => [
// 'clientId' => env('MY_PROVIDER_CLIENT_ID'),
// 'clientSecret' => env('MY_PROVIDER_CLIENT_SECRET'),
// 'redirectUri' => 'https://yourapp.com/oauth/my_provider/callback',
// 'fields' => ['role', 'team'],
// 'fieldsEndpoint' => 'https://api.example.com/userinfo',
// 'profileResolver' => \App\OAuth\MyCustomResolver::class,
// ],
];
Provider Configuration Keys๏
Key |
Description |
|---|---|
|
Standard OAuth credentials (required) |
|
OAuth scopes to request |
|
Extra profile fields to fetch after login |
|
Custom API URL for profile fields (GenericProfileResolver) |
|
Custom resolver class (must implement |
|
Azure-only: |
See OAuth 2.0 & Social Login for full setup instructions, profile resolvers, OAuth events, and the OAuthTokenRepository.
Sessions๏
File:
app/Config/Auth.php
// Web session config (existing)
public array $sessionConfig = [
'field' => 'user',
'allowRemembering' => true,
'rememberCookieName' => 'remember',
'rememberLength' => 30 * DAY,
'trackDeviceSessions' => true,
];
// Per-user concurrent session limit. When > 0, each new login terminates
// the oldest active sessions until at most this many remain.
// Requires sessionConfig.trackDeviceSessions = true.
// 0 = unlimited (default).
public int $maxConcurrentSessions = 0;
See Device Sessions โ Concurrent Limit for behaviour and edge cases.
Trusted Devices (2FA bypass)๏
File:
app/Config/AuthSecurity.php
// When > 0, users can tick "Trust this device" during 2FA. Successful
// verifications mark the current device session as trusted for this many
// seconds; subsequent logins from the same device skip the 2FA challenge
// until the timestamp expires.
// 0 = feature disabled (always require 2FA when configured).
public int $trustedDeviceLifetime = 30 * DAY;
See TOTP โ Trust This Device for the user flow.
Compliance & Observability๏
File:
app/Config/AuthSecurity.php
These four settings are independent โ enable only the ones you need. See Audit & Compliance for the full reference.
// Recheck the just-verified password against HIBP on every login.
// Sets force_reset = 1 on a hit. Adds 1 outbound HTTPS call per login.
public bool $recheckPwnedOnLogin = false;
// Compare each successful login's IP / User-Agent against the user's last
// 30 days of history. On anomalies fires the `suspicious-login` event +
// audit entry. Wire your own listener for the email/Slack/push delivery.
public bool $suspiciousLoginAlerts = false;
// Number of recent password hashes retained per user. The HistoryValidator
// rejects new passwords matching any retained hash.
// 0 = feature disabled.
public int $passwordHistorySize = 0;
// Force a password reset once `password_changed_at` is older than this.
// Apply the `password-age` filter on protected route groups to enforce.
// 0 = passwords never expire.
public int $passwordMaxAge = 0;
When passwordHistorySize > 0, also extend the validator chain:
public array $passwordValidators = [
\Daycry\Auth\Authentication\Passwords\CompositionValidator::class,
\Daycry\Auth\Authentication\Passwords\NothingPersonalValidator::class,
\Daycry\Auth\Authentication\Passwords\DictionaryValidator::class,
\Daycry\Auth\Authentication\Passwords\HistoryValidator::class,
];
Common Presets๏
Web Application๏
// app/Config/Auth.php
public bool $allowRegistration = true;
public string $defaultAuthenticator = 'session';
public array $actions = ['register' => null, 'login' => null];
// app/Config/AuthSecurity.php
public bool $allowMagicLinkLogins = true;
public int $userMaxAttempts = 5;
public int $userLockoutTime = 1800; // 30 minutes
API (Stateless)๏
// app/Config/Auth.php
public string $defaultAuthenticator = 'jwt';
public array $authenticationChain = ['jwt', 'access_token'];
// app/Config/AuthSecurity.php
public bool $accessTokenEnabled = true;
public int $jwtRefreshLifetime = 30 * DAY;
public int $recordLoginAttempt = 2;
public int $tokenLastUsedThrottle = 60; // seconds
High-Security (production)๏
// app/Config/Auth.php
public array $actions = ['login' => \Daycry\Auth\Authentication\Actions\Totp2FA::class];
public int $maxConcurrentSessions = 5;
// app/Config/AuthSecurity.php
public int $minimumPasswordLength = 12;
public bool $enableInvalidAttempts = true;
public int $maxAttempts = 5;
public int $timeBlocked = 3600;
public int $userMaxAttempts = 3;
public int $userLockoutTime = 7200; // 2 hours
public bool $permissionCacheEnabled = true;
public int $totpWindow = 1;
public int $trustedDeviceLifetime = 30 * DAY;
public bool $suspiciousLoginAlerts = true;
Compliance (SOC 2 / ISO 27001)๏
// app/Config/AuthSecurity.php
public int $passwordHistorySize = 5; // no reuse of last 5
public int $passwordMaxAge = 90 * DAY; // rotation policy
public bool $recheckPwnedOnLogin = true; // ongoing HIBP check
public bool $suspiciousLoginAlerts = true;
public bool $enableLogs = true;
public int $recordLoginAttempt = AuthSecurity::RECORD_LOGIN_ATTEMPT_ALL;
public array $passwordValidators = [
\Daycry\Auth\Authentication\Passwords\CompositionValidator::class,
\Daycry\Auth\Authentication\Passwords\NothingPersonalValidator::class,
\Daycry\Auth\Authentication\Passwords\DictionaryValidator::class,
\Daycry\Auth\Authentication\Passwords\PwnedValidator::class,
\Daycry\Auth\Authentication\Passwords\HistoryValidator::class,
];
Then enforce rotation on protected routes:
// app/Config/Routes.php
$routes->group('app', ['filter' => 'session,password-age'], static function ($routes) {
$routes->get('/dashboard', 'Dashboard::index');
});
Dynamic Configuration๏
Override methods for runtime-computed values:
class Auth extends BaseAuth
{
public function loginRedirect(): string
{
if (auth()->user()?->inGroup('admin')) {
return site_url('admin/dashboard');
}
return site_url('dashboard');
}
public function __construct()
{
parent::__construct();
// Load secrets from environment
if (isset($_ENV['JWT_SECRET'])) {
// Configure JWT via the jwt library's config
}
}
}
๐ Next: Authentication โ Use each authenticator