AAGUID policies for passkeys: what they really enforce
Why AAGUID restrictions are trickier than they look If you're designing passkey policy for a regulated CIAM rollout, the core question is usually not "can we block some authenticators?" but "what does that control actu
Why AAGUID restrictions are trickier than they look
If you're designing passkey policy for a regulated CIAM rollout, the core question is usually not "can we block some authenticators?" but "what does that control actually prove?" Vincent Delitz's guide on restricting passkey authenticators breaks that down for banks, insurers and payment providers that need a defensible provider policy without locking out customers.
The important nuance: an AAGUID is useful, but it is not magic. It identifies the make and model of an authenticator at registration, which makes it valuable for policy, support and telemetry. But without trusted attestation, an AAGUID is still a self-asserted label. It does not prove the provider build, the current signing surface or theft-protection settings.
That changes how you should think about an AAGUID policy for passkeys. It's a registration-time filter and a provider label, not a full runtime assurance model.
What you can safely enforce
The guide's data point that matters most operationally is this: 92-98% of passkeys sit with five first-party providers in Corbado's aggregate CIAM data. Third-party password managers account for 1-2% overall, rising to 3-6% on desktop.
That leads to a practical policy shape:
- always allow major first-party providers
- allow attested security keys when the attestation chain validates
- review browser profiles and third-party managers by implementation, not by brand
- keep an explicit unknown class instead of silently accepting anything unrecognized
This is why the choice between an allowlist and a blocklist is usually less about ideology and more about maintenance. A managed allowlist is stronger because it excludes the long tail of virtual, generic and unknown AAGUIDs. But it also needs better error handling, FAQ coverage and a review process.
Attestation, UV and the security-key lane
The guide recommends separating policy into lanes instead of using one flat list.
| Lane | What to trust | What to check |
|---|---|---|
| Consumer synced passkeys | Qualified provider catalog | Required user verification, challenge freshness, RP ID, origin, signature |
| Security keys | Trusted attestation | Certificate path, FIDO Metadata Service status, required UV |
| Unknown class | Nothing by default | Review route and recovery path |
This is the part many teams miss: the user verification (UV) flag is important, but it is not evidence of biometrics or proof that the account owner performed the check. It tells you the authenticator claims verification happened. That's still useful, but different from strong provenance. It also varies a lot between providers: in Corbado's tests, four of six password manager extensions reported UV on an already unlocked vault without verifying the user again.
For the security-key lane, the article is clear: require validated attestation and verify the model in the FIDO Metadata Service. That's where AAGUID-based restrictions become much more defensible.
A minimal registration check
Here is a sketch of how the lanes translate into code at registration. Remember that the AAGUID is only a label unless the attestation validates.
// authData from the parsed attestationObject (registration only)
function readAaguid(authData: Uint8Array): string | null {
const AT = 0x40; // flag: attested credential data present
if (!(authData[32] & AT)) return null;
const hex = [...authData.slice(37, 53)]
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
}
type PolicyClass = "allow" | "review" | "deny";
function classify(aaguid: string | null, allow: Set<string>, review: Set<string>): PolicyClass {
if (!aaguid || aaguid === "00000000-0000-0000-0000-000000000000") return "review";
if (allow.has(aaguid)) return "allow";
if (review.has(aaguid)) return "review";
return "deny"; // default deny, with a review route for the unknown class
}
Start by logging the class instead of rejecting. That is the report-only mode the guide recommends before you enforce anything.
The two rollout problems teams underestimate
The first problem is that rejected credentials still get created on the client. The server refuses them only after the ceremony completes, so the refused passkey can remain visible on the device. That creates confusing retries, failed logins and support tickets.
The second problem is lifecycle drift. The guide calls out the Credential Exchange Protocol (CXP) and Format (CXF) as a major gap: when a passkey moves between providers, the server does not see a new registration event. The credential keeps the same ID and public key, and your stored AAGUID becomes historical attribution rather than proof of the current provider.
That means restrictions are strongest at enrollment, weaker across the full credential lifecycle.
A short rollout checklist from the guide:
- Start in report-only mode.
- Apply restrictions to new registrations first.
- Let users enroll a replacement before retiring older credentials.
- Never mass-revoke credentials because of a registration-time AAGUID.
The real takeaway for enterprise CIAM
For banks and other regulated CIAM teams, the strongest pattern here is pretty balanced: prefer first-party providers at onboarding, qualify alternatives by implementation, require user verification everywhere and use an independent control for sensitive actions.
An AAGUID restriction helps most when you treat it as policy and observability infrastructure, not as a silver bullet. If your team wants to restrict passkey authenticators, the hard part is not writing the list. It's handling the edge cases: refused passkeys, unknown AAGUIDs, desktop variance and credentials that move later without telling you.
Read the full breakdown.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.