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.

Three outcomes, not two Every AAGUID lookup lands in one of three states, and collapsing them is how policies misbehave. Handle all three explicitly at the call site all-zero AAGUID no model identity reported — normal for synced platform passkeys entry found a certified model with known capabilities and certification status no entry uncertified, newly released, or simply absent from the directory

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”.

From response to decision The AAGUID is extracted from attested credential data, matched against the cache, and resolved into a policy outcome. Four steps, none of which touch the network attested data present only at registration 16 bytes the AAGUID field cache lookup local, no network decision accept, tier or refuse The extraction only works on a registration payload — an assertion carries no attested credential data and therefore no AAGUID.

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.

What to store, and why Store the resolved facts rather than the raw entry, so a later policy change does not require re-fetching history. Four fields on the credential row the raw AAGUID — so the lookup can be repeated after a metadata update the resolved model name — so account settings can label the credential the certification level at enrolment — so a later change is detectable the lookup outcome — known, unknown, or no-identity The fourth is what lets you re-evaluate policy later without guessing why a credential was accepted.

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