Looking Up Authenticators by AAGUID
An AAGUID is sixteen bytes inside the attested credential data of a registration response, and on its own it means nothing. This page covers how to extract it correctly, how to resolve it against a cached metadata set, and how to handle the three outcomes that lookup produces — because collapsing them into two is the most common way an authenticator policy starts rejecting legitimate users. It sits under Authenticator Metadata and MDS3; the blob it queries must have been verified first.
Where the AAGUID Lives
| Field | Location | Notes |
|---|---|---|
authData |
inside the attestation object | present in registration responses only |
| flags byte | offset 32 | the AT bit must be set for attested data to follow |
| AAGUID | first 16 bytes of attested credential data | after the 37-byte fixed header |
| credential id length | next 2 bytes, big-endian | needed to skip to the public key |
| credential public key | after the credential id | COSE-encoded |
The AAGUID is therefore reachable only when the attested-data flag is set, which means only during registration. Code that tries to read it from an assertion is reading whatever bytes happen to be there, and it will produce a plausible-looking value that corresponds to nothing.
Root Cause Analysis
1. Reading the AAGUID from an assertion. An authentication response has no attested credential data. A parser that does not check the AT flag before slicing will return sixteen bytes of something — often zeros, sometimes part of an extension — and a policy built on that value is comparing noise against a directory.
2. Trusting the value without verifying attestation. The AAGUID arrives in a payload the client controls. Only a verified attestation statement makes it a manufacturer’s claim rather than a sender’s claim, which means the lookup must come after chain validation, never before.
3. Treating unknown as invalid. Certification is voluntary and lags hardware, so a missing entry is the expected state for a large share of devices. A policy that refuses the unknown refuses new models, uncertified models, and — depending on how it is written — every synced platform passkey.
4. Treating all-zero as unknown. An all-zero AAGUID is a positive statement that the authenticator declines to identify a model, not a failed lookup. Merging the two cases means your dashboards cannot distinguish “our metadata is stale” from “our users are on platform passkeys”.
Step-by-Step Resolution
Step 1 — check the AT flag before parsing
Read byte 32, test the attested-data bit, and return early if it is clear. This single guard eliminates the entire class of bugs where an assertion is parsed as a registration.
Step 2 — extract after the fixed header
The first 37 bytes are the RP ID hash, the flags byte and the counter. The AAGUID is the next 16. Slice by offset rather than by searching for a pattern.
Step 3 — normalise the representation
Store and compare the AAGUID in one canonical form. Metadata entries publish it as a hyphenated string; your parser produces raw bytes. Pick one, convert at the boundary, and never compare across representations — a mismatch here produces a permanent, silent unknown for every device.
Step 4 — resolve against the local cache
The lookup is an in-memory or single-read operation against entries you have already verified. It must not perform a network fetch.
Step 5 — branch on all three outcomes
Known, unknown and no-identity are different states with different policy answers. Return a typed result rather than a nullable entry, so the call site cannot accidentally treat two of them the same.
Verification and Testing
Three fixtures cover the ground: a registration from a certified hardware key with a known AAGUID, a registration from a platform authenticator reporting all zeros, and a registration carrying an AAGUID deliberately absent from your cache. Assert that each produces the corresponding branch, and that the third does not throw.
Add an assertion payload to the same suite and assert that the extractor refuses it. That is the test that catches the AT-flag guard being removed during a refactor.
Finally, assert the normalisation both ways. A test that converts a hyphenated metadata identifier to bytes and back, and compares against a fixture from a real device, is the cheapest possible guard against the representation mismatch that otherwise turns every lookup into a silent miss.
Pitfalls
1. Comparing a hyphenated string with raw bytes. Produces a permanent unknown that looks like stale metadata.
2. Looking up before verifying attestation. Turns a client-supplied value into a policy input.
3. Failing the registration on an unknown model. Refuses new hardware and most consumer authenticators.
Frequently Asked Questions
Why do most of my registrations report an all-zero AAGUID?
Because they come from synced platform passkeys, which deliberately report no model identity. The credential exists on more than one device by design, so there is no single unit or model to name in a way that would be meaningful. This is the expected majority case in any consumer product.
Can I use the AAGUID to recognise a returning device?
No. Every unit of a model shares the value, so it identifies a product line rather than a device. Using it as a device signal produces a fingerprint that matches millions of users, which is both useless for recognition and undesirable for privacy.
Should the resolved model name be shown to users?
It is useful in account settings, where “Security key (model name)” helps a user identify which credential to remove. Fall back to a neutral label when the lookup returned unknown or no identity, and let the user rename the entry — the name they choose will always be more meaningful to them than the vendor’s.
What happens to the lookup when metadata is updated?
Nothing automatically. A credential enrolled while its model was unknown keeps that recorded outcome until something re-runs the lookup. Storing the raw AAGUID is what makes a periodic re-evaluation possible, and a scheduled sweep is the right way to pick up entries that have appeared since.
Does an AAGUID ever change for a given model?
Vendors sometimes issue a new AAGUID for a firmware generation that changes certified behaviour, so a product line can span several values over time. Treat the AAGUID as identifying a certified configuration rather than a marketing name, and expect an allow-list to need occasional additions for hardware you already thought you supported.
How should the lookup outcome affect what the user sees?
Ideally not at all during enrolment. A user setting up a passkey has no interest in whether their device is in a certification directory, and an interface that surfaces the distinction is exposing an internal policy detail as though it were their problem. Where the outcome genuinely must change the experience — in a deployment that only accepts certified hardware — the message should name the requirement in concrete terms, such as which authenticators the organisation issues, rather than reporting that a lookup failed.
Is it worth re-running lookups on a schedule?
Yes, and it is cheap. A weekly sweep over credentials whose recorded outcome was unknown, re-resolving them against the current cache, picks up models certified since enrolment and keeps your account-settings labels accurate. Because the raw AAGUID was stored, the sweep needs no new data from the user and no ceremony. The same sweep is the natural place to notice a status report that has downgraded a model you previously accepted, which is otherwise a change nobody sees until an audit asks about it.
Related
- Authenticator Metadata and MDS3 — the parent topic and the caching architecture
- Verifying the FIDO Metadata Blob — the checks that must precede any lookup
- Decoding authenticatorData Byte Layout — the offsets this extraction depends on
- Attestation vs Assertion Explained — why the AAGUID is only trustworthy after chain validation