<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
padosoft / laravel-rebel-bridge-passkeys example snippets
use Padosoft\Rebel\Bridge\Passkeys\Challengers\SpatiePasskeyChallenger;
use Padosoft\Rebel\Bridge\Passkeys\Contracts\PasskeyChallenger;
public function register(): void
{
$this->app->singleton(PasskeyChallenger::class, SpatiePasskeyChallenger::class);
}
use Spatie\LaravelPasskeys\Models\Concerns\HasPasskeys;
use Spatie\LaravelPasskeys\Models\Passkeys\HasPasskeysTrait;
class User extends Authenticatable implements HasPasskeys
{
use HasPasskeysTrait;
// ...
}
use App\WebAuthn\MyWebAuthnChallenger;
use Padosoft\Rebel\Bridge\Passkeys\Contracts\PasskeyChallenger;
$this->app->singleton(PasskeyChallenger::class, MyWebAuthnChallenger::class);
interface PasskeyChallenger
{
// Does this user have at least one passkey registered?
public function hasPasskey(Authenticatable $user): bool;
// Issue a fresh single-use challenge. Return an opaque string (the nonce,
// or serialised WebAuthn options JSON) that you can later pass to verifyAssertion().
public function startChallenge(Authenticatable $user): string;
// Verify the assertion (JSON from navigator.credentials.get()) against
// the challenge issued by startChallenge(). Return true on full success.
public function verifyAssertion(Authenticatable $user, string $assertion, string $challenge): bool;
}
use Padosoft\Rebel\StepUp\RebelStepUp;
use Padosoft\Rebel\StepUp\StepUpContext;
use Padosoft\Rebel\Core\Context\SecurityContext;
class PaymentController extends Controller
{
public function confirm(Request $request, RebelStepUp $stepUp): JsonResponse
{
$context = new StepUpContext(
subject: $request->user(),
purpose: 'confirm-payment',
security: SecurityContext::fromRequest($request, app(KeyedHasher::class)),
);
// Start the step-up: issues a WebAuthn challenge.
$result = $stepUp->start($context, driver: 'passkeys');
return response()->json([
'challenge_reference' => $result->reference,
'webauthn_options' => $result->options,
]);
}
public function verify(Request $request, RebelStepUp $stepUp): JsonResponse
{
$context = new StepUpContext(
subject: $request->user(),
purpose: 'confirm-payment',
security: SecurityContext::fromRequest($request, app(KeyedHasher::class)),
);
$verified = $stepUp->verify(
$context,
input: $request->input('assertion'), // JSON from navigator.credentials.get()
reference: $request->input('reference'), // reference from start()
);
if (! $verified) {
return response()->json(['error' => 'Step-up failed'], 422);
}
// Proceed with the protected action.
$this->processPayment($request);
return response()->json(['status' => 'confirmed']);
}
}
use Padosoft\Rebel\StepUp\DriverRegistry;
use Padosoft\Rebel\StepUp\StepUpContext;
$driver = app(DriverRegistry::class)->get('passkeys');
if ($driver && $driver->isAvailableFor($context)) {
// Show the "Use passkey" button.
}
use Padosoft\Rebel\Bridge\Passkeys\Contracts\PasskeyChallenger;
use Padosoft\Rebel\Bridge\Passkeys\Testing\FakePasskeyChallenger;
// In your test boot or TestCase::defineEnvironment():
$app->singleton(PasskeyChallenger::class, fn () => new FakePasskeyChallenger(
registered: true, // user has a passkey
expectedAssertion: 'valid', // the assertion string that will be accepted
challenge: 'test-nonce', // the challenge returned by startChallenge()
));
// All step-up driver logic works offline without any WebAuthn ceremony.