ecdsa.com

WebAuthn / Passkey Inspector

Paste what navigator.credentials.create() or .get() gave you — the whole JSON or a single base64url field. Every part is decoded and explained: clientDataJSON, the authenticator data flags bit by bit, AAGUID, credential ID, the COSE public key, and the attestation statement. For logins, the ECDSA P-256 signature is verified right here.

100% local — everything runs in your browser, nothing is sent to any server

The response from navigator.credentials.create() — a new passkey.

Decoded

Nothing pasted yet

everything decodes locally

In your browser console, run a registration or a login and copy the result. In Chrome and Safari, credential.toJSON() gives you exactly the JSON this box expects; older code paths need the base64url values from credential.response. Or press Load example to see a real, signature-verifying passkey response.

How it works

A passkey is an ECDSA key pair — almost always on the P-256 curve — created by your phone, laptop or security key. The private half never leaves that device. Registration hands your server the public half; every later login is a signature made with the private half over data your server chose. There is no shared secret to steal, and the browser refuses to sign for a site the credential was not created on, which is what makes passkeys phishing-resistant.

The browser returns two or three base64url blobs. clientDataJSON is plain JSON describing what the browser saw: the ceremony type, your challenge, and the real origin. authenticatorData is a packed binary record: 32 bytes of SHA-256(RP ID), one flags byte, a 4-byte counter, and — during registration — the AAGUID, the credential ID and the new public key as a CBOR-encoded COSE_Key. During registration that record is wrapped in an attestationObject, a CBOR map that adds the authenticator's optional statement about its own make and model.

Verifying a login is one line of maths: verify(signature, SHA-256(authenticatorData ‖ SHA-256(clientDataJSON)), publicKey). Note what is not in there — the challenge and the origin are only covered because they sit inside clientDataJSON, so a valid signature is necessary but never sufficient: your server still has to read those fields and compare them with what it expects. WebAuthn signatures are ASN.1 DER, not the raw r‖s format JWTs use, which is a common source of "valid signature that will not verify".

This page does all of it in your browser: the CBOR decoder and the WebAuthn parsers are written from scratch in a few hundred lines, and the ECDSA check runs through the audited @noble/curves library. Nothing you paste is uploaded, logged or stored — which matters, because a real response identifies a real credential of a real user.