Attestation: The WebAuthn Feature You Probably Shouldn't Use

There is a moment in most WebAuthn implementations when someone reads the spec, discovers attestation, and gets excited. It sounds like exactly what a security-conscious team wants: cryptographic proof of what kind of authenticator the user registered. Not a claim, not a user-agent string — a signat

There is a moment in most WebAuthn implementations when someone reads the spec, discovers attestation, and gets excited. It sounds like exactly what a security-conscious team wants: cryptographic proof of what kind of authenticator the user registered. Not a claim, not a user-agent string — a signature from a manufacturer's certificate chain saying this is a genuine YubiKey 5 NFC, model such-and-such, with these certified security characteristics.

You can then enforce policy on it. Only FIPS-validated authenticators. Only hardware keys, no phones. Only devices from an approved list.

It's real, it works, and for the overwhelming majority of deployments you should set attestation: "none" and move on. Here's the argument, including the cases where the opposite is true.

What attestation actually proves

At registration, the authenticator can include an attestation statement: a signature over the new credential's data, made with a key that chains to a manufacturer root certificate. The statement includes the AAGUID, a 16-byte identifier for the authenticator make and model.

Verify the chain against the manufacturer's root, and you know the AAGUID is genuine. Look up that AAGUID in the FIDO Metadata Service and you get the model name, its certification level, supported algorithms, and known security characteristics.

That's a genuine capability with no equivalent elsewhere in web authentication. The question is what it costs and what it buys.

The four costs

The metadata service becomes a dependency of registration

To do anything useful with an AAGUID you need the FIDO Metadata Service — a signed BLOB listing thousands of authenticators, their roots, their certification status, and revocation entries. It updates monthly.

So your registration path now depends on an external data source that must be fetched, signature-verified, cached, refreshed, and monitored. Get the caching wrong and registrations fail when the fetch fails. Let the cache go stale and you reject authenticators certified last month, which are exactly the new devices your users just bought. Fail open and your policy is advisory. Fail closed and your registration availability is now coupled to a third-party BLOB.

None of this is hard. It's all unglamorous, and unglamorous dependencies in a rarely-exercised code path are where incidents come from — because a broken metadata refresh doesn't page anyone. It just quietly starts rejecting a subset of new registrations, and you find out from a support ticket three weeks later.

The privacy problem is structural

The AAGUID identifies make and model, not the individual device — the spec is careful about that, and manufacturers ship batch attestation keys shared across production lots specifically to avoid a per-device identifier.

But "make and model" is more identifying than it sounds. If a user registers a credential on an unusual authenticator, the AAGUID plus the timing plus the other attributes you hold can be quite narrowing. And attestation formats differ: some platform attestations have historically carried more information, and Android SafetyNet-based attestation involved sending a signed statement to Google's servers, which is a third-party disclosure inside your authentication flow.

Then there's the part users notice. Browsers know attestation is privacy-relevant, so requesting it can trigger an additional consent prompt — Chrome and others have shown "allow this site to see the make and model of your security key?" style interstitials. Every extra prompt in a registration flow costs completion rate, and it costs it at the moment you are trying to persuade someone to adopt a stronger credential.

It excludes users, and the exclusion is invisible to you

This is the cost that actually shows up in a support queue.

If your policy is "only authenticators on the approved list," then users with anything else cannot register. Not "get a warning" — cannot proceed. And the population you exclude is not the population you intended:

  • Someone with a perfectly good authenticator that's three months newer than your metadata snapshot.
  • Someone using a password manager's passkey implementation, several of which produce no meaningful attestation and report a zero AAGUID.
  • Someone on a platform whose attestation format your library doesn't parse.
  • Someone whose device is fine but whose browser passes attestation through in a way you didn't anticipate.

They all see the same thing: registration failed, with a NotAllowedError or a generic message, because WebAuthn error reporting is deliberately uninformative. They contact support, who cannot diagnose it, because the useful information — which AAGUID was presented and why it was rejected — is in your server logs and probably isn't being recorded with enough context.

The predictable result is a support burden with a bad resolution rate, in the flow whose adoption you're trying to maximize.

It buys less than it appears to

The strongest version of the case against: think carefully about the threat attestation defends against.

It stops a user from registering an authenticator you haven't approved. That's it. It doesn't stop phishing (origin binding does that). It doesn't stop credential theft (the private key is non-exportable regardless). It doesn't stop account takeover through your recovery flow, which is where takeovers actually happen. It doesn't tell you the credential is still on the device it was created on.

So attestation is a procurement control, not an attack mitigation. It enforces "our people use approved hardware." That's a legitimate organizational goal — and it's worth noticing that in the environments where that goal genuinely matters, you usually have better enforcement mechanisms available: device management, conditional access based on managed-device state, physically issuing the keys yourself. If you handed the user the YubiKey, you don't need a certificate to tell you it's a YubiKey.

The gap where attestation is uniquely necessary is narrower than it first appears: you need to enforce hardware requirements on devices you did not issue and do not manage. Real, but not common.

What to use instead

Here's the part that makes none a defensible default rather than a shrug: most of the risk signal people want from attestation is available without it.

The BE and BS flags in authenticatorData. BE (Backup Eligible) tells you whether the credential can be synced; BS (Backup State) whether it currently is. This is the distinction that usually underlies the request for attestation — "we need to know this credential can't leave the device." BE=0 answers exactly that question, it's in every response, it needs no metadata service, and it triggers no consent prompt. If your requirement is "device-bound credentials for administrators," check BE. You do not need attestation for it.

The UV flag. Whether user verification actually happened, per authentication rather than per registration.

The transports the client reports. usb, nfc, ble, internal, hybrid — a coarse but useful signal about the kind of authenticator, with none of the costs.

Attestation in "indirect" or advisory mode. You can request attestation and record it without enforcing on it. Store the AAGUID, report on the distribution across your user base, and use it for analytics and incident investigation. You get the visibility without the exclusion, and you learn what your population actually uses — which is the input you'd need before writing a sensible allowlist anyway.

That last option is genuinely underused and it's what I'd recommend to most teams who feel they need attestation: collect it, don't gate on it. Then revisit in six months with data instead of an assumption.

When you should turn it on

To be fair to the feature, the cases where attestation is the right answer:

Regulated environments with an explicit hardware mandate. Government, defence, some financial contexts where a control framework names FIPS-validated or specific certification levels. The requirement is external and non-negotiable, the user population is issued their devices, and support can escalate to someone who understands the policy.

High-assurance administrative access. A small population — a few dozen people with production access — where you want to guarantee hardware keys and where you can afford to help each person individually if registration fails. Small N makes the support cost tractable and the security benefit concentrated.

You're an enterprise IdP whose customers demand it. Then it's a feature you support, per-connection, because a customer's compliance team requires it. Offer it; don't default it.

Fraud investigation in high-value consumer contexts. Recording AAGUIDs across a large population lets you spot patterns — a cluster of accounts registering credentials from an unusual authenticator model in a short window is a genuine signal. Note that this is the record, don't enforce posture.

In all four, the pattern is the same: a defined population, a specific policy, and someone who can be paged when it goes wrong.

If you do enable it, do it properly

Half-implemented attestation is worse than none, because it produces the costs without the benefit:

Verify the chain, don't just read the AAGUID. An unverified attestation statement is self-asserted metadata; an attacker with a software authenticator can claim any AAGUID they like. If you're not validating chains against FIDO MDS roots, you are not doing attestation — you're reading a field.

Handle fmt: "none" explicitly. Many authenticators, including several major platform implementations in some configurations, provide no attestation. Decide what that means for your policy and make it a deliberate branch, not an exception.

Cache MDS with a long TTL, last-known-good on failure, and an alert on staleness. A metadata refresh that has been failing for six weeks should page someone, because your policy is quietly diverging from reality.

Log the AAGUID and the exact rejection reason on every failure, and surface a user-facing message that names the problem: "This security key isn't on your organization's approved list" is actionable; "Registration failed" generates a ticket.

Have an exception path. A documented process by which a user with a legitimate but unlisted authenticator gets registered — because otherwise the exception path is "email an administrator who edits the database," which is both an audit gap and a privilege escalation vector.

Prefer enterprise attestation where it applies. For managed devices in an enterprise context, enterprise attestation can return a per-device identifier and is designed for exactly this use case, with policy-gated browser support.

The recommendation

Default: attestation: "none". Enforce user verification, check the BE/BS flags for device-binding requirements, and record transports. You get phishing resistance, a clear MFA posture, and a syncable-versus-bound signal, with no external dependency and no excluded users.

Middle ground, if you want the data: request attestation, store the AAGUID, enforce nothing. Revisit with a distribution report in hand.

Enable enforcement when a named external requirement or a small high-assurance population justifies it — and then build the metadata pipeline, the logging, and the exception process, because those are the parts that make the difference between a working control and a mysterious registration failure rate.

The general shape of the argument isn't specific to WebAuthn. A control that excludes legitimate users, depends on an external data source, and defends against a threat you don't have is not a strong control — it's an expensive one. Attestation is a good feature that most deployments are better off without, and being able to say why you turned it off is a better security posture than having turned it on because it sounded rigorous.