---
title: "WebAuthn verification — Crypto for Laravel | Roundly"
description: "Parse authenticator data and COSE keys with a canonical CBOR decoder, then verify passkey signatures across ES, RS and EdDSA."
url: https://roundly-consulting.com/open-source/docs/crypto-for-laravel/webauthn
language: en
---

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

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

# WebAuthn verification

The Cose namespace decodes the byte structures a WebAuthn or passkey ceremony produces and turns a COSE public key into a verifiable key. Signature checks go through KeyVerifier:

```php
use RoundlyConsulting\Crypto\Facades\Crypto;

$authData = Crypto::authenticatorData($rawAuthenticatorData);     // rpIdHash, flags, signCount, COSE key
$key      = Crypto::coseKey($coseBytes);                           // COSE_Key bytes -> a verifiable public key
// or, from the attested credential: Crypto::coseKey($authData->coseKeyBytes)

$ok = Crypto::verifier()->verify($key, $signedData, $signature);   // ES256 DER or raw, RS256, EdDSA
```

Or call the classes the facade fronts directly:

```php
use RoundlyConsulting\Crypto\Cose\AuthenticatorData;
use RoundlyConsulting\Crypto\Cose\CoseKey;
use RoundlyConsulting\Crypto\Signature\KeyVerifier;

$authData = AuthenticatorData::parse($rawAuthenticatorData);      // rpIdHash, flags, signCount, COSE key
$key = CoseKey::fromCbor($coseBytes);                             // COSE_Key bytes -> a verifiable public key

$ok = (new KeyVerifier)->verify($key, $signedData, $signature);   // ES256 DER or raw, RS256, EdDSA
```

CoseKey::fromCbor() — and Crypto::coseKey() — takes the COSE\_Key bytes (a stored credential key, or $authData->coseKeyBytes), not a decoded array: a PHP string cannot say whether CBOR carried it as a byte string or a text string, and a strict COSE reader refuses a text-typed x, y, n or e.

The ceremony — challenge binding, origin, rpId hash, flag policy and sign-count reconciliation — stays in your relying-party code. This package only decodes bytes and checks signatures.

## Authenticator data

```php
$authData = AuthenticatorData::parse($rawAuthenticatorData);

$authData->rpIdHash;                  // 32 bytes
$authData->flags->userPresent;        // UP
$authData->flags->userVerified;       // UV
$authData->flags->backupEligible;     // BE
$authData->flags->backupState;        // BS
$authData->signCount;                 // int — unsigned 32-bit, never negative
$authData->aaguid;                    // ?string (present with attested credential data)
$authData->credentialId;              // ?string
$authData->coseKey;                   // ?PublicKey — the verifiable credential key
$authData->coseKeyBytes;              // ?string — the raw COSE key bytes
```

AuthenticatorData::parse() validates structure strictly: a too-short buffer, a truncated or oversized credential id, a non-map COSE key or unexpected trailing bytes all throw MalformedCborException. Flags also expose attestedCredentialData (AT) and extensionData (ED).

## Supported COSE keys

| COSE kty | Result | Algorithms / curves |
| --- | --- | --- |
| `2 (EC2)` | `EcKey` | ES256 / P-256, ES384 / P-384, ES512 / P-521 — alg must match the curve |
| `3 (RSA)` | `RsaKey` | RS256 |
| `1 (OKP)` | `OkpKey` | EdDSA / Ed25519 |

An unknown key type, curve or algorithm throws UnsupportedAlgorithmException; a missing or mistyped label throws MalformedCborException. The CoseAlgorithm enum maps the IANA identifiers (-7, -8, -35, -36, -257) to their JOSE equivalents via toSignatureAlgorithm().

## The CBOR decoder

CborDecoder supports only the subset WebAuthn uses — integers, byte and text strings, definite-length arrays and maps, and false/true/null. It is canonical (CTAP2): indefinite lengths, tags, floats, non-shortest encodings and duplicate map keys are rejected, and nesting is capped at 16. It also refuses a text string that is not UTF-8 and a text map key PHP would store as an integer (“1”, “-1”), so a text label can never pass for the integer label it imitates:

```php
use RoundlyConsulting\Crypto\Cose\CborDecoder;

$decoded = (new CborDecoder)->decode($attestationObjectBytes);   // exactly one item; trailing bytes throw

$result = (new CborDecoder)->decodeFirst($bytes);
$result->value;            // the decoded item
$result->bytesConsumed;    // how many bytes it spanned
```

## 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/crypto-for-laravel.md)
