Upgrading to v4 from v3
Requirements
PHP 8.1+ and Laravel 10 to 13. Laravel 9 is no longer supported.
composer require laranex/laravel-biometric-auth:^4.0The package no longer depends on spatie/laravel-package-tools. The publish tags biometric-auth-config and biometric-auth-migrations are unchanged, and the new biometric-auth tag publishes both.
Database
No database changes: the biometrics table and its columns are unchanged. If you published the migration under v3, keep it. The package detects a published *_create_biometrics_table.php and no longer loads its own copy; without one, the package migration now runs automatically.
RSA padding
phpseclib 4 is supported alongside phpseclib 3. phpseclib 4 moved to the phpseclib4 namespace and renumbered the RSA signature constants, so rsa.encryption_padding now takes a name. If you published config/biometric-auth.php, update it:
// Before
'encryption_padding' => \phpseclib3\Crypt\RSA::SIGNATURE_PKCS1,
// After
'encryption_padding' => 'pkcs1', // or 'pss'The old constant keeps working while you stay on phpseclib 3, but it is a fatal error on phpseclib 4 (the class no longer exists). Unknown names throw InvalidArgumentException.
Single-use, expiring challenges
verifyBiometric() now clears the challenge after a successful verification, so a captured signature cannot be replayed. Clients must request a new challenge with getBiometric() after every successful verification. Failed attempts keep the challenge so the device can retry, up to challenge.max_attempts (5 by default, BIOMETRIC_AUTH_CHALLENGE_MAX_ATTEMPTS); then the challenge is cleared and the client must request a new one. Attempts are counted in the default cache store. Set the option to 0 to keep v3's unlimited retries.
Challenges now expire challenge.ttl seconds after they are issued (300 by default, BIOMETRIC_AUTH_CHALLENGE_TTL). getBiometric() replaces an expired challenge and verifyBiometric() throws BiometricChallengeNotFoundException for one, so devices must sign within that window. The issue time is the row's updated_at, so no column is added. Set the option to 0 to keep v3's challenges that never expire.
getBiometric(), verifyBiometric() and revokeBiometric() throw BiometricNotFoundException for an id that is not a UUID instead of querying the database (which failed on PostgreSQL's native uuid column). verifyBiometric() decodes the signature as strict base64: a signature with characters outside the base64 alphabet returns false.
Revoking
revokeBiometric() throws BiometricNotFoundException when the biometric does not exist, is already revoked or belongs to another model. It previously failed with a PHP error on null. Catch the exception where you previously checked for a failure:
use Laranex\LaravelBiometricAuth\Exceptions\BiometricNotFoundException;
try {
$user->revokeBiometric($biometricId);
} catch (BiometricNotFoundException) {
abort(404);
}Service class
LaravelBiometricAuth now takes the config repository through its constructor. If you create it with new, resolve it instead:
app(\Laranex\LaravelBiometricAuth\LaravelBiometricAuth::class);The LaravelBiometricAuth facade works as before.
Exceptions
The exceptions extend the new BiometricException base class and their constructors are typed: string $message, ?int $code, ?Throwable $previous.
The default code is now an HTTP status instead of 500: 404 for BiometricNotFoundException, 422 for BiometricChallengeNotFoundException and InvalidPublicKeyException. They are renderable, so an uncaught exception in a JSON request returns {"message": "..."} with that status instead of a 500 server error. If you relied on the 500 code or response, catch the exceptions yourself. See Exceptions.
Morph map
authenticable_type is now stored through getMorphClass(). If your app uses a morph map, new rows store the alias instead of the class name. Existing rows with the class name keep resolving through the instance relation.
New
HasBiometrics::biometrics()morph-many relation.Biometric::active()scope.- The migration and the model read the table name from
biometric-auth.table.