Skip to main content
Every stage of the authentication lifecycle dispatches events you can listen to for auditing, SIEM attribution, side effects, and real-time monitoring. The package fires Laravel’s standard auth events so your existing listeners keep working, and adds custom events for the contextual concepts — principal assignment, device binding, and refresh rotation — that Laravel’s built-in events do not cover.

Standard Laravel events

These events are dispatched on both the JWT bearer and HTTP Basic paths:
EventFired when
Illuminate\Auth\Events\AttemptingBearer, refresh, or credential attempt starts
Illuminate\Auth\Events\ValidatedSuccessful login path about to bind context
Illuminate\Auth\Events\AuthenticatedIdentity bound to the guard
Illuminate\Auth\Events\LoginFull lifecycle complete
Illuminate\Auth\Events\FailedAny bearer, refresh, or credential rejection

Custom events

EventFired when
SineMacula\Laravel\Authentication\Events\PrincipalAssignedPrincipal resolved and bound to the guard
SineMacula\Laravel\Authentication\Events\DeviceAuthenticatedDevice hydrated and bound to the guard
SineMacula\Laravel\Authentication\Events\RefreshedRefresh exchange completed successfully
SineMacula\Laravel\Authentication\Events\RefreshFailedRefresh exchange failed

PrincipalAssigned

Dispatched immediately after the principal resolver binds a principal to a guard. Carries the guard name and the resolved principal instance.
use SineMacula\Laravel\Authentication\Events\PrincipalAssigned;

public function handle(PrincipalAssigned $event): void
{
    // $event->guard     — the guard name (e.g. 'api')
    // $event->principal — the resolved Principal instance
}

DeviceAuthenticated

Dispatched when a device is hydrated from the devices table and bound to the guard during a bearer or refresh request. Carries the guard name and the device instance. Listeners on this event may persist request metadata — such as last IP address or user-agent — during the authentication lifecycle.
use SineMacula\Laravel\Authentication\Events\DeviceAuthenticated;

public function handle(DeviceAuthenticated $event): void
{
    // $event->guard  — the guard name
    // $event->device — the authenticated Device instance

    $event->device->update([
        'last_ip'         => request()->ip(),
        'last_user_agent' => request()->userAgent(),
    ]);
}

Refreshed

Dispatched after a successful refresh-token exchange. Implements ShouldDispatchAfterCommit, so it fires only after the rotation database transaction commits. Carries the full contextual surface so activity-log consumers can attribute the refresh without a second round-trip through the guard.
use SineMacula\Laravel\Authentication\Events\Refreshed;

public function handle(Refreshed $event): void
{
    // $event->guard     — the guard name
    // $event->identity  — the Identity whose session was refreshed
    // $event->principal — the Principal bound on the refreshed guard
    // $event->device    — the Device whose rotation key was rotated
}

RefreshFailed

Dispatched whenever a refresh-token exchange fails. Carries a RefreshFailureReason backed enum so SIEM consumers can count and alert on failure modes without scraping log messages.
use SineMacula\Laravel\Authentication\Events\RefreshFailed;
use SineMacula\Laravel\Authentication\Events\Enums\RefreshFailureReason;

public function handle(RefreshFailed $event): void
{
    // $event->guard    — the guard name
    // $event->reason   — a RefreshFailureReason enum case
    // $event->deviceId — device ID from the token, when parseable; null otherwise

    if ($event->reason === RefreshFailureReason::ROTATION_REUSE) {
        // Concurrent rotation or replay detected — device has been revoked.
        // Consider alerting your security team.
    }
}

RefreshFailureReason reason codes

RefreshFailed carries a RefreshFailureReason backed enum. Every failure path dispatches a distinct reason code so you can attribute events without ambiguity:
ReasonValueMeaning
TOKEN_INVALIDtoken_invalidDecode, expiry, typ, iss, or aud failure — the refresh token itself is malformed or expired
DEVICE_UNKNOWNdevice_unknownThe device ID in the token did not resolve to a record in the devices table
ROTATION_MISMATCHrotation_mismatchThe rotation digest did not match the stored refresh key — stale or tampered token
ROTATION_REUSErotation_reuseReplay or concurrent rotation detected; the device has been revoked to prevent further use
DEVICE_REVOKEDdevice_revokedThe device row was explicitly marked as revoked before the refresh attempt
AUTHENTICATABLE_MISSINGauthenticatable_missingThe device’s authenticatable polymorphic relation could not be loaded
IDENTITY_INACTIVEidentity_inactiveThe resolved identity implements CanBeActive and returned false
PRINCIPAL_UNRESOLVEDprincipal_unresolvedThe principal resolver returned null for the identity
PRINCIPAL_MISMATCHprincipal_mismatchThe resolved principal does not match the pid hint carried in the refresh token
PRINCIPAL_INACTIVEprincipal_inactiveThe resolved principal implements CanBeActive and returned false

Listening to events

Register listeners in your EventServiceProvider or using Laravel’s #[AsEventListener] attribute:
use Illuminate\Support\Facades\Event;
use SineMacula\Laravel\Authentication\Events\RefreshFailed;
use SineMacula\Laravel\Authentication\Events\Refreshed;
use SineMacula\Laravel\Authentication\Events\PrincipalAssigned;
use SineMacula\Laravel\Authentication\Events\DeviceAuthenticated;

// In AppServiceProvider::boot() or EventServiceProvider::$listen:
Event::listen(RefreshFailed::class, [RefreshAuditListener::class, 'handle']);
Event::listen(Refreshed::class, [RefreshAuditListener::class, 'handleSuccess']);
Event::listen(PrincipalAssigned::class, [PrincipalAuditListener::class, 'handle']);
Event::listen(DeviceAuthenticated::class, [DeviceMetadataListener::class, 'handle']);
Or using the attribute syntax available in Laravel 11+:
use Illuminate\Events\Attributes\AsEventListener;
use SineMacula\Laravel\Authentication\Events\RefreshFailed;

#[AsEventListener]
final class RefreshAuditListener
{
    public function handle(RefreshFailed $event): void
    {
        // ...
    }
}