> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sinemacula.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication Events Reference

> Laravel Authentication fires standard Laravel events plus custom events for principal assignment, device authentication, and refresh rotation.

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:

| Event                                  | Fired when                                    |
| -------------------------------------- | --------------------------------------------- |
| `Illuminate\Auth\Events\Attempting`    | Bearer, refresh, or credential attempt starts |
| `Illuminate\Auth\Events\Validated`     | Successful login path about to bind context   |
| `Illuminate\Auth\Events\Authenticated` | Identity bound to the guard                   |
| `Illuminate\Auth\Events\Login`         | Full lifecycle complete                       |
| `Illuminate\Auth\Events\Failed`        | Any bearer, refresh, or credential rejection  |

## Custom events

| Event                                                          | Fired when                                |
| -------------------------------------------------------------- | ----------------------------------------- |
| `SineMacula\Laravel\Authentication\Events\PrincipalAssigned`   | Principal resolved and bound to the guard |
| `SineMacula\Laravel\Authentication\Events\DeviceAuthenticated` | Device hydrated and bound to the guard    |
| `SineMacula\Laravel\Authentication\Events\Refreshed`           | Refresh exchange completed successfully   |
| `SineMacula\Laravel\Authentication\Events\RefreshFailed`       | Refresh exchange failed                   |

### `PrincipalAssigned`

Dispatched immediately after the principal resolver binds a principal to a guard. Carries the guard name and the resolved principal instance.

```php theme={null}
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.

```php theme={null}
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.

```php theme={null}
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.

```php theme={null}
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:

| Reason                    | Value                     | Meaning                                                                                           |
| ------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------- |
| `TOKEN_INVALID`           | `token_invalid`           | Decode, expiry, `typ`, `iss`, or `aud` failure — the refresh token itself is malformed or expired |
| `DEVICE_UNKNOWN`          | `device_unknown`          | The device ID in the token did not resolve to a record in the `devices` table                     |
| `ROTATION_MISMATCH`       | `rotation_mismatch`       | The rotation digest did not match the stored refresh key — stale or tampered token                |
| `ROTATION_REUSE`          | `rotation_reuse`          | Replay or concurrent rotation detected; the device has been revoked to prevent further use        |
| `DEVICE_REVOKED`          | `device_revoked`          | The device row was explicitly marked as revoked before the refresh attempt                        |
| `AUTHENTICATABLE_MISSING` | `authenticatable_missing` | The device's `authenticatable` polymorphic relation could not be loaded                           |
| `IDENTITY_INACTIVE`       | `identity_inactive`       | The resolved identity implements `CanBeActive` and returned `false`                               |
| `PRINCIPAL_UNRESOLVED`    | `principal_unresolved`    | The principal resolver returned `null` for the identity                                           |
| `PRINCIPAL_MISMATCH`      | `principal_mismatch`      | The resolved principal does not match the `pid` hint carried in the refresh token                 |
| `PRINCIPAL_INACTIVE`      | `principal_inactive`      | The resolved principal implements `CanBeActive` and returned `false`                              |

## Listening to events

Register listeners in your `EventServiceProvider` or using Laravel's `#[AsEventListener]` attribute:

```php theme={null}
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+:

```php theme={null}
use Illuminate\Events\Attributes\AsEventListener;
use SineMacula\Laravel\Authentication\Events\RefreshFailed;

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