Coder Social home page Coder Social logo

webauthn's Introduction

WebAuthn

Latest Version on Packagist Latest stable test run Codecov coverage CodeClimate Maintainability Sonarcloud Status Laravel Octane Compatibility

Authenticate users with Passkeys: fingerprints, patterns and biometric data.

// App\Http\Controllers\LoginController.php
use Laragear\WebAuthn\Http\Requests\AssertedRequest;

public function login(AssertedRequest $request)
{
    $user = $request->login();

    return response()->json(['message' => "Welcome back, $user->name!"]);
}

Tip

You want to add two-factor authentication to your app? Check out Laragear TwoFactor.

Become a sponsor

Your support allows me to keep this package free, up-to-date and maintainable. Alternatively, you can spread the word!

Requirements

  • Laravel 10.x or later.
  • PHP 8.1 or later.
  • The ext-openssl extension.
  • The ext-sodium extension (optional, for EdDSA 25519 public keys).

Tip

If you can't enable the ext-sodium extension for whatever reason, you may try installing paragonie/sodium_compat.

Installation

Require this package into your project using Composer:

composer require laragear/webauthn

How Passkeys work?

Passkeys, hence WebAuthn, consists in two ceremonies: attestation, and assertion.

Attestation is the process of asking the authenticator (a phone, laptop, USB key...) to create a private-public key pair, save the private key internally, and store the public key inside your app. For that to work, the browser must support WebAuthn, which is what intermediates between the authenticator (OS & device hardware) and the server.

Assertion is the process of pushing a cryptographic challenge to the authenticator, which will return back to the server signed by the private key of the device. Upon arrival, the server checks the signature is correct with the stored public key, ready to log in.

The private key doesn't leave the authenticator, there are no shared passwords stored anywhere, and Passkeys only work on the server domain (like google.com) or subdomain (like auth.google.com).

Set up

We need to make sure your users can register their devices and authenticate with them.

  1. Publish the files
  2. Add the WebAuthn driver
  3. Implement the contract and trait
  4. Register the controllers (optional)
  5. Use the Javascript helper (optional)

1. Add the WebAuthn driver

Laragear WebAuthn works by extending the Eloquent User Provider with a simple additional check to find a user for the given WebAuthn Credentials (Assertion). This makes this WebAuthn package compatible with any guard you may have.

Simply go into your auth.php configuration file, change the driver from eloquent to eloquent-webauthn, and add the password_fallback to true.

return [
    // ...

    'providers' => [
        'users' => [
            'driver' => 'eloquent-webauthn',
            'model' => App\User::class,
            'password_fallback' => true,
        ],
    ]
];

The password_fallback indicates the User Provider should fall back to validate the password when the request is not a WebAuthn Assertion. It's enabled to seamlessly use both classic (password) and WebAuthn authentication procedures.

2. Publish files and migrate

With the single webauthn:install command, you can install the configuration, routes, and migration files.

php artisan webauthn:install

This will also publish a migration file needed to create a table to hold the WebAuthn Credentials (Passkeys). Once ready, migrate your application to create the table.

php artisan migrate

Tip

You can modify the migration if you need to, like changing the table name.

3. Implement the contract and trait

Add the WebAuthnAuthenticatable contract and the WebAuthnAuthentication trait to the User class, or any other that uses authentication.

<?php

namespace App;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Laragear\WebAuthn\Contracts\WebAuthnAuthenticatable;
use Laragear\WebAuthn\WebAuthnAuthentication;

class User extends Authenticatable implements WebAuthnAuthenticatable
{
    use WebAuthnAuthentication;

    // ...
}

From here you're ready to work with WebAuthn Authentication. The following steps will help you close the gap to a full implementation.

4. Register the routes and controllers

WebAuthn uses exclusive routes to register and authenticate users. Creating these routes and controller may be cumbersome, specially if it's your first time in the WebAuthn realm, so these are installed automatically at Http\Controllers\WebAuthn when using webauthn:install.

Go into your web.php routes file and register a default set of routes with the \Laragear\WebAuthn\Http\Routes::register() method. Since WebAuthn doesn't require protection for CSRF/XSRF tokens, you may disable it for these routes.

// web.php
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
use Illuminate\Support\Facades\Route;
use Laragear\WebAuthn\Http\Routes as WebAuthnRoutes;

Route::view('welcome');

// WebAuthn Routes
Route::withoutMiddleware([VerifyCsrfToken::class])->group(function () {
    WebAuthnRoutes::register();
});

Tip

The @laragear/webpass javascript helper supports adding CSRF/XSRF tokens.

The method allows to use different attestation and assertion paths, and even each of the controllers.

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
use Laragear\WebAuthn\Http\Routes as WebAuthnRoutes;

WebAuthnRoutes::register(
    attest: 'auth/register',
    assert: 'auth/login'
)->withoutMiddleware(VerifyCsrfToken::class);

[!INFO]

You can also delete the controllers and implement attestation and assertion manually.

5. Use the Javascript helper

This package original Javascript helper has been moved into its own package, called @laragear/webpass. You may use directly in your HTML application by just using JSDelivr CDN:

<head>
    <script src="https://cdn.jsdelivr.net/npm/@laragear/webpass@2/dist/webpass.js" defer></script>
</head>

<body>
    <script async>
        if (Webpass.isUnsupported()) {
            alert("Your browser doesn't support WebAuthn.")
        }
        
        const { success } = await Webpass.attest("/webauthn/register/options", "/webauthn/register")
        
        if (success) {
            window.location.replace("/dashboard")
        }
    </script>
</body>

Alternatively, you may want to include it in your project dependencies if you're using a frontend framework like Vue, React, Angular or Svelte, to name a few.

npm i @laragear/webpass@2

Once done, you may attest and assert the authenticator using the Webpass object:

import Webpass from "@laragear/webpass"

if (Webpass.isUnsupported()) {
    return alert("Your browser doesn't support WebAuthn.")
}

// Create new credentials for a logged in user
const { credential, success, error } = await Webpass.attest("/webauthn/register/options", "/webauthn/register")

// Check the credentials for a guest user
const { user, success, error } = await Webpass.assert("/webauthn/login/options", "/webauthn/login")

The Webpass helper offers more flexibility than just adjusting the WebAuthn ceremony paths. For more information, check the documentation of @laragear/webpass.

Attestation

Attestation is the ceremony to create WebAuthn Credentials. To create an Attestable Response that the user device can understand, use the AttestationRequest::toCreate() form request.

For example, we can create our own AttestationController to create it.

// app\Http\Controllers\WebAuthn\AttestationController.php
use Laragear\WebAuthn\Http\Requests\AttestationRequest;

public function createChallenge(AttestationRequest $request)
{
    return $request->toCreate();
}

The device will receive the "instructions" to make a key, and will respond with it. You can use the AttestedRequest form request and its save() method to persist the WebAuthn key if it is valid. The request will automatically return a Validation exception if something fails.

// app\Http\Controllers\WebAuthn\AttestationController.php
use Laragear\WebAuthn\Http\Requests\AttestedRequest;

public function register(AttestedRequest $attestation)
{
    $attestation->save();
    
    return 'Now you can login without passwords!';
}

You may pass an array, or a callback, to the save(), which will allow you to modify the underlying WebAuthn Eloquent Model before saving it. For example, we could add an alias for the key present in the Request data.

// app\Http\Controllers\WebAuthn\AttestationController.php
use Laragear\WebAuthn\Http\Requests\AttestedRequest;

public function register(AttestedRequest $request)
{
    $request->validate(['alias' => 'nullable|string']);

    $attestation->save($request->only('alias'));
    
    // Same as:
    // $attestation->save(function ($credentials) use ($request) {
    //    $credentials->alias = $request->input('alias');
    // })
}

Important

Both AttestationRequest and AttestedRequest require the authenticated user. If the user is not authenticated, an HTTP 403 status code will be returned.

Attestation User verification

By default, the authenticator decides how to verify user when creating a credential. Some may ask to press a "Continue" button to confirm presence, others will verify the User with biometrics, patterns or passwords.

You can override this using fastRegistration() to only check for user presence if possible, or secureRegistration() to actively verify the User.

// app\Http\Controllers\WebAuthn\AttestationController.php
use Laragear\WebAuthn\Http\Requests\AttestationRequest;

public function createChallenge(AttestationRequest $request)
{
    return $request->fastRegistration()->toCreate();
}

Userless/One-touch/Typeless Login

This enables one click/tap login, without the need to specify the user credentials (like the email) beforehand.

For this to work, the device has to save the "username id" inside itself. Some authenticators may save it regardless, others may be not compatible. To make this mandatory when creating the WebAuthn Credential, use the userless() method of the AttestationRequest form request.

// app\Http\Controllers\WebAuthn\AttestationController.php
use Laragear\WebAuthn\Http\Requests\AttestationRequest;

public function registerDevice(AttestationRequest $request)
{
    return $request->userless()->toCreate();
}

Important

The Authenticator WILL require user verification on login when using userless(). Its highly probable the user will also be asked for user verification on login, as it will depend on the authenticator itself.

Multiple credentials per device

By default, during Attestation, the device will be informed about the existing enabled credentials already registered in the application. This way the device can avoid creating another one for the same purpose.

You can enable multiple credentials per device using allowDuplicates(), which in turn will always return an empty list of credentials to exclude. This way the authenticator will think there are no already stored credentials for your app, and create a new one.

// app\Http\Controllers\WebAuthn\AttestationController.php
use Laragear\WebAuthn\Http\Requests\AttestationRequest;

public function registerDevice(AttestationRequest $request)
{
    return $request->allowDuplicates()->make();
}

Assertion

The Assertion procedure also follows a two-step procedure: the user will input its username, the server will return the IDs of the WebAuthn credentials to use, and the device pick one to sign the response. If you're using userless login, only the challenge is returned.

First, use the AssertionRequest::toVerify() form request. It will automatically create an assertion for the user that matches the credentials, or a blank one in case you're using userless login. Otherwise, you may set stricter validation rules to always ask for credentials.

For example, we can use our own AssertionController to handle it.

// app\Http\Controllers\WebAuthn\AssertionController.php
use Laragear\WebAuthn\Http\Requests\AssertionRequest;

public function createChallenge(AssertionRequest $request)
{
    $request->validate(['email' => 'sometimes|email']);

    return $request->toVerify($request->only('email'));
}

After that, you may receive the challenge using the AssertedRequest request object by just type-hinting it in the controller.

Since the authentication is pretty much straightforward, you only need to check if the login() method returns the newly authenticated user or null when it fails. When it's a success, it will take care of regenerating the session for you.

// app\Http\Controllers\WebAuthn\AssertionController.php
use Laragear\WebAuthn\Http\Requests\AssertedRequest;

public function createChallenge(AssertedRequest $request)
{
    $user = $request->login();
    
    return $user 
        ? response("Welcome back, $user->name!");
        : response('Something went wrong, try again!');
}

If you need greater control on the Assertion procedure, you may want to Assert manually.

Assertion User Verification

In the same style of attestation user verification, the authenticator decides if it should verify the user on login or not.

You may only require the user presence with fastLogin(), or actively verify the user with secureLogin().

// app\Http\Controllers\WebAuthn\AssertionController.php
use Laragear\WebAuthn\Http\Requests\AssertionRequest;

public function createChallenge(AssertionRequest $request)
{
    $request->validate(['email' => 'sometimes|email']);

    return $request->fastLogin()->toVerify($request->only('email'));
}

Password Fallback

By default, the eloquent-webauthn can be used to log in users with passwords when the credentials are not a WebAuthn JSON payload. This way, your normal Authentication flow is unaffected:

// app\Http\Controllers\Auth\LoginController.php
use Illuminate\Support\Facades\Auth;

public function login(Request $request)
{
    $request->validate(['email' => 'required|email', 'password' => 'required|string']);

    if (Auth::attempt($request->only('email', 'password'))) {
        return redirect()->home();
    }
    
    return back()->withErrors(['email' => 'No user found with these credentials']);
}

You may disable the fallback to only allow WebAuthn authentication by setting password_fallback to false. This may force you to handle classic user/password using a separate guard.

Detecting Cloned Credentials

During assertion, the package will automatically detect if a Credential has been cloned by comparing how many times the user has logged in with it.

If it's detected as cloned, the Credential is disabled, a CredentialCloned event is fired, and the Assertion gets denied.

You can use the event to warn the user:

use Illuminate\Support\Facades\Event;
use Laragear\WebAuthn\Events\CredentialCloned;
use App\Notifications\SecureYourDevice;

Event::listen(CredentialCloned::class, function ($cloned) {
    $notification = new SecureYourDevice($cloned->credential);
    
    $cloned->credential->user->notify($notification);
});

Managing Credentials

The purpose of the WebAuthnAuthenticatable contract is to allow managing credentials within the User instance. The most useful methods are:

  • webAuthnData(): Returns the non-variable WebAuthn user data to create credentials.
  • flushCredentials(): Removes all credentials. You can exclude credentials by their id.
  • disableAllCredentials(): Disables all credentials. You can exclude credentials by their id.
  • makeWebAuthnCredential(): Creates a new WebAuthn Credential instance.
  • webAuthnCredentials(): One-to-Many relation to query for WebAuthn Credentials.

You can use these methods to, for example, find a credential to blacklist, or disable WebAuthn completely by flushing all registered devices.

Events

The following events are fired by this package, which you can hook into in your application:

Event Description
CredentialCreated An User has registered a new WebAuthn Credential through Attestation.
CredentialEnabled A disabled WebAuthn Credential was enabled using enable().
CredentialDisabled A enabled WebAuthn Credential was disabled using disable().
CredentialCloned A WebAuthn Credential was detected as cloned dring Assertion.

Manually Attesting and Asserting

If you want to manually Attest and Assert users, you may instance their respective pipelines used for both WebAuthn Ceremonies:

Pipeline Description
AttestationCreator Creates a request to create a WebAuthn Credential.
AttestationValidator Validates a response with the WebAuthn Credential and stores it.
AssertionCreator Creates a request to validate a WebAuthn Credential.
AssertionValidator Validates a response for a WebAuthn Credential.

All of these pipelines require the current Request to work, as is used to generate Challenges in the Session and validate different parts of the authentication data.

For example, you may manually authenticate a user with its WebAuthn Credentials AssertionValidator pipeline. We can just type-hint a pipeline in a Controller action argument and Laravel will automatically inject the instance to it.

use Laragear\WebAuthn\Assertion\Validator\AssertionValidation;
use Laragear\WebAuthn\Assertion\Validator\AssertionValidator;
use Illuminate\Support\Facades\Auth;

public function authenticate(Request $request, AssertionValidator $assertion)
{
    $credential = $assertion
        ->send(new AssertionValidation($request))
        ->thenReturn()
        ->credential;
    
    Auth::login($credential->user);
    
    return "Welcome aboard, {$credential->user->name}!";
}

Since these are Laravel Pipelines, you're free to push additional pipes. These pipes can be a class with handle(), or just a function that receives the validation procedure.

use Laragear\WebAuthn\Assertion\Validator\AssertionValidator;
use Exception;

public function authenticate(Request $request, AssertionValidator $assertion)
{
    $credential = $assertion
        ->send(new AssertionValidation($request))
        // Add new pipes to the validation.
        ->pipe(function($validation, $next) {
            if ($validation->user?->isNotAwesome()) {
                throw new Exception('The user is not awesome');
            }

            return $next($validation);
        })
        ->thenReturn()
        ->credential;
    
    Auth::login($credential->user);
    
    return "Welcome aboard, {$credential->user->name}!";
}

Warning

The pipes list and the pipes themselves are not covered by API changes, and are marked as internal. These may change between versions without notice.

Migrations

This package comes with a migration file that extends a special class that takes most of the heavy lifting for you. You only need to create additional columns if you need to.

use Illuminate\Database\Schema\Blueprint;
use Laragear\WebAuthn\Database\WebAuthnCredentialsMigration;

return new class extends WebAuthnCredentialsMigration {
    /**
     * Modify the migration for the WebAuthn Credentials.
     */
    public function modifyMigration(Blueprint $table): void
    {
        // You may add here your own columns...
        //
        // $table->string('device_name')->nullable();
        // $table->string('device_type')->nullable();
        // $table->timestamp('last_login_at')->nullable();
    }
};

If you need to modify the table, or adjust the data, after is created or before is dropped, you may use the afterUp() and beforeDown() methods of the migration file, respectively.

use Illuminate\Database\Schema\Blueprint;
use Laragear\WebAuthn\Database\WebAuthnCredentialsMigration;

return new class extends WebAuthnCredentialsMigration {
    // ...
    
    public function afterUp(Blueprint $table): void
    {
        $table->foreignId('device_serial')->references('serial')->on('devices');
    }
    
    public function beforeDown(Blueprint $table): void
    {
        $table->dropForeign('device_serial')
    }
};

UUID or ULID morphs

There may be some scenarios where your authenticatable User is using a different type of primary ID in the database, like UUID or ULID. If this is the case, you may change the morph type accordingly with the $morphType property.

use Illuminate\Database\Schema\Blueprint;
use Laragear\WebAuthn\Database\WebAuthnCredentialsMigration;

return new class extends WebAuthnCredentialsMigration {

    protected ?string $morphType = 'ulid';
    
    // ...
};

Advanced Configuration

Laragear WebAuthn was made to work out-of-the-box, but you can override the configuration by simply publishing the config file.

php artisan vendor:publish --provider="Laragear\WebAuthn\WebAuthnServiceProvider" --tag="config"

After that, you will receive the config/webauthn.php config file with an array like this:

<?php

return [
    'relying_party' => [
        'name' => env('WEBAUTHN_NAME', env('APP_NAME')),
        'id'   => env('WEBAUTHN_ID'),
    ],
    'challenge' => [
        'bytes' => 16,
        'timeout' => 60,
        'key' => '_webauthn',
    ]
];

Relying Party Information

return [
    'relying_party' => [
        'name' => env('WEBAUTHN_NAME', env('APP_NAME')),
        'id'   => env('WEBAUTHN_ID'),
    ],
];

The Relying Party is just a way to uniquely identify your application in the user device:

  • name: The name of the application. Defaults to the application name.
  • id: An unique ID the application, recommended to be the site domain. If null, the device may fill it internally, usually as the full domain.

Warning

WebAuthn authentication only work on the top domain it was registered.

Instead of modifying the config file, you should use the environment variables to set the name and ID for WebAuthn.

WEBAUTHN_NAME=SecureBank
WEBAUTHN_ID=auth.securebank.com

Challenge configuration

return [
    'challenge' => [
        'bytes' => 16,
        'timeout' => 60,
        'key' => '_webauthn',
    ]
];

The outgoing challenges are random string of bytes. This controls how many bytes, the seconds which the challenge is valid, and the session key used to store the challenge while its being resolved by the device.

Laravel UI, Jetstream, Fortify, Sanctum, Breeze, Inertia and Livewire

In theory this package should work without any problems with these packages, but you may need to override or redirect the authentication flow (read: override methods) to one using WebAuthn.

There is no support for using WebAuthn with these packages because these are meant to be used with classic user-password authentication. Any issue regarding these packages will be shot down with extreme prejudice.

If you think WebAuthn is critical for these packages, consider supporting this package.

FAQ

  • Does this work with any browser?

Yes. In the case of old browsers, you should have a fallback detection script. This can be asked with the included JavaScript helper in a breeze:

if (WebAuthn.isNotSupported()) {
   alert('Your device is not secure enough to use this site!');
}
  • Does this store the user fingerprints, PINs or patterns in my site?

No. WebAuthn only stores a cryptographic public key generated randomly by the device.

  • Can a phishing site steal WebAuthn credentials and use them in my site to impersonate an user?

No. WebAuthn kills the phishing because, unlike passwords, the private key never leaves the device, and the key-pair is bound to the top-most domain it was registered.

An user bing phished at staetbank.com won't be able to login with a key made on the legit site statebank.com, as the device won't be able to find it.

  • Can WebAuthn data identify a particular device?

No, unless explicitly requested and consented. This package doesn't support other attestation conveyances than none, so it's never transmitted.

  • Are my user's classic passwords safe?

Yes, as long you are hashing them as you should. This is done by Laravel by default. You can also disable them.

  • Can a user register two or more different devices for the same account?

Yes.

  • Can a user register two or more credentials in the same device?

Not by default, but you can enable it.

  • If a user loses his device, can he register a new device?

Yes. If you're not using a password fallback, you may need to create a logic to register a new device using an email or SMS. It's assumed he is reading his email using a trusted device.

  • What's the difference between disabling and deleting a credential?

Disabling a credential doesn't delete it, so it's useful as a blacklisting mechanism and these can also be re-enabled. When the credential is deleted, it goes away forever from the server, so the credential in the authenticator device becomes orphaned.

  • Can a user delete its credentials from its device?

Yes. If it does, the other part of the credentials in your server gets orphaned. You may want to show the user a list of registered credentials in the application to delete them.

  • How secure is this against passwords or 2FA?

Extremely secure since it works only on HTTPS (or localhost). Also, no password or codes are exchanged nor visible in the screen.

  • Can I deactivate the password fallback? Can I enforce only WebAuthn authentication and nothing else?

Yes. Just be sure to create recovery helpers to avoid locking out your users.

  • Does this include JavaScript to handle WebAuthn in the frontend?

It's encouraged to use Webpass package.

Alternatively, for complex WebAuthn management, consider using the navigator.credentials API directly.

  • The attestation is fine, but assertion never logs in the user

This happens because you forgot the first step, using the WebAuthn driver to authenticate users.

  • Does WebAuthn eliminate bots? Can I forget about captchas?

Yes and no. To register users, you still need to use captcha, honeypots, or other mechanisms to stop bots from filling forms.

Once a user is registered, bots won't be able to log in because the real user is the only one that has the private key required for WebAuthn.

  • Does this encode/decode the WebAuthn data automatically in the frontend?

Yes, the Webpass helper does it automatically for you.

  • Does this encrypt the public keys?

Yes, public keys are encrypted when saved into the database with your app key.

  • I changed my APP_KEY and nobody can log in

Since public keys are encrypted with your app key, older public keys will become useless. To change that, create a console command that decrypts (with the old key) and re-encrypts the public_key column of the table where the authentication data is.

  • Does this include WebAuthn credential recovery routes?

No. You're free to create your own flow for recovery.

My recommendation is to email the user, pointing to a route that registers a new device, and immediately redirect him to blacklist which credential was lost (or blacklist the only one he has).

  • Can I use my smartphone as authenticator through my PC or Mac?

Usually.

While this is entirely up to hardware, OS and browser vendor themselves, modern platforms will show a QR code, push notification, or ask to bring closer your smartphone to complete the WebAuthn ceremony. Please check your target platforms of choice.

  • Why my device doesn't show Windows Hello/Passkey/TouchID/FaceID/OpticID/pattern/fingerprint authentication?

By default, this WebAuthn works on almost everything. Some combinations of devices, OS and Web browsers may differ on what to make available for WebAuthn authentication.

You may check this site for authenticator support.

  • Why my device doesn't work at all with this package?

This package supports WebAuthn 2.0, which is W3C Recommendation. Your device/OS/browser may be using an unsupported version.

There are no plans to support older WebAuthn specs. The new WebAuthn 3.0 draft spec needs to be finished to be supported.

  • I'm trying to test this in my development server, but it doesn't work

Use localhost exclusively (not 127.0.0.1 or ::1) or use a proxy to tunnel your site through HTTPS. WebAuthn only works on localhost or under HTTPS only.

  • Why this package supports only none attestation conveyance?

Because direct, indirect and enterprise attestations are mostly used on high-security high-risk scenarios, where an entity has total control on the devices used to authenticate. Imagine banks, medical, or military.

If you deem this feature critical for you, consider supporting this package.

  • Can I allow logins with only USB keys?

No. The user can use whatever to authenticate in your app. This may be enabled on future versions.

  • Everytime I make attestations or assertions, it says no challenge exists!

Remember that your WebAuthn routes must use Sessions, because the Challenges are stored there.

Session are automatically started on the web route group, or using the StartSession middleware directly. You can check this on your HTTP Kernel Middleware.

  • My ceremonies always fail. How I can debug this package?

If you have debugging enabled, like on development environments, the assertion data is logged in your application logs.

The rest of errors are thrown as-is. You may want to log them manually using Laravel's Error Handler depending on the case.

  • Can I publish only some files?

Yes. Instead of using webauthn:install, use vendor:publish and follow the prompts.

  • Why ext-sodium is required as optional?

Some authenticators can create EdDSA 25519 public keys, which are part of W3C WebAuthn 3.0 draft. These keys are shorter and don't require too much computational power to verify, which opens the usage for low-power or "passive" authenticators (like smart-cards).

If sodium or the paragonie/sodium-compat package are not installed, the server won't report EdDSA 25519 compatibility to the authenticator, and any EdDSA 25519 public key previously stored will fail validation.

Consider also that there are no signs of EdDSA 25519 incorporation into PHP ext-openssl extension.

Laravel Octane Compatibility

  • There are no singletons using a stale application instance.
  • There are no singletons using a stale config instance.
  • There are no singletons using a stale request instance.
  • There are no static properties written during a request.

There should be no problems using this package with Laravel Octane.

Security

These are some details about this WebAuthn implementation you should be aware of.

  • Registration (attestation) and Login (assertion) challenges use the current request session.
  • Only one ceremony can be done at a time, because ceremonies use the same challenge key.
  • Challenges are pulled (retrieved and deleted from source) from the session on resolution, independently of their result.
  • All challenges and ceremonies expire after 60 seconds.
  • WebAuthn User Handle is UUID v4.
  • User Handle is reused when a new credential for the same user is created.
  • Credentials can be blacklisted (enabled/disabled).
  • Public Keys are encrypted by with application key in the database automatically, using the application key.

If you discover any security related issues, please email [email protected] instead of using the issue tracker.

License

The MIT License (MIT). Please see License File for more information.

Contains Code from Lukas Buchs WebAuthn 2.0 implementation. The MIT License (MIT) where applicable.

Laravel is a Trademark of Taylor Otwell. Copyright © 2011-2022 Laravel LLC.

webauthn's People

Contributors

bubka avatar darkghosthunter avatar donatj avatar felix-exon avatar fidelisepeter avatar ildyria avatar nooxx avatar szepeviktor avatar tobz-nz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

webauthn's Issues

[1.x] A challenge is returned when there is no device registered

PHP & Platform

8.2.1 & MacOs

Database

No response

Laravel version

10

Have you done this?

  • I have checked my logs and I'm sure is a bug in this package.
  • I can reproduce this bug in isolation (vanilla Laravel install)
  • I can suggest a workaround as a Pull Request

Expectation

My understanding is that if there is no associated credential to the user, the challenge should be empty.

Description

I created a new user and on the first login, I want the user to login via the password as no passkey as been saved to the device yet. But, when I hit the /login/options route, I get a challenge instead of the null value.

Reproduction

1- Create a new user
2- Send the email as the credential to the `/login/options` route

Stack trace & logs

No response

[3.x] iCloud (TouchID) userHandle is without dash

PHP & Platform

8.3.6 - macOS 14.4.1 aarch64

Database

PostgreSQL 14.11

Laravel version

11.5.0

Have you done this?

  • I have checked my logs and I'm sure is a bug in this package.
  • I can reproduce this bug in isolation (vanilla Laravel install)
  • I can suggest a workaround as a Pull Request

Expectation

When I use 1Password / my macbook's TouchId, as long as I have registered it in the system, I should be able to login.

Description

When using 1Password as a Passkey, 1Password seems to be sending the userHandle in the format that this package expects, which is without dash, e.g.:
6943324022c046d395de29ceda194b63

Which will be validated in \Laragear\WebAuthn\Assertion\Validator\Pipes\CheckCredentialIsForUser@validateId like so:

hash_equals(Uuid::fromString($validation->credential->user_id)->getHex()->toString(), $handle);
// using the userHandle 1Password provided, the line above becomes:
'6943324022c046d395de29ceda194b63' === '6943324022c046d395de29ceda194b63';

But my MBP's TouchID seems to sent userHandle with dash, i.e.: 69433240-22c0-46d3-95de-29ceda194b63
the hash_equals above effectively becomes:

'6943324022c046d395de29ceda194b63' === '69433240-22c0-46d3-95de-29ceda194b63';

This makes logging-in using my TouchID to be impossible, I reckon other people also can not login.

Right now this issue is not a problem in my projects, as I am using this workaround in my WebAuthnLoginController.php:

    public function login(AssertedRequest $request)
        : \Symfony\Component\HttpFoundation\Response
    {
        // hacky workaround =>
        $userHandle = $request->json()->get('response.userHandle');

        $request->json()->set('response',
            [
                'userHandle' => Str::replace(
                    '-', '', $userHandle ?? ''
                )
            ]
            + $request->json('response')
        );

        $user = $request->login();

        // ...
    }

But someone might want to address this issue

Reproduction

// sorry, don't have the time to make a repro, feel free to just close this issue :)
// the issue itself is easily fixable on userland, with a little hack.

Stack trace & logs

// This is the TouchID
[2024-05-03 09:50:12] local.DEBUG: array (
  'request' => 
  array (
    'remember' => NULL,
    'id' => 'ZDariNmfR72XYzZEG-QMubXC9Mc',
    'rawId' => 'ZDariNmfR72XYzZEG-QMubXC9Mc',
    'response' => 
    array (
      'authenticatorData' => 'REDACTED',
      'clientDataJSON' => 'REDACTED',
      'signature' => 'REDACTED',
      'userHandle' => '69433240-22c0-46d3-95de-29ceda194b63',
    ),
    'type' => 'public-key',
    'clientExtensionResults' => 
    array (
    ),
    'authenticatorAttachment' => 'platform',
  ),
  'handle' => '69433240-22c0-46d3-95de-29ceda194b63',
  'Uuid::fromString()->getHex()->toString()' => '6943324022c046d395de29ceda194b63',
)  
[2024-05-03 09:50:12] local.DEBUG: Assertion Error: User ID is not owner of the stored credential.  

// This is 1Password
[2024-05-03 09:50:35] local.DEBUG: array (
  'request' => 
  array (
    'remember' => NULL,
    'id' => 'auDdo3Qn2MZ47Fp0bg0hIg',
    'rawId' => 'auDdo3Qn2MZ47Fp0bg0hIg',
    'response' => 
    array (
      'authenticatorData' => 'REDACTED',
      'clientDataJSON' => 'REDACTED',
      'signature' => 'REDACTED',
      'userHandle' => '6943324022c046d395de29ceda194b63',
    ),
    'type' => 'public-key',
    'clientExtensionResults' => 
    array (
    ),
    'authenticatorAttachment' => 'platform',
  ),
  'handle' => '6943324022c046d395de29ceda194b63',
  'Uuid::fromString()->getHex()->toString()' => '6943324022c046d395de29ceda194b63',
)

[1.2.1] Custom Relying Party Id does not pass CheckRelyingPartyIdContained pipe

PHP & Platform

8.1.22

Database

No response

Laravel version

10.16.1

Have you done this?

  • I have checked my logs and I'm sure is a bug in this package.
  • I can reproduce this bug in isolation (vanilla Laravel install)
  • I can suggest a workaround as a Pull Request

Expectation

Setting a custom Relying Party Id in the .env file should allow to register new webauthn devices.

Description

Following the Laragear/webauthn documentation:

WebAuthn/README.md

Lines 605 to 609 in 7e62ec9

Instead of modifying the config file, you should use the environment variables to set the name and ID for WebAuthn.
```dotenv
WEBAUTHN_NAME=SecureBank
WEBAUTHN_ID=https://auth.securebank.com

If I set WEBAUTHN_ID=https://my.domain.com the registration ceremony fails because the RP-ID is not valid. Indeed, regarding the Webauthn W3C recommandation, the RP ID should be a domain, not an URL. Using such an URL makes the webauthn API throwing a SecurityError (see https://www.w3.org/TR/webauthn-2/#CreateCred-DetermineRpId)

But if I set WEBAUTHN_ID=my.domain.com, the registration ceremony also fails but this time because the CheckRelyingPartyIdContained does not pass because of the way $current is defined:

$current = parse_url(
$this->config->get('webauthn.relying_party.id') ?? $this->config->get('app.url'), PHP_URL_HOST
);

Using parse_url() with the PHP_URL_HOST flag will return nothing, causing the next evaluation to fail:

if (hash_equals($current, $host) || Str::is("*.$current", $host)) {
return $next($validation);
}

Reproduction

// Set WEBAUTHN_ID=my.domain.com and try to register a new device

Stack trace & logs

No response

[3.x] Stateless behavior

Please check these requirements

  • This feature helps everyone using this package
  • It's feasible and maintainable
  • It's non breaking
  • I issued a PR with the implementation (optional)

Description

Hi, first of all thanks for super power 🚀 package,

I think it can be very useful and important to give the ability of using a stateless mode (e.g. for token based communications like rest/grapqhl/..).

In this sense I identify two areas for improvement:

  • ability to use the cache as an alternative driver to the session
  • pipelines, ability of receiving an alternative contract to the request as input from which to obtain the exchange data

I'll try to clarify this last point for a moment: at several points pipes expect precise data present in the input of incoming request but this is quite limiting if the communication where the payload of the data exchanged is different (e.g. graphql/rest/..) and this cause complications in use of manual management, (as a work around I currently use request()->merge([]) but I think we can find a better strategy.

What do you think about this? Did I miss something, maybe some security-related steps?

Thanks.

Code sample

// I'm trying to see if I can produce a draft PR on this.

[1.x] Fails to register yubikey (or platform authenticator) when using @simplewebauthn/browser

PHP & Platform

8.1 (Debian - Bookworm)

Database

PostgreSQL 15

Laravel version

10.40.0

Have you done this?

  • I have checked my logs and I'm sure is a bug in this package.
  • I can reproduce this bug in isolation (vanilla Laravel install)
  • I can suggest a workaround as a Pull Request

Expectation

When I am trying to register a yubikey, I expect it to be registered with my account.

Description

Impossible to register my yubikey (or any platform authenticator, in my case Touch ID).

I guess that the javascript used in @simplewebauthn/browser is a bit different which causes this issue.

Reproduction

I've used the following example https://github.com/MasterKale/SimpleWebAuthn/blob/master/example/public/index.html

Adjusted the urls and tried to register a yubikey and tried to register Touch ID.

Stack trace & logs

When trying to register I get the following validation error:

ByteBuffer: Invalid offset or length.

[1.2] Configuration options for WebAuthn data properties

Please check these requirements

  • This feature helps everyone using this package
  • It's feasible and maintainable
  • It's non breaking
  • I issued a PR with the implementation (optional)

Description

It would be great if the fields used in the WebAuthn data were configurable instead of being hardcoded to the email and name properties of the user.

https://github.com/Laragear/WebAuthn/blob/1.x/src/WebAuthnAuthentication.php#L28

For example, I might want to use the user's username instead of email as the visible name of the WebAuthn credential.

It should be straightforward to add a configuration option to set these properties.

Code sample

`config/webauthn.php`


...
  'webauthn_data_properties' => [
    'name' => env('WEBAUTHN_NAME_PROP', 'email'),
    'displayName' => env('WEBAUTHN_DISPLAYNAME_PROP', 'name'),
  ]
...

E-Mail not really required for login

PHP & Platform

8.1

Database

No response

Laravel version

10

Have you done this?

  • I have checked my logs and I'm sure is a bug in this package.
  • I can reproduce this bug in isolation (vanilla Laravel install)
  • I can suggest a workaround as a Pull Request

Expectation

When I'm logging in with wrong E-Mail Adress, the browser should not be able to find a matching Key.

Description

The browser is not able to find a matching key, but if I only have one key, the browser is choosing the one and logging in into the account where the key is for and not the account I typed the email-adress in.

So long story short: No matter which E-Mail I'm inserting in Login Form, I am always in the same account.
Tested on MacOS with Chrome.

Potentially a bug in chrome? I'm not shure? Or is this the expected behaviour?

Reproduction

Generate Webauthn Login and try to log in with different E-Mail.

Stack trace & logs

No response

[3.x] Native Android and iOS implementation integration support

Please check these requirements

  • This feature helps everyone using this package
  • It's feasible and maintainable
  • It's non breaking
  • I issued a PR with the implementation (#84)

Description

Studying the integration of the library on Android and iOS we found that although via WebView (both platform) or Android CustomTabs / iOS SFSafariViewController there are no difficulties, it is necessary to carry out a small server side integration to allow the native implementation of the solution on Android and iOS.

The evaluation of this analysis was done with a small poc (actors: android / ios / web / server), in essence, everything translates (in addition to the various configurations of the case for both environments which are not the responsibility of this library) in:

On the implementation side, the areas of impact that I find are:

  • configuration / env: possibility to configure an array of valid "origins" (strings).
  • SharedPipes/CheckOriginSecure adaptation implementation for allow valid origins integration
  • SharedPipes/CheckRelyingPartyIdContained adaptation implementation for allow valid sources integration
  • Create pipe for attach rpId and add to Assertion\Creator\AssertionCreator pipeline

I looked at a previous "draft" of closed pr #61 which went in that direction, although in my opinion it can be simplified with a simple in_array (match string) to be able to also be used for other possible non-Android scenarios.
For example, for Android I would value with the already calculated android:apk-key-hash (e.g. android:apk-key-hash:hlbf0LpDSuQ3UpvvmFAMc1OhrD96549OYYOkGJKxJVs) instead of calculating the relevant fingerprint (see detail on the composition), in order to make everything simpler (avoid recalculations at each request) and not differentiate per os (possibly commands could be provided for os as helpers to generate the appropriate strings).

Although at the current state of the branches it seems to me that this feat is also compatible on 2.x, perhaps it is better to keep 3.x as the basis (I don't seem to see any conflicts but I don't understand the tests part).

Let me know what you think,
thanks.

Database Migration fails

PHP & Platform

PHP 8.1.7 (in Valet) (MAC OS dev)

Laravel version

9.17.0

Authenticator type

not applicable

OS and Browser versions

MAC OS Monterrey, FireFox (current)

Have you done this?

  • I am willing to share my stack trace and logs
  • I can reproduce this bug in isolation (vanilla Laravel install)
  • I can suggest a workaround as a Pull Request

Expectation

For the database migration to complete without error. I just ginned up a totally new clean install, installed breeze, created one user, then tried, same issue. MySQL: 10.7.3-MariaDB - Homebrew

Description

I get this error:

Illuminate\Database\QueryException

SQLSTATE[42000]: Syntax error or access violation: 1059 Identifier name 'webauthn_credentials_authenticatable_type_authenticatable_id_index' is too long (SQL: alter table webauthn_credentials add index webauthn_credentials_authenticatable_type_authenticatable_id_index(authenticatable_type, authenticatable_id))

at vendor/laravel/framework/src/Illuminate/Database/Connection.php:742
738▕ // If an exception occurs when attempting to run a query, we'll format the error
739▕ // message to include the bindings with SQL, which will make this exception a
740▕ // lot more helpful to the developer instead of just the database's errors.
741▕ catch (Exception $e) {
➜ 742▕ throw new QueryException(
743▕ $query, $this->prepareBindings($bindings), $e
744▕ );
745▕ }
746▕ }

  +9 vendor frames 

10 database/migrations/2022_06_17_161949_create_webauthn_credentials.php:25
Illuminate\Support\Facades\Facade::__callStatic("create")

  +22 vendor frames 

33 artisan:35
Illuminate\Foundation\Console\Kernel::handle(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))

Reproduction

not applicable

Stack trace & logs

Illuminate\Database\QueryException 

  SQLSTATE[42000]: Syntax error or access violation: 1059 Identifier name 'webauthn_credentials_authenticatable_type_authenticatable_id_index' is too long (SQL: alter table `webauthn_credentials` add index `webauthn_credentials_authenticatable_type_authenticatable_id_index`(`authenticatable_type`, `authenticatable_id`))

  at vendor/laravel/framework/src/Illuminate/Database/Connection.php:742
    738▕         // If an exception occurs when attempting to run a query, we'll format the error
    739▕         // message to include the bindings with SQL, which will make this exception a
    740▕         // lot more helpful to the developer instead of just the database's errors.
    741▕         catch (Exception $e) {
  ➜ 742▕             throw new QueryException(
    743▕                 $query, $this->prepareBindings($bindings), $e
    744▕             );
    745▕         }
    746▕     }

      +9 vendor frames 
  10  database/migrations/2022_06_17_161949_create_webauthn_credentials.php:25
      Illuminate\Support\Facades\Facade::__callStatic("create")

      +22 vendor frames 
  33  artisan:35
      Illuminate\Foundation\Console\Kernel::handle(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))

Attestation / Assertion objects

No response

Are you a Patreon supporter?

No, don't give priority to this

[X.x] What does happen that is considered an error or bug?

PHP & Platform

8.2.1 & MacOs

Database

No response

Laravel version

10

Have you done this?

  • I have checked my logs and I'm sure is a bug in this package.
  • I can reproduce this bug in isolation (vanilla Laravel install)
  • I can suggest a workaround as a Pull Request

Expectation

PhpStorm doesn't throw a warning

Description

When I add: // WebAuthn Routes WebAuthn::routes(); to my routes file, I get a warning: Class WebAuthn is marked as @internal

Reproduction

Import the routes in your routes files in Phpstorm

Stack trace & logs

No response

Problems on Nuxt.js Frontend, Attestation Error on different API URL

PHP & Platform

8.1.20 - Cent OS (Plesk)

Database

No response

Laravel version

9.0

Have you done this?

  • I have checked my logs and I'm sure is a bug in this package.
  • I can reproduce this bug in isolation (vanilla Laravel install)
  • I can suggest a workaround as a Pull Request

Expectation

No Error Message

Description

Sorry, I'm not sure wether this is an issue, but searching for help.
I'm using Nuxt.js Frontend and Laravel Backend, authenticated with Laravel Sanctum Cookie Based.
Frontend URL: example.com Backend URL: api.example.com, so Cookie Based Auth works fine here, but not Webauthn I get the Error "Attestation Error: Relying Party ID not scoped to current"

On localhost:3000 for frontend and 8010 for backend everything is working fine. I think the problem comes from difference in URL, but I don't know how to configure, for not getting this error. Is this possible?
Thanks!

Reproduction

Install Frontend and Backend on different URLs

Stack trace & logs

No response

alias parameter not stored in database

PHP & Platform

8.1.11 - Mac OS Ventura 13.2

Database

latest (docker)

Laravel version

9.19

Have you done this?

  • I have checked my logs and I'm sure is a bug in this package.
  • I can reproduce this bug in isolation (vanilla Laravel install)
  • I can suggest a workaround as a Pull Request
    I added the workaround/solution to this bug to #30 by mistake 😞

Expectation

alias should be reflected in the database but it is not.

Description

sadly the parameter seems to be dropped.

Reproduction

const register = event => {
    event.preventDefault()
    new WebAuthn().register({
        alias: 'some alias'
    }).then(response => alert('Registration successful!'));
};


alias is sent to the options request but dropped on the way to the register request.
stored credentials do not include the given alias.

vanilla backend as described in docs `WebAuthnRegisterController.php`:
```php
    public function options(AttestationRequest $request): Responsable
    {
        return $request
            ->fastRegistration()
//            ->userless()
//            ->allowDuplicates()
            ->toCreate();
    }

    public function register(AttestedRequest $request): Response
    {
        $request->save($request->input('alias'));
        return response()->noContent();
    }


### Stack trace & logs

_No response_

[1.2] Attestation Error: ByteBuffer: Invalid offset or length.

PHP & Platform

8.2.11 - Windows 21H2

Database

MariaDB 10.9.3

Laravel version

10.28

Have you done this?

  • I have checked my logs and I'm sure is a bug in this package.
  • I can reproduce this bug in isolation (vanilla Laravel install)
  • I can suggest a workaround as a Pull Request

Expectation

successful registration

Description

api returns an error response when registering through webauthn. i have an SPA with the package @simplewebauthn/browser.

i setup the default configs w/ breeze; sanctum, api

routes/auth.php

Route::prefix('webauth')
    ->middleware(['auth:sanctum'])
    ->group(function () {
        Route::get('/register/options', [WebAuthnRegisterController::class, 'options'])->name('webauthn.register.options');
        Route::post('/register', [WebAuthnRegisterController::class, 'register'])->name('webauthn.register');
    });

sample fetch:

register/options response

{
    "rp": {
        "name": "Laravel"
    },
    "authenticatorSelection": {
        "residentKey": "required",
        "requireResidentKey": true,
        "userVerification": "required"
    },
    "user": {
        "name": "[email protected]",
        "displayName": "Test User",
        "id": "671b1314af7a4ae59acb11bfe31da868"
    },
    "pubKeyCredParams": [
        {
            "type": "public-key",
            "alg": -7
        },
        {
            "type": "public-key",
            "alg": -257
        }
    ],
    "attestation": "none",
    "excludeCredentials": [],
    "timeout": 60000,
    "challenge": "B_nNl8II2S7rLFDkciHU2Q"
}

simplewebauthn startRegistration() payload to /register

{
  "id": "EBKcjOuGFLJkuKALSW4FxGGgVEKb_hQywCjx4Qa-i1E",
  "rawId": "EBKcjOuGFLJkuKALSW4FxGGgVEKb_hQywCjx4Qa-i1E",
  "response": {
    "attestationObject": "o2N ... AB",
    "clientDataJSON": "eyJ ... V9",
    "transports": [],
    "publicKeyAlgorithm": -257,
    "publicKey": "MI ... AB",
    "authenticatorData": "x8 ... AE"
  },
  "type": "public-key",
  "clientExtensionResults": {},
  "authenticatorAttachment": "platform"
}

/register response

{
    "message": [
        "Attestation Error: ByteBuffer: Invalid offset or length."
    ],
    "file": "\\vendor\\laravel\\framework\\src\\Illuminate\\Validation\\ValidationException.php",
    "line": 71,
    "trace": [
        {
            "file": "\\vendor\\laragear\\webauthn\\src\\Exceptions\\AttestationException.php",
            "line": 18,
            "function": "withMessages",
            "class": "Illuminate\\Validation\\ValidationException",
 ...

* also tested on w11, but the same error happens.

Full stack trace: https://pastebin.com/raw/9UfA1GxT

excuse my poor understanding of all these. many thanks in advance

[2.0] Make this package ORM agnostic

Please check these requirements

  • This feature helps everyone using this package
  • It's feasible and maintainable
  • It's non breaking
  • I issued a PR with the implementation (optional)

Description

Hi @DarkGhostHunter ,

I've been using your Larapass package, since it's abandoned now I tried to use this one.
I'm using Laravel-doctrine/orm package which means that I'm not relaying on Eloquent.

With previous package I was able to easily implement contracts, etc. but since current contracts are forcing return types to strictly Eloquent related, I'm not able to implement contracts without Model usage.

I think it's mostly about WebAuthnAuthenticatable contract and it's webAuthnCredentials returning MorphMany which I'm not able to use in doctrine world :)

Are there any plans to make this package more open ?

Thanks

Code sample

interface WebAuthnAuthenticatable
{
    /**
     * Returns displayable data to be used to create WebAuthn Credentials.
     *
     * @return array{name: string, displayName: string}
     */
    public function webAuthnData(): array;

    /**
     * Removes all credentials previously registered.
     *
     * @param  string  ...$except
     * @return void
     */
    public function flushCredentials(string ...$except): void;

    /**
     * Disables all credentials for the user.
     *
     * @param  string  ...$except
     * @return void
     */
    public function disableAllCredentials(string ...$except): void;

    /**
     * Makes an instance of a WebAuthn Credential attached to this user.
     *
     * @param  array  $properties
     * @return \Laragear\WebAuthn\Models\WebAuthnCredential
     */
    public function makeWebAuthnCredential(array $properties): WebAuthnCredential;

    /**
     * Returns a queryable relationship for its WebAuthn Credentials.
     *
     * @phpstan-ignore-next-line
     * @return \Illuminate\Database\Eloquent\Relations\MorphMany|\Laragear\WebAuthn\Models\WebAuthnCredential
     */
    public function webAuthnCredentials(): MorphMany;
}

[1.2] SQLSTATE[01000]: Warning: 1265 Data truncated for column 'authenticatable_id' at row 1

PHP & Platform

8.1.23 - Ubuntu 22.04.3 LTS WSL

Database

MySQL 8.0.33-0ubuntu0.22.04.4

Laravel version

10.10

Have you done this?

  • I have checked my logs and I'm sure is a bug in this package. - [X] I can reproduce this bug in isolation (vanilla Laravel install) - [ ] I can suggest a workaround as a Pull Request

Expectation

Just installed this package did not change anything, following the documentation, attestation does not work and I need help . Thanks :)

Description

[2023-09-24 08:12:33] local.ERROR: SQLSTATE[01000]: Warning: 1265 Data truncated for column 'authenticatable_id' at row 1 (Connection: mysql, SQL: insert into webauthn_credentials (authenticatable_id, authenticatable_type, id, user_id, alias, counter, rp_id, origin, transports, aaguid, public_key, attestation_format, updated_at, created_at) value (detail in the stack logs).

I used ulid instead of id in the users table.

Reproduction

async function func_registerkey () { new WebAuthn().register().then(response => { alert('Registration successful!') }).catch(error => { console.log(error); alert('Something went wrong, try again!') }); }

Stack trace & logs

shell [2023-09-24 08:12:33] local.ERROR: SQLSTATE[01000]: Warning: 1265 Data truncated for column 'authenticatable_id' at row 1 (Connection: mysql, SQL: insert into `webauthn_credentials` (`authenticatable_id`, `authenticatable_type`, `id`, `user_id`, `alias`, `counter`, `rp_id`, `origin`, `transports`, `aaguid`, `public_key`, `attestation_format`, `updated_at`, `created_at`) values (01hanwjtzrrn2mf7wfpa9ns6jb, App\Models\User, Ki0iUkN3NYMdIQnQxRgyGQ, c9b23794b1fa48cdb9f6b31440c28958, ?, 0, https://dev.test, https://dev.test, ?, 531126d6-e717-415c-9320-3d9aa6981239, eyJpdiI6InlaS2ZPdmxuL1cxajdPdWRYcHk2OVE9PSIsInZhbHVlIjoiU3owM3RuNy9nZCtHRi80dDlUMVFPZ0FCUFAvamZUYlJDL0ZXY0cyS0ZZS3dLdTdWTUVNUVU2dURTbXJrRlZ2Skw5RFQ5dUltanBoV1ZkWllMSUJIYXBmQTlHS2w0b3dLWCt3T1RsUXp0UXhrc0xXQkpqamZPZ3kvcFVVY0hWVVRwZTkrVU1VSjlnUzNXQ2dBR1lVT0F5ZHRLOUVMZWdoM0NJWGYwY1U5OEtWellLNkt1Yk5sRVV6WmdFM3RZVTg0MDhMVGNpR29kSUZmUk90dy94TkJNV3VNVUJWTDdPYjdTaTV5SG9mRzh2aFdXWW83S2Q5YVpDUlh3VlU2VWpyTiIsIm1hYyI6ImQ2NTcxZWY1YzQxYzA3NTIzNDY3ZWY1NmQ1ZWQ1MDUyZDQyYzZjYzg4ZDI5YjE1OWQyZTI5ODE3YjY4ZWRkM2YiLCJ0YWciOiIifQ==, none, 2023-09-24 08:12:33, 2023-09-24 08:12:33)) {"userId":"01hanwjtzrrn2mf7wfpa9ns6jb","exception":"[object] (Illuminate\\Database\\QueryException(code: 01000): SQLSTATE[01000]: Warning: 1265 Data truncated for column 'authenticatable_id' at row 1 (Connection: mysql, SQL: insert into `webauthn_credentials` (`authenticatable_id`, `authenticatable_type`, `id`, `user_id`, `alias`, `counter`, `rp_id`, `origin`, `transports`, `aaguid`, `public_key`, `attestation_format`, `updated_at`, `created_at`) values (01hanwjtzrrn2mf7wfpa9ns6jb, App\\Models\\User, Ki0iUkN3NYMdIQnQxRgyGQ, c9b23794b1fa48cdb9f6b31440c28958, ?, 0, https://dev.test, https://dev.test, ?, 531126d6-e717-415c-9320-3d9aa6981239, eyJpdiI6InlaS2ZPdmxuL1cxajdPdWRYcHk2OVE9PSIsInZhbHVlIjoiU3owM3RuNy9nZCtHRi80dDlUMVFPZ0FCUFAvamZUYlJDL0ZXY0cyS0ZZS3dLdTdWTUVNUVU2dURTbXJrRlZ2Skw5RFQ5dUltanBoV1ZkWllMSUJIYXBmQTlHS2w0b3dLWCt3T1RsUXp0UXhrc0xXQkpqamZPZ3kvcFVVY0hWVVRwZTkrVU1VSjlnUzNXQ2dBR1lVT0F5ZHRLOUVMZWdoM0NJWGYwY1U5OEtWellLNkt1Yk5sRVV6WmdFM3RZVTg0MDhMVGNpR29kSUZmUk90dy94TkJNV3VNVUJWTDdPYjdTaTV5SG9mRzh2aFdXWW83S2Q5YVpDUlh3VlU2VWpyTiIsIm1hYyI6ImQ2NTcxZWY1YzQxYzA3NTIzNDY3ZWY1NmQ1ZWQ1MDUyZDQyYzZjYzg4ZDI5YjE1OWQyZTI5ODE3YjY4ZWRkM2YiLCJ0YWciOiIifQ==, none, 2023-09-24 08:12:33, 2023-09-24 08:12:33)) at /var/www/projects/evangeline/vendor/laravel/framework/src/Illuminate/Database/Connection.php:801) [stacktrace] #0 /var/www/projects/evangeline/vendor/laravel/framework/src/Illuminate/Database/Connection.php(755): Illuminate\\Database\\Connection->runQueryCallback() #1 /var/www/projects/evangeline/vendor/laravel/framework/src/Illuminate/Database/Connection.php(581): Illuminate\\Database\\Connection->run() #2 /var/www/projects/evangeline/vendor/laravel/framework/src/Illuminate/Database/Connection.php(533): Illuminate\\Database\\Connection->statement() #3 /var/www/projects/evangeline/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php(3338): Illuminate\\Database\\Connection->insert() #4 /var/www/projects/evangeline/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php(1968): Illuminate\\Database\\Query\\Builder->insert() #5 /var/www/projects/evangeline/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php(1309): Illuminate\\Database\\Eloquent\\Builder->__call() #6 /var/www/projects/evangeline/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php(1137): Illuminate\\Database\\Eloquent\\Model->performInsert() #7 /var/www/projects/evangeline/vendor/laragear/webauthn/src/Http/Requests/AttestedRequest.php(83): Illuminate\\Database\\Eloquent\\Model->sav

[1.2] Unable to Login

PHP & Platform

8.2.8 - MacOS

Database

MySQL 8.0.27

Laravel version

10

Have you done this?

  • I have checked my logs and I'm sure is a bug in this package.
  • I can reproduce this bug in isolation (vanilla Laravel install)
  • I can suggest a workaround as a Pull Request

Expectation

when i try to login. but it prompt the error below.

Description

Prompt my user table missing column. i just migrate the default db souce from package

Reproduction

i was used the default function from package
public function login(AssertedRequest $request): Response
 {
     return response()->noContent($request->login('admin') ? 204 : 422);
 }

Stack trace & logs

Column not found: 1054 Unknown column 'rawId' in 'where clause' (Connection: mysql, SQL: select * from table where id = IUFjLK-rfAh7U3oV_zP_uJ6YW3umWXo3fipCY3HE6Ho and rawId = IUFjLK+rfAh7U3oV/zP/uJ6YW3umWXo3fipCY3HE6Ho= and response in (u2ghoRLl52c6KTnfJU/7W3nVPzrVuUA/98mAWWV0hw8FAAAAAA==, eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiX2QtNC1tVjJfUEVueUFTZkJHNnBRdyIsIm9yaWdpbiI6Imh0dHBzOi8vYmFzZS10ZW1wbGF0ZS50ZXN0IiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQ==, MEYCIQDhaC21Gjmin6oB9tyvrQJSsrukRNcscvd8s36HRxrT+AIhAL3RS6G4idm6mgfoWeak8SIgtJla++A4saiAznGWV6ih, a07bc26051d04b909c9dfce9f9068255) and type = public-key limit 1)

[1.0] ReferenceError: WebAuthn is not defined

PHP & Platform

8.2.2

Database

No response

Laravel version

10.13.5

Have you done this?

  • I have checked my logs and I'm sure is a bug in this package.
  • I can reproduce this bug in isolation (vanilla Laravel install)
  • I can suggest a workaround as a Pull Request

Expectation

When I click the "register" or "login" buttons the WebAuthn class is initialised and run.

Description

The ReferenceError: WebAuthn is not defined error is thrown.

I suspect this is due to Vite loading the script as type="module" and so the WebAuthn class is in a different scope.

Reproduction

after adding the code from the documentation:


<html>
<head>
@vite(['resources/js/app.js', 'resources/js/vendor/webauthn/webauthn.js'])
</head>
<body>
<form id="register-form">
    <button type="submit">Register authenticator</button>
</form>

<!-- Registering credentials -->
<script>
    const register = event => {
        event.preventDefault()

        new WebAuthn().register()
          .then(response => alert('Registration successful!'))
          .catch(error => alert('Something went wrong, try again!'))
    }

    document.getElementById('register-form').addEventListener('submit', register)
</script>
</body>
</html>


click the `Register authenticator` button.

Stack trace & logs

No response

POST https://domain/webauthn/register 422 at webauthn.js:159

Please check these requirements

  • This feature helps everyone using this package
  • It's feasible and maintainable
  • It's non breaking
  • I issued a PR with the implementation (optional)

Description

Please i am trying to use webauthn in my app i had to upgrade to laravel 9 to user Laragear/WebAuthn because i was unable to set up Larapass and i can't create any issue there.

Wel Successfull Set WebAuthn but having Issue registering Device...
I get 422 Error Code

And when I tried publishing the config file.

php artisan vendor:publish --provider="Laragear\WebAuthn\WebAuthnServiceProvider" --tag="config"

It doesn't work.

Please I need guidelines on how to successfully set it up

Code sample

public function register(AttestedRequest $request): Response
    {
        $request->save();

        return response()->noContent();
    }

[2.x] WebAuthn Recovery by email

Please check these requirements

  • This feature helps everyone using this package
  • It's feasible and maintainable
  • It's non breaking
  • I issued a PR with the implementation (optional)

Description

Hi dev team, i have been using the old package Larapass i guess, it was working perfectly.

What about implementing it in this also?

Code sample

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use DarkGhostHunter\Larapass\Http\SendsWebAuthnRecoveryEmail;

class WebAuthnDeviceLostController extends Controller


and 

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use DarkGhostHunter\Larapass\Http\RecoversWebAuthn;

class WebAuthnRecoveryController extends Controller
{
    use RecoversWebAuthn;

[3.x] Add support for CarbonImmutable

Please check these requirements

  • This feature helps everyone using this package
  • It's feasible and maintainable
  • It's non breaking
  • I issued a PR with the implementation (optional)

Description

Currently the method expiresAt of class Laragear\WebAuthn\Challenge\Challenge expects the return type to be Carbon. And since it uses Date facade by default it will return Carbon only. But for projects where Date facade uses CarbonImmutable handler using Date::use(CarbonImmutable::class); this method will break as the return value from Date facade will be an instance of CarbonImmutable. We can add the return type of expiresAt as CarbonInterface to support both Carbon and CarbonImmutable.

Feel free to close this issue if it is of not that importance.

Code sample

use Carbon\CarbonInterface;

public function expiresAt(): CarbonInterface
{
    return Date::createFromTimestamp($this->expiresAt);
}

[1.1.2] Routes namespace issue

PHP & Platform

8.1.6 - Windows 11

Database

MySQL 8.0.27

Laravel version

9.33.0

Have you done this?

  • I have checked my logs and I'm sure is a bug in this package.
  • I can reproduce this bug in isolation (vanilla Laravel install)
  • I can suggest a workaround as a Pull Request

Expectation

On new install, have in my /routes/web

use Laragear\WebAuthn\WebAuthn;
WebAuthn::routes();

When I try to use the register/options route, I get a 500 error

Description

In the log, I get this:

[2022-10-04 01:07:58] local.ERROR: Target class [App\Http\Controllers\App\Http\Controllers\WebAuthn\WebAuthnRegisterController] does not exist. {"exception":"[object] (Illuminate\\Contracts\\Container\\BindingResolutionException(code: 0): Target class [App\\Http\\Controllers\\App\\Http\\Controllers\\WebAuthn\\WebAuthnRegisterController] does not exist. at C:\\wamp\\www\\station\\vendor\\laravel\\framework\\src\\Illuminate\\Container\\Container.php:877)
[stacktrace]

It appears that there is a namespacing issue because App\Http\Controllers is repeated.

For example, when I put the following in my web.php file, it works:

Route::controller(WebAuthnRegisterController::class)->middleware('web')->prefix('webauthn')->group(function () {
  Route::post('register/options', 'options')->name('webauthn.register.options');
  Route::post('register', 'register')->name('webauthn.register');
});
Route::controller(WebAuthnLoginController::class)->middleware('web')->prefix('webauthn')->group(function () {
  Route::post('login/options', 'options')->name('webauthn.login.options');
  Route::post('login', 'login')->name('webauthn.login');
});

Reproduction

As above

Stack trace & logs

as above

[3.x] Set in the session which device is used for login

Please check these requirements

  • This feature helps everyone using this package
  • It's feasible and maintainable
  • It's non breaking
  • I issued a PR with the implementation (optional)

Description

When a user logs in via Assertion, a session key should be set with the ID of the credential used to login via assertion.

This would help developers to check which device the user is using, and reject disabling the current device by mistake by simple id comparison: If the Credential ID is equal to the Session Credential ID, don't allow to disable it.

Implementation should be in the guard. Assertion pipelines should be kept as-is as the feature is authentication-session-specific.

Code sample

/**
     * Validate the WebAuthn assertion.
     */
    protected function validateWebAuthn(WebAuthnAuthenticatable $user, array $credentials): bool
    {
        try {
            // When we hit this method, we already have the user for the credential, so we will
            // pass it to the Assertion Validation data, thus avoiding fetching it again.
            $this->validator
                ->send(new AssertionValidation(new JsonTransport($credentials), $user))
                ->thenReturn();
        } catch (AssertionException $e) {
            // If we're debugging, like under local development, push the error to the logger.
            if (config('app.debug')) {
                logger($e->getMessage());
            }

            return false;
        }

+        // If the dev has ID-on-session enabled, set it.
+        if (config('webauthn.auth.set_id')) {
+            $this->session->put(
+                config('webauthn.auth.session_key', '_webauthn.credential_id'),
+                $credentials['id']
+            ):
+        }

        return true;
    }

[2.x] Update readme to use `WebAuthnLoginController` and `WebAuthnRegisterController`

PHP & Platform

8.1.2 & Ubuntu

Database

No response

Laravel version

9

Have you done this?

  • I have checked my logs and I'm sure is a bug in this package.
  • I can reproduce this bug in isolation (vanilla Laravel install)
  • I can suggest a workaround as a Pull Request

Expectation

Description

Hey Guys,

The README file talks about an AttestationController file. This is not created during installation.

  1. How do I create these?
  2. How does Laravel know to speak to this controller?

Reproduction

-

Stack trace & logs

No response

Assertion Error: User ID is not owner of the stored credential. Login Error

PHP & Platform

8.2

Database

No response

Laravel version

10 [Latest]

Have you done this?

  • I have checked my logs and I'm sure is a bug in this package.
  • I can reproduce this bug in isolation (vanilla Laravel install)
  • I can suggest a workaround as a Pull Request

Expectation

I want to login.

Description

When I attempted to log in using my Android device with a QR code, an error message appeared is Laravel Log files:

"Assertion Error: User ID is not the owner of the stored credential."

The userHandle is null. I tried the codes in the PRs and the Android null repository.

Reproduction

...

Stack trace & logs

No response

[1.x] Php function class_implements() used improperly.

PHP & Platform

Laravel version

Authenticator type

No response

OS and Browser versions

No response

Have you done this?

  • I am willing to share my stack trace and logs
  • I can reproduce this bug in isolation (vanilla Laravel install)
  • I can suggest a workaround as a Pull Request

Expectation

Code being used with respect to manual ? 😅

Description

https://github.com/Laragear/WebAuthn/blob/1.x/src/Auth/WebAuthnUserProvider.php#L49

class_implements($this->model, WebAuthnAuthenticatable::class)

https://www.php.net/manual/en/function.class-implements.php

class_implements(object|string $object_or_class, bool $autoload = true): array|false
This function returns an array with the names of the interfaces that the given object_or_class and its parents implement.

Reproduction

See above.

Stack trace & logs

No response

Attestation / Assertion objects

No response

Are you a Patreon supporter?

No, don't give priority to this

[3.x] Signing

Please check these requirements

  • This feature helps everyone using this package
  • It's feasible and maintainable
  • It's non breaking
  • I issued a PR with the implementation (optional)

Description

"While WebAuthn was designed primarily for authentication, the framework can be extended to support signing with keypairs generated on authenticators without requiring custom clients for end users."

I found this functionality very interesting, the flow is described here:

"In short, the registration and authentication events consists of a cryptographic signing process with the public key returned, which is then used to sign a data set which is provided by the relying party. Replacing the randomly generated challenge with a hash of a file to be signed allows this signature flow to be applied to actual documentation, with the public key also available for verification after the fact."

Currently the related discuss and progress in this regard under webauthn / webcrypto are in a sort of "limbo" (see w3c/webauthn#1608 and related w3c/webcrypto#263) so I have the thought there would be room for use currently.

Interesting also this caveats to keep in mind.

On the implementation side, I think it would be enough to give the possibility to inject the ByteBuffer into AssertionCreation and then use it as an alternative for the creation of the Challenge (instead of random).

What do you think about it?

Code sample

$assertion = app()->make(AssertionCreator::class)
 ->send(new AssertionCreation(
  user: null,
  userVerification: UserVerification::DISCOURAGED,
  challengeData: new ByteBuffer('...hash_of_a_file...')
 ))
 ->then(static function (AssertionCreation $creation): Responsable {
  return $creation->json;
 });

[1.x] XCRF token are ignored.

PHP & Platform

8.1.5 - Debian

Laravel version

9.2

Authenticator type

No response

OS and Browser versions

No response

Have you done this?

  • I am willing to share my stack trace and logs
  • I can reproduce this bug in isolation (vanilla Laravel install)
  • I can suggest a workaround as a Pull Request

Expectation

CSRF are not always provided, sometimes a XCRF token is provided in the cookies headers. Those should be supported.

Description

Larapass was supporting XCRF tokens:
https://github.com/DarkGhostHunter/Larapass/blob/master/resources/js/larapass.js#L85

Reproduction

Use Laragear with any Laravel installation without using @csrf in meta or form.

Stack trace & logs

No response

Attestation / Assertion objects

No response

Are you a Patreon supporter?

No, don't give priority to this

POST /webauthn/register 422 at webauthn.js:159

HELP

Please i am trying to use webauthn in my app i had to upgrade to laravel 9 to user Laragear/WebAuthn because i was unable to set up Larapass and i can't create any issue there.

Wel Successfull Set WebAuthn but having Issue registering Device...
I get 422 Error Code

Actually need Help is not a bug

[1.2] Incompatibility with User ID that uses ULID.

PHP & Platform

8.1.2 - Ubuntu 22.04 WSL

Database

MySQL 8.0.28

Laravel version

10.10

Have you done this?

  • I have checked my logs and I'm sure is a bug in this package.
  • I can reproduce this bug in isolation (vanilla Laravel install)
  • I can suggest a workaround as a Pull Request

Expectation

How to change the webauthn_credentials.authenticatable_id data type?

image
image

I can change it manually after the migration is done but how can I change it in the migration file instead? Is it possible?

$table->morphs('authenticatable', 'webauthn_user_index');

Thanks.

Description

I use ULID as ID (primary key) in the users table and everytime I tried to attest it is failed because the authenticatable_id column in the webauthn_credentials table does not match with the data type of my users table's ID column, how can I change the authenticatable_id to match the users.id data type which is CHAR(26)?

Thanks.

Reproduction

.

Stack trace & logs

No response

[1.2.1] No way to handle validation errors

PHP & Platform

8.2.2

Database

No response

Laravel version

10.13.5

Have you done this?

  • I have checked my logs and I'm sure is a bug in this package.
  • I can reproduce this bug in isolation (vanilla Laravel install)
  • I can suggest a workaround as a Pull Request

Expectation

On a login/register options request, if validation fails, errors should be availabe in the catch(resposne => {}) method.

Description

On a login/register options request, if validation fails, there is no way to display the specific errors to the user.

Reproduction

new WebAuthn().login({
    email: 'not a valid email',
}, {
    remember: 'on',
}).then(response => {
    // 
})
    .catch(response => {

    if (reponse.status === 422) {

        // response.errors should be an array of errors, and response.message should be the main validation message

        let errors = response.errors;
        for (let key in errors) {
            document.getElementById(key).setCustomValidity(errors[key][0]);
            document.getElementById(key).reportValidity();
        }

        return;
    }

})

Stack trace & logs

No response

[1.x] Too long id (of Nitrokey) cause MySQL error

PHP & Platform

8.1.11 - Windows 10

Database

MySQL 8.0.30

Laravel version

9.36.3

Have you done this?

  • I have checked my logs and I'm sure is a bug in this package.
  • I can reproduce this bug in isolation (vanilla Laravel install)
  • I can suggest a workaround as a Pull Request

Expectation

When registering a security key, the attestation is stored to db.

Description

When registering a Nitrokey, an error occurs when attestation is written to MySQL db because of a too long index (298 chars!)

This Nitrokey does not validate the assumption made here: DarkGhostHunter/Larapass#14 (comment)

Reproduction

// Using the WebAuthn repository tests.
// Change the CREDENTIAL_ID to a 255+ long string
// Then run the Tests\Http\Requests\AttestedRequestTest tests

class FakeAuthenticator
{
    public const CREDENTIAL_ID = 'owBYu_waGLhAOCg4EFzi6Lr55x51G2dR5yhJi8q2C3tgZQQL2aEi-nK3I54J6ILj70pJzR_6QxvA5XER17d7NA9EFe2QH3VoJYQGpO8G5yDoFQvsdkxNhioyMyhyQHNrAgTMGyfigIMCfhjk9te7LNYl9K5GbWRc4TGeQl1vROjBtTNm3GdpEOqp9RijWd-ShQZ95eHoc8SA_-8vzCyfmy-wI_K4ZqlQNNl85Fzg2GIBcC2zvcJhLYy1A2kw6JoBTAmz1ZCCgkTKWhzUvAJQpMpu40M67FqE0WkGZfSJ9A';    
    public const CREDENTIAL_ID_RAW = 'owBYu/waGLhAOCg4EFzi6Lr55x51G2dR5yhJi8q2C3tgZQQL2aEi+nK3I54J6ILj70pJzR/6QxvA5XER17d7NA9EFe2QH3VoJYQGpO8G5yDoFQvsdkxNhioyMyhyQHNrAgTMGyfigIMCfhjk9te7LNYl9K5GbWRc4TGeQl1vROjBtTNm3GdpEOqp9RijWd+ShQZ95eHoc8SA/+8vzCyfmy+wI/K4ZqlQNNl85Fzg2GIBcC2zvcJhLYy1A2kw6JoBTAmz1ZCCgkTKWhzUvAJQpMpu40M67FqE0WkGZfSJ9A=';

// ...
}

Stack trace & logs

// Error in VS Code during the execution of Tests\Http\Requests\AttestedRequestTest tests

node:events:504
      throw er; // Unhandled 'error' event
      ^

Error: ENOENT: no such file or directory, open 'd:\ SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'id' at row 1 in ...\WebAuthn\vendor\laravel\framework\src\Illuminate\Database\Connection.php'
Emitted 'error' event on Interface instance at:
    at ReadStream.onerror (node:readline:265:10)
    at ReadStream.emit (node:events:526:28)
    at emitErrorNT (node:internal/streams/destroy:157:8)
    at emitErrorCloseNT (node:internal/streams/destroy:122:3)
    at process.processTicksAndRejections (node:internal/process/task_queues:83:21) {
  errno: -4058,
  code: 'ENOENT',
  syscall: 'open',
  path: "d:\\ SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'id' at row 1 in ...\\WebAuthn\\vendor\\laravel\\framework\\src\\Illuminate\\Database\\Connection.php"
}

// In production stacktrace (from https://github.com/Bubka/2FAuth/issues/166#issuecomment-1460991223)
[2023-03-01 12:24:10] local.ERROR: SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'id' at row 1 (SQL: insert into `web_authn_credentials` (`id`, `user_handle`, `type`, `transports`, `attestation_type`, `trust_path`, `aaguid`, `public_key`, `counter`, `user_id`, `updated_at`, `created_at`) values (owBYu_waGLhAOCg4EFzi6Lr55x51G2bhCQIYNOXkC3tgZQQL2aEi-nK3I54J6ILj70pJzR_6QxvA5XER17d7NA9EFe2QH3VoJYQGpO8G5yDoFQvsdkxNhioyMyhyQHNrAgTMGyfigIMCfhjk9te7LNYl9K5GbWRc4TGeQl1vROjBtTNm3GdpEOqp9RijWd-ShQZ95eHoc8SA_-8vzCyfmy-wI_K4ZqlQNNl85Fzg2GIBcC2zvcJhLYy1A2kw6JoBTAmz1ZCCgkTKWhzUvAJQpMpu40M67FqE0WkGZfSJ9A, c6d01c4c-95fb-4e39-ab4f-78be47568837, public-key, [], none, {"type":"Webauthn\\TrustPath\\EmptyTrustPath"}, 00000000-0000-0000-0000-000000000000, XXXXXXXXXXXXXXXXX, 25, 1, 2023-03-01 12:24:10, 2023-03-01 12:24:10)) {"userId":1,"exception":"[object] (Illuminate\\Database\\QueryException(code: 22001): SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'id' at row 1 (SQL: insert into `web_authn_credentials` (`id`, `user_handle`, `type`, `transports`, `attestation_type`, `trust_path`, `aaguid`, `public_key`, `counter`, `user_id`, `updated_at`, `created_at`) values (owBYu_waGLhAOCg4EFzi6Lr55x51G2bhCQIYNOXkC3tgZQQL2aEi-nK3I54J6ILj70pJzR_6QxvA5XER17d7NA9EFe2QH3VoJYQGpO8G5yDoFQvsdkxNhioyMyhyQHNrAgTMGyfigIMCfhjk9te7LNYl9K5GbWRc4TGeQl1vROjBtTNm3GdpEOqp9RijWd-ShQZ95eHoc8SA_-8vzCyfmy-wI_K4ZqlQNNl85Fzg2GIBcC2zvcJhLYy1A2kw6JoBTAmz1ZCCgkTKWhzUvAJQpMpu40M67FqE0WkGZfSJ9A, c6d01c4c-95fb-4e39-ab4f-78be47568837, public-key, [], none, {\"type\":\"Webauthn\\\\TrustPath\\\\EmptyTrustPath\"}, 00000000-0000-0000-0000-000000000000, XXXXXXXXXXXXXXXXXXXX, 25, 1, 2023-03-01 12:24:10, 2023-03-01 12:24:10)) at /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Database/Connection.php:712)
[stacktrace]
#0 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Database/Connection.php(672): Illuminate\\Database\\Connection->runQueryCallback()
#1 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Database/Connection.php(502): Illuminate\\Database\\Connection->run()
#2 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Database/Connection.php(454): Illuminate\\Database\\Connection->statement()
#3 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php(2980): Illuminate\\Database\\Connection->insert()
#4 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php(1657): Illuminate\\Database\\Query\\Builder->insert()
#5 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php(1164): Illuminate\\Database\\Eloquent\\Builder->__call()
#6 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php(994): Illuminate\\Database\\Eloquent\\Model->performInsert()
#7 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php(267): Illuminate\\Database\\Eloquent\\Model->save()
#8 /var/www/2fauth/vendor/darkghosthunter/larapass/src/WebAuthnAuthentication.php(93): Illuminate\\Database\\Eloquent\\Relations\\HasOneOrMany->save()
#9 /var/www/2fauth/vendor/darkghosthunter/larapass/src/Http/RegistersWebAuthn.php(47): App\\Models\\User->addCredential()
#10 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): App\\Http\\Controllers\\Auth\\WebAuthnRegisterController->register()
#11 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(45): Illuminate\\Routing\\Controller->callAction()
#12 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Routing/Route.php(262): Illuminate\\Routing\\ControllerDispatcher->dispatch()
#13 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Routing/Route.php(205): Illuminate\\Routing\\Route->runController()
#14 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Routing/Router.php(721): Illuminate\\Routing\\Route->run()
#15 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(128): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()
#16 /var/www/2fauth/app/Http/Middleware/RejectIfReverseProxy.php(26): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#17 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): App\\Http\\Middleware\\RejectIfReverseProxy->handle()
#18 /var/www/2fauth/app/Http/Middleware/KickOutInactiveUser.php(53): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#19 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): App\\Http\\Middleware\\KickOutInactiveUser->handle()
#20 /var/www/2fauth/app/Http/Middleware/LogUserLastSeen.php(35): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#21 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): App\\Http\\Middleware\\LogUserLastSeen->handle()
#22 /var/www/2fauth/vendor/laravel/passport/src/Http/Middleware/CreateFreshApiToken.php(50): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#23 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Laravel\\Passport\\Http\\Middleware\\CreateFreshApiToken->handle()
#24 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(50): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#25 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Routing\\Middleware\\SubstituteBindings->handle()
#26 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Auth/Middleware/Authenticate.php(44): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#27 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Auth\\Middleware\\Authenticate->handle()
#28 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(78): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#29 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken->handle()
#30 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(121): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#31 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(64): Illuminate\\Session\\Middleware\\StartSession->handleStatefulRequest()
#32 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Session\\Middleware\\StartSession->handle()
#33 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php(37): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#34 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse->handle()
#35 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php(67): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#36 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Cookie\\Middleware\\EncryptCookies->handle()
#37 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(103): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#38 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Routing/Router.php(723): Illuminate\\Pipeline\\Pipeline->then()
#39 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Routing/Router.php(698): Illuminate\\Routing\\Router->runRouteWithinStack()
#40 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Routing/Router.php(662): Illuminate\\Routing\\Router->runRoute()
#41 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Routing/Router.php(651): Illuminate\\Routing\\Router->dispatchToRoute()
#42 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(167): Illuminate\\Routing\\Router->dispatch()
#43 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(128): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()
#44 /var/www/2fauth/app/Http/Middleware/ForceJsonResponse.php(20): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#45 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): App\\Http\\Middleware\\ForceJsonResponse->handle()
#46 /var/www/2fauth/app/Http/Middleware/SetLanguage.php(54): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#47 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): App\\Http\\Middleware\\SetLanguage->handle()
#48 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#49 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(31): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()
#50 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle()
#51 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#52 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(40): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()
#53 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle()
#54 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#55 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize->handle()
#56 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(86): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#57 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle()
#58 /var/www/2fauth/vendor/fruitcake/laravel-cors/src/HandleCors.php(38): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#59 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Fruitcake\\Cors\\HandleCors->handle()
#60 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php(39): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#61 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Http\\Middleware\\TrustProxies->handle()
#62 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(103): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#63 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(142): Illuminate\\Pipeline\\Pipeline->then()
#64 /var/www/2fauth/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(111): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()
#65 /var/www/2fauth/public/index.php(73): Illuminate\\Foundation\\Http\\Kernel->handle()
#66 {main}

[1.2.1] userHandle is null on webauthn.ts

PHP & Platform

8.1.3 & MacOs

Database

MYSQL 5

Laravel version

Laravel 9

Have you done this?

  • I have checked my logs and I'm sure is a bug in this package.
  • I can reproduce this bug in isolation (vanilla Laravel install)
  • I can suggest a workaround as a Pull Request

Expectation

When you login with credentials, it should log the user in

Description

I get a 422 error on login

Reproduction

Setup back and frontend and try to login.

Stack trace & logs

I was able to pinpoint the issue. In the webauthn file, in the login method:


async login(request = {}, response = {}) {
        const optionsResponse = await this.#fetch(
            request,
            this.routes.loginOptions
        );
        const json = await optionsResponse.json();
        const publicKey = this.#parseIncomingServerOptions(json);
        const credentials = await navigator.credentials.get({ publicKey });
        console.log(credentials);
        const publicKeyCredential = this.#parseOutgoingCredentials(credentials);

        Object.assign(publicKeyCredential, response);

        return await this.#fetch(
            publicKeyCredential,
            this.routes.login,
            response
        ).then(WebAuthn.#handleResponse);
    }

THe credentials has a userHandle of null, so it fails the check in the backend eventually.

Edit:
I checked that during creation, the userHandle is not even set by the navigator.

In the documentation, it does say it maybe be null:

For [navigator.credentials.create()](https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/create) calls made with a non-empty allowCredentials properties, the returned userHandle may be null.

https://developer.mozilla.org/en-US/docs/Web/API/AuthenticatorAssertionResponse/userHandle

[1.0] WebAuthn().register() throw 403 error.

PHP & Platform

8.2.2

Database

No response

Laravel version

10.13.5

Have you done this?

  • I have checked my logs and I'm sure is a bug in this package.
  • I can reproduce this bug in isolation (vanilla Laravel install)
  • I can suggest a workaround as a Pull Request

Expectation

When calling new WebAuthn().register() the request to /webauthn/register/options should succeed.

Description

But is does not:

image

Reproduction

On a Fresh install,

update vite.config.js

-input: ['resources/css/app.css', 'resources/js/app.js'],
+input: ['resources/css/app.css', 'resources/js/app.js', 'resources/js/webauthn/webauthn.js'],

add WebAuthn::routes(); to routes/web.php,

then create a page like:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title></title>
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <link rel="preconnect" href="https://fonts.bunny.net">
    @vite('resources/js/app.js')
    <script src="{{ Vite::asset('/resources/js/vendor/webauthn/webauthn.js') }}"></script>
</head>
<body>
    <main>
        <form id="register-form">
            <button type="submit" value="Register authenticator">Register authenticator</button>
        </form>

<script defer>
if (WebAuthn.doesntSupportWebAuthn()) {
    alert('Your device is not secure enough to use this site!');
}

const register = event => {
    event.preventDefault()

    new WebAuthn().register()
      .then(response => alert('Registration successful!'))
      .catch(error => alert('Something went wrong, try again!'))
}

document.getElementById('register-form').addEventListener('submit', register)
</script>

    </main>
</body>
</html>

load the page page and click the button. 💥

Stack trace & logs

No response

Attestation Error: Challenge does not exist.

PHP & Platform

PHP 8.3 - Ubuntu 22.04

Database

Mysql 8

Laravel version

11

Have you done this?

  • I have checked my logs and I'm sure is a bug in this package.
  • I can reproduce this bug in isolation (vanilla Laravel install)
  • I can suggest a workaround as a Pull Request

Expectation

Attestation Error: Challenge does not exist.

Description

I did everything exactly as you described in the installation steps. In Laravel 10 I installed version 1.x of this package without any problems, but in Laravel 11 and Webauthn 2.x I get the following error.

Attestation Error: Challenge does not exist.

Reproduction

//

Stack trace & logs

No response

[2.x] Column not found: 1054 Unknown column 'rawId' in 'where clause'

PHP & Platform

8.3.7 MacOS

Database

MariaDB 11.3.2

Laravel version

11.7.0

Have you done this?

  • I have checked my logs and I'm sure is a bug in this package.
  • I can reproduce this bug in isolation (vanilla Laravel install)
  • I can suggest a workaround as a Pull Request

Expectation

I've gone through the README steps in a brand new Laravel install.

I've configured the user provider to eloquent-webauthn as described in the readme:

    'providers' => [
        'users' => [
            'driver' => 'eloquent-webauthn',
            'model' => App\Models\User::class,
            'password_fallback' => false,
        ],
    ],

I ran the install and migrate scripts

php artisan webauthn:install
php artisan migrate

Description

When attempting to log in, I get a database error:

Column not found: 1054 Unknown column 'rawId' in 'where clause'

Looking at my database, I don't see that column on the users table either.

Reproduction

await Webpass.assert("/webauthn/login/options", "/webauthn/login")


### Stack trace & logs

```shell
[2024-05-18 17:50:12] local.ERROR: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rawId' in 'where clause' (Connection: mysql, SQL: select * from `users` where `id` = QnWxGTtsch2q8KZef5SOl7Z2yYHSVdvhQ_JP7vLH9tk and `rawId` = QnWxGTtsch2q8KZef5SOl7Z2yYHSVdvhQ_JP7vLH9tk and `response` in (SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MFAAAAAA, eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoib3JMd21VTVA1VjFQdWo4cXBsVjVDQSIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODAwMCIsImNyb3NzT3JpZ2luIjpmYWxzZX0, MEUCIQDjh1bTeARNmaNiWSmv4jp7EtRsPVfCHala0-PXkYEJNgIgIXsv10E1GwTp410zovTsXxOMVgu9rZXrOQwhu7Co0i4, ��]�G����=�\�Oz{��ۭu) and `type` = public-key limit 1) {"exception":"[object] (Illuminate\\Database\\QueryException(code: 42S22): SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rawId' in 'where clause' (Connection: mysql, SQL: select * from `users` where `id` = QnWxGTtsch2q8KZef5SOl7Z2yYHSVdvhQ_JP7vLH9tk and `rawId` = QnWxGTtsch2q8KZef5SOl7Z2yYHSVdvhQ_JP7vLH9tk and `response` in (SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MFAAAAAA, eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoib3JMd21VTVA1VjFQdWo4cXBsVjVDQSIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODAwMCIsImNyb3NzT3JpZ2luIjpmYWxzZX0, MEUCIQDjh1bTeARNmaNiWSmv4jp7EtRsPVfCHala0-PXkYEJNgIgIXsv10E1GwTp410zovTsXxOMVgu9rZXrOQwhu7Co0i4, ��]�G\u001b׍���=�\\�Oz{��ۭu) and `type` = public-key limit 1) at /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Database/Connection.php:813)
[stacktrace]
#0 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Database/Connection.php(767): Illuminate\\Database\\Connection->runQueryCallback('select * from `...', Array, Object(Closure))
#1 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Database/Connection.php(398): Illuminate\\Database\\Connection->run('select * from `...', Array, Object(Closure))
#2 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php(2993): Illuminate\\Database\\Connection->select('select * from `...', Array, true)
#3 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php(2978): Illuminate\\Database\\Query\\Builder->runSelect()
#4 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php(3566): Illuminate\\Database\\Query\\Builder->Illuminate\\Database\\Query\\{closure}()
#5 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php(2977): Illuminate\\Database\\Query\\Builder->onceWithColumns(Array, Object(Closure))
#6 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php(749): Illuminate\\Database\\Query\\Builder->get(Array)
#7 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php(731): Illuminate\\Database\\Eloquent\\Builder->getModels(Array)
#8 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Database/Concerns/BuildsQueries.php(335): Illuminate\\Database\\Eloquent\\Builder->get(Array)
#9 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Auth/EloquentUserProvider.php(139): Illuminate\\Database\\Eloquent\\Builder->first()
#10 /Users/aaronpk/Code/Laravel/vendor/laragear/webauthn/src/Auth/WebAuthnUserProvider.php(59): Illuminate\\Auth\\EloquentUserProvider->retrieveByCredentials(Array)
#11 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Auth/SessionGuard.php(393): Laragear\\WebAuthn\\Auth\\WebAuthnUserProvider->retrieveByCredentials(Array)
#12 /Users/aaronpk/Code/Laravel/vendor/laragear/webauthn/src/Http/Requests/AssertedRequest.php(57): Illuminate\\Auth\\SessionGuard->attempt(Array, false)
#13 /Users/aaronpk/Code/Laravel/app/Http/Controllers/WebAuthn/WebAuthnLoginController.php(33): Laragear\\WebAuthn\\Http\\Requests\\AssertedRequest->login()
#14 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(46): App\\Http\\Controllers\\WebAuthn\\WebAuthnLoginController->login(Object(Laragear\\WebAuthn\\Http\\Requests\\AssertedRequest))
#15 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Routing/Route.php(260): Illuminate\\Routing\\ControllerDispatcher->dispatch(Object(Illuminate\\Routing\\Route), Object(App\\Http\\Controllers\\WebAuthn\\WebAuthnLoginController), 'login')
#16 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Routing/Route.php(206): Illuminate\\Routing\\Route->runController()
#17 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php(806): Illuminate\\Routing\\Route->run()
#18 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(144): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}(Object(Illuminate\\Http\\Request))
#19 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(50): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#20 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Routing\\Middleware\\SubstituteBindings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#21 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(88): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#22 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#23 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#24 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\View\\Middleware\\ShareErrorsFromSession->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#25 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(121): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#26 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(64): Illuminate\\Session\\Middleware\\StartSession->handleStatefulRequest(Object(Illuminate\\Http\\Request), Object(Illuminate\\Session\\Store), Object(Closure))
#27 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Session\\Middleware\\StartSession->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php(37): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#29 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#30 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php(75): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#31 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Cookie\\Middleware\\EncryptCookies->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#32 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(119): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#33 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php(805): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#34 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php(784): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#35 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php(748): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#36 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php(737): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#37 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#38 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(144): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#39 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#40 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(31): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#41 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#42 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#43 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#44 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#45 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Http/Middleware/ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#46 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Http\\Middleware\\ValidatePostSize->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#47 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(110): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#48 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#49 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#50 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#51 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php(57): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#52 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Http\\Middleware\\TrustProxies->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#53 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(119): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#54 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#55 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#56 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1172): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#57 /Users/aaronpk/Code/Laravel/public/index.php(17): Illuminate\\Foundation\\Application->handleRequest(Object(Illuminate\\Http\\Request))
#58 /Users/aaronpk/Code/Laravel/vendor/laravel/framework/src/Illuminate/Foundation/resources/server.php(16): require_once('/Users/aaronpk/...')
#59 {main}```

[1.x] Missing import jetbrains/phpstorm-attributes

PHP & Platform

Laravel version

Authenticator type

No response

OS and Browser versions

No response

Have you done this?

  • I am willing to share my stack trace and logs
  • I can reproduce this bug in isolation (vanilla Laravel install)
  • I can suggest a workaround as a Pull Request

Expectation

All imports of php attributes should be provided in composer.

Description

When using vscode attributes are not being recognised:

    #[ArrayShape(['binaryData' => "string"])]
    public function __serialize(): array
    {
        return ['binaryData' => static::encodeBase64Url($this->binaryData)];
    }

Those require

use JetBrains\PhpStorm\ArrayShape;

Reproduction

Open ByteBuffer.php in vscode.

Stack trace & logs

No response

Attestation / Assertion objects

No response

Are you a Patreon supporter?

No, don't give priority to this

[2.x] Migration throws access violation: 1059 Identifier name is too long

PHP & Platform

8.3.3 - Linux aarch64

Database

MariaDB 11.3.2

Laravel version

10.48.3

Have you done this?

  • I have checked my logs and I'm sure is a bug in this package.
  • I can reproduce this bug in isolation (vanilla Laravel install)
  • I can suggest a workaround as a Pull Request

Expectation

Migrate create_webauthn_credentials

Description

Table index names have a maximum length of 64 characters. The relevant generated index name is 66 characters long:

webauthn_credentials_authenticatable_type_authenticatable_id_index

Maybe this is more of an issue for laragear/meta-model

$this->createMorph($table, 'authenticatable');

A possible workaround is to shorten the table name in AppServiceProvider before run the migration:

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        \Laragear\WebAuthn\Models\WebAuthnCredential::$useTable = 'webauthn';

    }
}

However, this is not so elegant. The max length also applies to MySQL or PostgreSQL.

Reproduction

php artisan webauthn:install;
php artisan migrate;

Stack trace & logs

2024_03_15_173020_create_webauthn_credentials  6ms FAIL

Illuminate\Database\QueryException 

SQLSTATE[42000]: Syntax error or access violation: 1059 Identifier name 'webauthn_credentials_authenticatable_type_authenticatable_id_index' is too long (Connection: mariadb, SQL: alter table `webauthn_credentials` add index `webauthn_credentials_authenticatable_type_authenticatable_id_index`(`authenticatable_type`, `authenticatable_id`))

  at vendor/laravel/framework/src/Illuminate/Database/Connection.php:829
    825▕                     $this->getName(), $query, $this->prepareBindings($bindings), $e
    826▕                 );
    827▕             }
    828▕ 
  ➜ 829▕             throw new QueryException(
    830▕                 $this->getName(), $query, $this->prepareBindings($bindings), $e
    831▕             );
    832▕         }
    833▕     }

      +45 vendor frames 

  46  artisan:35
      Illuminate\Foundation\Console\Kernel::handle(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))

[1.2.1] userHandle empty causing User is not owner of the stored credential.

PHP & Platform

8.2.7 Ubuntu 22.04.2

Database

MariaDB 10.10.5

Laravel version

10.14.1

Browser

Google Chrome 114.0.5735.198

Have you done this?

  • I have checked my logs and I'm sure is a bug in this package.
  • I can reproduce this bug in isolation (vanilla Laravel install)
  • I can suggest a workaround as a Pull Request

Expectation

Not sure

Description

When logging in the response from the browser publicKeyCredential.response contains userHandle: ''

not sure why its empty but it ise (I checked on a few other site using webauthn, and it's empty there also).

However in the database the user_id field is populated,
In Laragear\WebAuthn\Assertion\Validator\Pipes\CheckCredentialIsForUser::validateId there is a check againest user_id on the credentials model, this throws an exception when handle is empty.

Reproduction

public function registerOpts(AttestationRequest $request): Responsable
{
    return $request
        ->secureRegistration()
        //->fastRegistration()
        //->userless()
        //->allowDuplicates()
        ->toCreate();
}

public function register(AttestedRequest $request): Response
{
    $request->save($request->only(['alias']));

    return response()->noContent();
}


public function loginOpts(AssertionRequest $request): Responsable
{
    /** @var User $user */
    $user = Auth::guard('web')->user();

    return $request
        ->secureLogin()
        ->toVerify($user->only(['email']));
}

public function login(Request $request, AssertionValidator $assertion): Response
{
    $credential = $assertion
        ->send(new AssertionValidation($request))
        ->thenReturn()
        ->credential;

    if (!$credential) return \response()->noContent(422);

    session(['webauthn' => true]);

    return response()->noContent();
}

Stack trace & logs

not applicable

Using different connection

PHP & Platform

8.1.5

Laravel version

9.16.0

Authenticator type

No response

OS and Browser versions

No response

Have you done this?

  • I am willing to share my stack trace and logs
  • I can reproduce this bug in isolation (vanilla Laravel install)
  • I can suggest a workaround as a Pull Request

Expectation

For my main site, I have a default database connection that goes to "DifferentSchema".
For my auth, I have another database connection that goes to "AuthSchema"

I can't seem to figure out how to make webauthn use my "AuthSchema" connection. I added the "AuthSchema" database connection in App\User::class, and that fixed one of the errors, but there's more further down the "webauthn process" that I'm not sure how to fix.

Is there a way to specify what connection WebAuthn uses?

Thanks

Description

https://pastebin.com/raw/yrbc05V3 - Shows the default database connection being used and not the "AuthSchema" database connection I set in App\User::class

Reproduction

N/A

Stack trace & logs

https://pastebin.com/raw/yrbc05V3

Attestation / Assertion objects

No response

Are you a Patreon supporter?

No, don't give priority to this

Head's up, I'm hands full

As you can see, there are a lot of small bugs here and there.

Currently I'm under contract and hands full. I've not abandoned this package, or any under the Laragear.

I will retake it as soon as I'm able to, hopefully before this year ends.

[1.x] Attestation Error: ByteBuffer: Invalid offset or length.

PHP & Platform

8.2.1 & MacOs

Database

No response

Laravel version

10

Have you done this?

  • I have checked my logs and I'm sure is a bug in this package.
  • I can reproduce this bug in isolation (vanilla Laravel install)
  • I can suggest a workaround as a Pull Request

Expectation

When I try to register a new device, the credential should be saved in the DB.

Description

I am getting this error when I try to register a new device:
Attestation Error: ByteBuffer: Invalid offset or length.
It's coming from this class: CompileAttestationObject and the line number is 78.

Reproduction

Try to register a new device.

Stack trace & logs

No response

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.