xefi / laravel-passkey-api
A Laravel package for passkey authentication apis
Requires
- php: ^8.1|^8.2|^8.3|^8.4
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
- spomky-labs/cbor-php: ^3.0
- web-auth/cose-lib: ^4.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0|^11.0
- phpunit/phpunit: ^10.0
Suggests
- laravel/sanctum: Required for default token-based authentication session (^3.0|^4.0).
This package is not auto-updated.
Last update: 2026-03-26 07:47:20 UTC
README
A Laravel package for passkey (WebAuthn) authentication.
Requirements
- PHP 8.1+ (Note: Laravel 11/12/13 may require newer PHP versions depending on the framework release)
- Laravel 10.x, 11.x, 12.x, or 13.x
spomky-labs/cbor-php: for CBOR decodingweb-auth/cose-lib: for COSE key handlingopensslPHP extension
Tip
Laravel Sanctum is suggested if you want to use the default token-based authentication session, but it is not a hard requirement.
Installation
Install the package via Composer:
composer require xefi/laravel-passkey-api
The package will automatically register its service provider through Laravel's package auto-discovery.
Database Setup
Publish and run the migrations:
php artisan vendor:publish --tag=passkey-migrations php artisan migrate
This will create a passkeys table to store passkey credentials.
Configuration
Optionally publish the configuration file:
php artisan vendor:publish --tag=passkey-config
This creates config/passkey.php where you can customize:
enabled: Enable/disable the passkey package (default:true, env:PASSKEY_ENABLED)timeout: Passkey operation timeout in milliseconds (default:60000, env:PASSKEY_TIMEOUT)challenge_length: Length of the challenge in bytes (default:32, env:PASSKEY_CHALLENGE_LENGTH)user_model: The User model class (default:App\Models\User, env:PASSKEY_USER_MODEL)middleware: The middleware to apply to passkey routes. You can customize theauthmiddleware (default:auth:sanctum).
User Model Setup
Add the HasPasskeys trait to your User model:
use Xefi\LaravelPasskey\Traits\HasPasskeys; class User extends Authenticatable { use HasPasskeys; // ... existing code }
API Endpoints
The package provides several API endpoints for passkey management and authentication:
Passkey Management (require authentication)
List Passkeys
GET /api/passkeys Authorization: Bearer <token>
Returns a list of passkeys registered for the authenticated user.
Get Registration Options
POST /api/passkeys/register/options Authorization: Bearer <token> Content-Type: application/json
Returns options needed to create a new passkey credential.
Request Body:
{
"app_name": "My Application",
"app_url": "https://example.com"
}
Response:
{
"challenge": "base64-encoded-challenge",
"rp": {
"name": "My Application",
"id": "example.com"
},
"user": {
"id": "base64-encoded-user-id",
"name": "user@example.com",
"displayName": "John Doe"
},
"pubKeyCredParams": [
{"type": "public-key", "alg": -7},
{"type": "public-key", "alg": -257}
],
"timeout": 600000,
"attestation": "none",
"authenticatorSelection": {
"residentKey": "preferred",
"userVerification": "preferred"
}
}
Register Passkey
POST /api/passkeys/register Authorization: Bearer <token> Content-Type: application/json
Registers a new passkey credential and persists it to the database.
Request Body:
{
"label": "My Security Key",
"id": "credential-id",
"rawId": "raw-credential-id",
"type": "public-key",
"response": {
"clientDataJSON": "base64-encoded-client-data",
"attestationObject": "base64-encoded-attestation"
}
}
Response:
{
"passkey": {
"id": 1,
"label": "My Security Key",
"credential_id": "base64-encoded-credential-id",
"created_at": "2024-01-19T08:50:00.000000Z"
}
}
Authentication Flow (public)
Get Verification Options
POST /api/passkeys/verify/options Content-Type: application/json
Returns options needed to verify a passkey credential (challenge and allowed credentials).
Request Body:
{
"credential_id": "base64-encoded-credential-id"
}
Response:
{
"challenge": "base64-encoded-challenge",
"allowCredentials": [
{
"id": "base64-encoded-credential-id",
"type": "public-key"
}
],
"timeout": 60000,
"userVerification": "preferred"
}
Verify Passkey
POST /api/passkeys/verify Content-Type: application/json
Verifies a passkey authentication attempt without creating a session. Useful for MFA or re-authentication.
Request Body:
{
"id": "credential-id",
"rawId": "raw-credential-id",
"type": "public-key",
"response": {
"clientDataJSON": "base64-encoded-client-data",
"authenticatorData": "base64-encoded-auth-data",
"signature": "base64-encoded-signature"
}
}
Response:
{
"user": {
"id": 1
},
"passkey": {
"id": 1
}
}
Authenticate (Login)
POST /api/passkeys/login Content-Type: application/json
Authenticates a user via passkey and returns a Sanctum token.
Request Body: Same as Verify Passkey.
Response:
{
"user": {
"id": 1,
"name": "User Name",
"email": "user@example.com"
},
"token": "sanctum-plain-text-token"
}
Usage
After installation, the package routes will be automatically registered. You can verify the routes are available:
php artisan route:list --path=passkeys
Accessing User Passkeys
You can access a user's passkeys through the relationship:
$user = User::find(1); $passkeys = $user->passkeys; foreach ($passkeys as $passkey) { echo $passkey->label; echo $passkey->created_at; }
Testing
This package comes with a fully isolated Docker environment to ensure tests run consistently without requiring a local PHP installation.
To run the test suite, simply use the provided make commands:
# Run the test suite make test # Run tests and generate an HTML code coverage report (in the /coverage directory) make test-coverage # Open a bash shell inside the PHP container for debugging make bash
Note
The make test and make test-coverage commands will automatically build the Docker image and install Composer dependencies if they are missing. You can force this installation step manually using make setup.
Architecture Pattern
This library follows a clean, service-oriented architecture to maintain the Single Responsibility Principle:
flowchart TD
HTTP([HTTP Request])
HTTP --> Route
Route["PasskeyRoute\n(api.php)"]
Route --> Validation
Validation["FormRequest Validation\nRegisterRequest / VerifyRequest"]
Validation --> Controller
Controller["PasskeyController\n(Thin Layer)\n— Throws Exceptions"]
Service["WebAuthn\n(Business Logic)\n— Parsing CBOR\n— Verify COSE / OpenSSL\n— Data Extraction"]
Service --> Controller
Controller --> Model
Model["Passkey Model\n(Persistence)"]
Model --> User
User["User Model\n(App\\Models\\User)\nvia HasPasskeys Trait"]
Loading
Note
The controller uses Laravel's exception handling mechanism. Errors are thrown as exceptions (AuthenticationException, PasskeyNotFoundException, UserNotFoundException, etc.) rather than returning JSON error responses directly.
Typical Sequence Flow
Here is the typical sequence of interactions between the Client (Browser), the Server (API), and the Authenticator (Security Key, TouchID, etc.):
sequenceDiagram
actor User
participant Browser as Browser (JS)
participant Server as Server (API)
participant Auth as Authenticator
Note over User,Auth: 1. Initial Login (App Logic)
User->>Browser: Login
Browser->>Server: POST /login
Server-->>Browser: Sanctum Token
Note over User,Auth: 2. Register Passkey
User->>Browser: Register
Browser->>Server: POST /api/passkeys/register/options
Server-->>Browser: Registration Options
Browser->>Auth: navigator.credentials.create()
Note over Auth: 3. Fingerprint / Biometric
Auth-->>User: Prompt
User-->>Auth: Confirm
Note over Auth: 4. Attestation
Auth-->>Browser: Attestation Object
Browser->>Server: POST /api/passkeys/register
Server-->>Browser: Success
Note over User,Auth: 5. Authentication (Login)
User->>Browser: Login with Passkey
Browser->>Server: POST /api/passkeys/verify/options
Server-->>Browser: Authentication Options
Browser->>Auth: navigator.credentials.get()
Note over Auth: 6. Fingerprint / Biometric
Auth-->>User: Prompt
User-->>Auth: Confirm
Note over Auth: 7. Assertion
Auth-->>Browser: Assertion Object
Browser->>Server: POST /api/passkeys/login
Server-->>Browser: NEW Sanctum Token
Loading