A session is a token family; its active row carries the device data. Row ids change on every rotation, so address a session by its family id — the stable session id, and a natural sid claim for your access token:
$sessions = RefreshTokens::sessions($user);
$sessions->all(); // active rows, newest first
$sessions->find($familyId); // ?RefreshToken — active row of that family
$sessions->revoke($familyId); // bool — revokes every active row of it
$sessions->revokeAllExcept($currentFamilyId); // "log out my other devices"; int
$sessions->revokeOthers($currentAccess->jti); // same, keyed by access reference
$sessions->revokeAll(); // revoke every session
// On the owner model via the trait:
$user->refreshTokens(); // MorphMany, all tokens
$user->sessions(); // MorphMany, active tokens only| Method | Returns | Notes |
|---|---|---|
sessions($owner)->all() | Collection<RefreshToken> | Active rows, newest first. |
sessions($owner)->find($familyId) | ?RefreshToken | The active row of that family; null for a malformed id or another owner’s family. |
sessions($owner)->revoke($familyId, $reason) | bool | Revokes every active row of the family (default Logout); false when nothing was active. |
sessions($owner)->revokeAllExcept($keepFamilyId, $reason) | int | Keeps one session by family id (default LogoutAll); null or an unrecognisable id revokes everything. |
sessions($owner)->revokeOthers($currentAccessReference, $reason) | int | Keeps the session holding that access reference (default LogoutAll); null revokes everything. |
sessions($owner)->revokeAll($reason) | int | Revokes every session (default LogoutAll). |
session($row)->enrich($device, $location) | void | Writes host-supplied device and geo data onto one row (the model or its key). |
session($row)->revoke($reason) | bool | Revokes one row you hold (default Manual). |
- The handle is owner-scoped: another owner’s family — including one of a same-id owner of another type — is unknown (find() returns null, revoke() false, nothing touched).
- Ids are validated as UUIDs before any query — a malformed id returns null/false, never a database error.
- revoke() revokes every active row of the family — a grace-window rotation can briefly leave two — denying each access reference once.
- revokeAllExcept with null or an unrecognisable id revokes everything: failing closed is the safe reading of an unknown “current session”.
- $row->sessionStartedAt() returns when the family began, inherited verbatim across rotation.
Verbs on the owner model
The HasRefreshTokens trait adds verbs that read as the owner acting on itself. Each one delegates to the manager — sugar over RefreshTokens::sessions($this) — so RefreshTokens::fake() records them too:
use RoundlyConsulting\RefreshTokens\DataTransferObjects\IssueContext;
$new = $user->issueRefreshToken(new IssueContext(accessReference: $access->jti));
$user->findSession($familyId); // ?RefreshToken
$user->revokeSession($familyId); // bool
$user->revokeAllSessions(); // = RefreshTokens::sessions($user)->revokeAll(); returns count
$user->revokeOtherSessions($currentAccess->jti); // keep current, revoke the rest; returns countA “your devices” endpoint
Build the list from the active rows; token_hash and access_reference are hidden from serialization, so returning whole rows is safe too:
return RefreshTokens::sessions($user)->all()->map(fn ($session) => [
'id' => $session->family_id, // stable session id
'browser' => $session->browser,
'os' => $session->os,
'device' => $session->device_type,
'city' => $session->city,
'started_at' => $session->sessionStartedAt(),
'current' => $session->family_id === $currentFamilyId,
]);Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.