Managing passkeys
List, rename or revoke an account’s passkeys through its handle, Passkeys::for($user) — no need to touch the model or the relation. find(), rename() and revoke() take a Passkey or its id — an int, or the string a route parameter arrives as — so a route parameter can be passed straight through. A string that is not a canonical positive integer is simply not found:
use RoundlyConsulting\Passkeys\Facades\Passkeys;
$keys = Passkeys::for($request->user());
// A route parameter (a string) passes straight through — another account's id is refused:
$keys->rename($request->route('passkey'), 'Work laptop'); // fires PasskeyRenamed
$keys->revoke($request->route('passkey')); // soft-deletes, fires PasskeyRevoked- rename() sets the cosmetic name, saves and fires PasskeyRenamed with the previous name. The name is never a verification input.
- revoke() soft-deletes the credential and fires PasskeyRevoked. A revoked credential can no longer authenticate, and its credential ID still counts as registered, so the authenticator can’t silently re-enrol it.
- Both refuse a passkey of any other account, or an already revoked one, with the uniform CredentialNotFound.
- Prefer the handle over $passkey->delete() or setting name directly — only the rename and revoke actions behind it fire the events.
Listing credentials
Read an account’s passkeys through the same handle, or with the ownedBy scope from anywhere that only holds the owner model. The scope matches morph type and key, so two guards’ models sharing a key never mix:
$keys = Passkeys::for($user);
$keys->all(); // Collection<int, Passkey> — active only, newest first
$keys->find($id); // ?Passkey — null for a revoked one or another account's id
$keys->count(); // int — revoked (soft-deleted) credentials are not counted
$keys->exists(); // bool — at least one active passkey
$user->hasPasskeys(); // == ->exists() (model verb)
$user->passkeyCount(); // == ->count() (model verb)
// From anywhere that only holds the owner model:
Passkey::query()->ownedBy($user)->latest('last_used_at')->get();Serialising safely
The Passkey model hides public_key, user_handle, credential_id and credential_id_hash from array and JSON output. For an explicit, display-safe payload use the shipped PasskeyResource:
use RoundlyConsulting\Passkeys\Http\Resources\PasskeyResource;
return PasskeyResource::collection(Passkeys::for($user)->all());Each item carries only what a settings screen needs — never the COSE public key, the login handle or the internal lookup keys:
{
"id": 12,
"name": "MacBook Touch ID",
"aaguid": "adce0002-35bc-c60a-648b-0b25f1f05503",
"attestation_type": "none",
"transports": ["internal", "hybrid"],
"backup_eligible": true,
"backup_state": true,
"last_used_at": "2026-07-09T18:00:00+00:00",
"created_at": "2026-07-01T18:00:00+00:00"
}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.