---
title: "Exceptions — Passkeys for Laravel | Roundly"
description: "One PasskeyException base with typed children for ceremony, configuration, decoding and attestation failures — with localisable messages."
url: https://roundly-consulting.com/open-source/docs/passkeys-for-laravel/exceptions
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Passkeys for Laravel](https://roundly-consulting.com/open-source/docs/passkeys-for-laravel.md)

# Exceptions

Every failure extends RoundlyConsulting\\Passkeys\\Exceptions\\PasskeyException (a RuntimeException). Catch the base for a uniform response, or a child for finer control:

```php
use RoundlyConsulting\Passkeys\Exceptions\PasskeyException;

try {
    $passkey = Passkeys::authenticate($response);
} catch (PasskeyException $e) {
    // Return a uniform failure to the client; log $e server-side.
    return response()->json(['message' => 'Sign-in failed.'], 422);
}
```

## Ceremony failures

| Exception | Thrown when |
| --- | --- |
| `InvalidClientData` | clientData.type is wrong, the response payload is malformed, the attestation fmt is not a well-formed format identifier, or a user handle is not base64url of random bytes. |
| `ChallengeExpired` | No challenge exists for the ceremony ID — expired or already used. |
| `ChallengeMismatch` | The challenge differs, was minted for another user, or for the other ceremony type. |
| `OriginMismatch` | The origin is not allow-listed, or a cross-origin ceremony is not allowed. |
| `RpIdMismatch` | The RP ID hash in the authenticator data doesn’t match. |
| `InvalidAuthenticatorData` | Bad authenticator data, no user-presence flag, inconsistent backup flags, a backup eligibility that differs from the one registered, missing attested credential data, or a registration whose credential ID is longer than 1023 bytes. |
| `UserVerificationRequired` | User verification was required but the authenticator didn’t verify the user. |
| `UnsupportedAlgorithm` | The credential’s algorithm isn’t in the offered list. |
| `SignatureInvalid` | The assertion signature failed verification. |
| `SignCountRegression` | The counter didn’t advance and sign\_count\_policy is reject. |
| `CredentialAlreadyRegistered` | The credential ID is already stored, revoked ones included — also for the losing request when two registrations race with the same ID. |
| `CredentialNotFound` | The uniform miss: an unknown credential, one whose owner was deleted or soft-deleted, no longer holds its user handle or no longer resolves to a model, one a user-bound ceremony didn’t offer, a usernameless assertion without a userHandle, a failed expectation, a credential revoked mid-assertion, or a rename/revoke of a passkey that isn’t this account’s. |
| `InvalidExpectation` | AuthenticationExpectation::owner() got an unsaved model — a programming error, never a ceremony outcome. |

## Configuration, decoding and attestation

| Exception | Thrown when |
| --- | --- |
| `InvalidConfiguration` | At boot: a config value of the wrong shape, naming the key (table only when first read), an unvetted algorithm, trust stricter than ignore with attestation none, or clock skew outside 0–3600. At the first ceremony: missing rp.id or origins. At first use: an unreadable trust anchor. |
| `MalformedCbor` | The CBOR data is malformed. |
| `InvalidCoseKey` | The COSE public key is invalid. |
| `InvalidAttestation` | The attestation statement’s maths failed. |
| `AttestationUntrusted` | The statement is sound but policy refused it — unanchored root, no anchors configured, self-attestation under basic, an invalid certification path, an expired certificate or a disallowed AAGUID. |
| `AttestationRequired` | The trust tier demands a statement and the authenticator sent none. |
| `UnsupportedAttestationFormat` | The format has no verifier (none, packed and apple are supported). |
| `PasskeyAssertionFailed` | A Passkeys::fake() assertion failed. |

## Localised messages

Messages resolve through the passkeys::errors.\* translation namespace, which ships in English and Slovak and follows the app locale — publish passkeys-translations to adjust them or add another language. A message is worded entirely in the app locale: the package’s own detail fragments — the shape a setting must have, why an attestation statement is malformed, which certificate requirement failed — are translation keys too, while literal tokens such as x5c, CA:FALSE, OIDs and setting keys stay as they are. Integer and enum settings are validated by package-toolkit-for-laravel, whose messages are English only.

Text from a lower layer — the CBOR/COSE decoder or the certificate parser — is not translatable, so it never enters the message. It stays available to developers:

- $e->getPrevious() — the lower-level exception (for example a CryptoException) behind MalformedCbor, InvalidCoseKey, InvalidAuthenticatorData::because(), UnsupportedAlgorithm::because() and a parser-backed InvalidAttestation::malformedStatement().
- $e->context() — \['reason' => '…'\] for the four reason-carrying factories, \[\] otherwise. Laravel’s exception handler merges it into the log entry automatically.

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/passkeys-for-laravel.md)
