last_logged_in_at writes to avoid hot-spot database pressure on high-traffic APIs.
Device tracking is opt-in. If you only need short-lived access tokens without refresh (the common pattern for M2M APIs), you can skip the migration and the HasDevices contract entirely. See access-only mode below.
Config block
config/authentication.php
Fields
The fully-qualified class name of the Eloquent model used for device persistence. Sourced from
AUTHENTICATION_DEVICE_MODEL. The default is the package’s shipped Device model. Swap this to a custom class when you need additional columns, relationships, or behaviour — the custom model must implement the EloquentDevice contract..env
The database table that stores device records. Sourced from
AUTHENTICATION_DEVICE_TABLE. Override this when your schema uses a different table name. The shipped Device model reads this value lazily on each instantiation, so runtime config swaps (such as in multi-tenancy scenarios or tests) take effect immediately..env
The column on the device table that stores the hashed refresh rotation digest. Sourced from
AUTHENTICATION_DEVICE_REFRESH_KEY_COLUMN. The package reads and writes this column during refresh-token rotation; it must exist in the migration and be nullable (it is null before a refresh token has been issued for a device)..env
The minimum number of seconds between
last_logged_in_at writes for a given device on the bearer path. When a bearer request resolves a persisted device, the package updates last_logged_in_at only if the stored value is older than this threshold. Set to 0 to write on every request (not recommended on high-traffic APIs). Set to a larger value to reduce write frequency further..env
The shipped Device model
The package providesSineMacula\Laravel\Authentication\Models\Device as the default device adapter. Key characteristics:
- UUID v7 primary key — generated on insert via Laravel’s
HasUuidsconcern, so device IDs are time-ordered and globally unique without a sequence dependency. - Polymorphic
authenticatablerelation — amorphTothat can point at any identity model, so onedevicestable serves multiple authenticatable types in the same app. - Not
final— you can subclass it to add columns or relationships while retaining the built-inEloquentDevicebehaviour.
| Column | Type | Notes |
|---|---|---|
id | uuid | UUID v7, primary key |
authenticatable_type | string | Morph type |
authenticatable_id | string | Morph ID |
os | string | Client operating system label |
refresh_key | string|null | Hashed rotation digest; null until first refresh |
revoked_at | timestamp|null | Non-null means the device is revoked |
last_logged_in_at | timestamp|null | Debounced last-seen timestamp |
last_mfa_verified_at | timestamp|null | Optional MFA timestamp |
Publishing and running the migration
devices table with the schema above. If you change device.table, update the migration’s Schema::create call before running it.
Using a custom device model
To use a custom device model, create a class that implementsSineMacula\Laravel\Authentication\Contracts\EloquentDevice and point device.model at it:
config/authentication.php (or .env)
app/Models/CustomDevice.php
EloquentDevice directly. The contract requires the persistence accessors the package needs for rotation and last-seen tracking — refer to the contract interface for the required method signatures.
Enabling device tracking on your identity model
To opt into device tracking and refresh-token rotation, implementHasDevices on your identity model and define the devices() morph-many relationship:
app/Models/User.php
HasDevices, the guard skips device resolution and refresh is unavailable.
Access-only mode
Device tracking and refresh-token rotation are entirely optional. If your use case only requires short-lived access tokens — common for M2M APIs, internal service calls, and simple backends — you can skip the
devices table and the HasDevices contract.To run in access-only mode:- Publish only the config:
php artisan vendor:publish --tag=authentication-config. Skip theauthentication-migrationstag. - Do not implement
HasDeviceson your identity model. - Issue access tokens with
nullas the device argument:Auth::jwt('api')->issueAccessToken($identity, $principal, null). - Do not call
$guard->refresh(...). Clients re-authenticate when their access token expires.
devices table. Auth::device() returns null, but the full contextual surface (Auth::identity(), Auth::principal(), Auth::tenant(), Auth::type()) still works normally. You can add the migration and refresh flow later — it is additive, not a rewrite.