โš™๏ธ 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

app/Config/Auth.php

Daycry\Auth\Config\Auth

Authenticators, actions, views, tables, routes, session/remember-me

app/Config/AuthSecurity.php

Daycry\Auth\Config\AuthSecurity

Passwords, lockout, rate-limit, token lifetimes, TOTP issuer, permission cache

app/Config/AuthOAuth.php

Daycry\Auth\Config\AuthOAuth

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.



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

Authorization Cache๏ƒ

File: app/Config/AuthSecurity.php

Avoid repeated database queries for group/permission checks in production:

public bool $permissionCacheEnabled = false; // true = use CI4 cache
public int  $permissionCacheTTL     = 300;   // Cache lifetime in seconds

The cache is automatically invalidated when you call addGroup(), removeGroup(), addPermission(), or removePermission(). See Authorization โ€” Permission Cache.


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

Email2FA

Sends a 6-digit code to the userโ€™s email; required to complete login

EmailActivator

Sends an activation link; user must click before first login

Totp2FA

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

clientId, clientSecret, redirectUri

Standard OAuth credentials (required)

scopes

OAuth scopes to request

fields

Extra profile fields to fetch after login

fieldsEndpoint

Custom API URL for profile fields (GenericProfileResolver)

profileResolver

Custom resolver class (must implement ProfileResolverInterface)

tenant

Azure-only: 'common', 'organizations', or tenant GUID

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