NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Crypto for Laravel

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:

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:

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

$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 ktyResultAlgorithms / curves
2 (EC2)EcKeyES256 / P-256, ES384 / P-384, ES512 / P-521 — alg must match the curve
3 (RSA)RsaKeyRS256
1 (OKP)OkpKeyEdDSA / 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:

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

By donating, you agree to our donation terms.

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.