xefi/laravel-passkey-api

A Laravel package for passkey authentication apis

Maintainers

Package info

github.com/xefi/laravel-passkey-api

pkg:composer/xefi/laravel-passkey-api

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

dev-main 2026-03-25 07:33 UTC

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 decoding
  • web-auth/cose-lib: for COSE key handling
  • openssl PHP 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 the auth middleware (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