NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Passkeys for Laravel

DI and actions

There are three equivalent entry points, and the choice is yours:

  • The Passkeys facade — the shortest, and the recommended default.
  • The service, injected through the constructor — the same API as an explicit dependency, with no static calls. Type-hint the RoundlyConsulting\Passkeys\Contracts\PasskeyService contract: it is the facade root, bound to RoundlyConsulting\Passkeys\PasskeyManager. The manager is bound only under the contract, not as its own singleton — inject the contract, not the class.
  • Actions — single-purpose classes with an execute() method, for composing into your own actions, jobs and commands.

Injecting the service

Passkeys::fake() swaps the PasskeyService binding, so constructor-injected code sees the fake too:

use Illuminate\Http\Request;
use Illuminate\Http\Response;
use RoundlyConsulting\Passkeys\Contracts\PasskeyService;

final readonly class RevokePasskeyController
{
    public function __construct(private PasskeyService $passkeys) {}

    public function __invoke(Request $request, int $passkey): Response
    {
        $this->passkeys->for($request->user())->revoke($passkey);

        return response()->noContent();
    }
}

Running an action

Each facade call is one action, resolved from the container:

Facade callAction
for($user)->registrationOptions($o)GenerateRegistrationOptionsAction::execute($user, $o)
for($user)->register($r, $name)VerifyRegistrationAction::execute($user, $r, $name)
for($user)->authenticationOptions($o) / authenticationOptions($o)GenerateAuthenticationOptionsAction::execute(?$user, $o)
for($user)->authenticate($r) / authenticate($r, $expect)VerifyAuthenticationAction::execute($r, ?$expect)
for($user)->rename($p, $name)RenamePasskeyAction::execute($passkey, $name)
for($user)->revoke($p)RevokePasskeyAction::execute($passkey)
use RoundlyConsulting\Passkeys\Actions\GenerateRegistrationOptionsAction;
use RoundlyConsulting\Passkeys\Actions\RevokePasskeyAction;
use RoundlyConsulting\Passkeys\Actions\VerifyRegistrationAction;
use RoundlyConsulting\Passkeys\Models\Passkey;

// The same two registration calls, one action each:
$options = app(GenerateRegistrationOptionsAction::class)->execute($user, $overrides);
$passkey = app(VerifyRegistrationAction::class)->execute($user, $response, 'MacBook Touch ID');

// Trusted code that already scoped its query — the action itself checks no ownership:
Passkey::query()
    ->ownedBy($user)
    ->where('last_used_at', '<', now()->subYear())
    ->each(fn (Passkey $passkey) => app(RevokePasskeyAction::class)->execute($passkey));

RenamePasskeyAction and RevokePasskeyAction take a resolved Passkey and check no ownership — the account handle does that. Call them directly only from trusted code, such as an admin tool or a job that already scoped its query.

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 crypto

By 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.