Return the raw attestation object from passkeys register - #612
Return the raw attestation object from passkeys register#612nidal-augment wants to merge 3 commits into
Conversation
The registration ceremony's attestation object contains attested credential data (notably the AAGUID identifying the passkey provider) that exists only at registration time and is not stored by Stytch. The SDK already holds it; return it alongside the response so callers can parse what they need instead of it being discarded. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
For anyone wanting to consume this: the community-maintained AAGUID → provider mapping lives at passkeydeveloper/passkey-authenticator-aaguids — specifically aaguid.json, which maps each provider's AAGUID to a display name plus light/dark SVG icons (e.g. The extraction itself is small: CBOR-decode the attestation object ( And to restate the parity point from the description: the JS SDK would need the same change (if it isn't already exposed there) so web apps can build the same passkey-naming experience — happy to file that as an issue on the JS repo if useful. |
Mirrors the upstream proposal (stytchauth#612): the SDK returns the ceremony's attestation object verbatim and takes no opinion on interpretation. AAGUID/backup-flag parsing moves to the consuming app, shrinking this fork's delta to what upstream should carry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@ZhangYiJiang the initial test failure here was a flaky test. |
|
Okay I just looked through this and I think it would be better if it is an additive change, since I think if we're making the same change on the web side we'll definitely have to extend the object, not return a tuple, so it would be good for these to mirror each other. Let me know if that's a less common pattern in Swift, and sorry if this is a bit bikeshed-y |
Motivation
Relying parties increasingly want to show users where each passkey is stored — "Apple Passwords", "1Password", "Google Password Manager" — in account-management UIs. Without that, users with multiple passkeys are guessing which is which, and the generic names that result are an industry-wide complaint.
The structured signal for this is the AAGUID: a 16-byte identifier of the credential manager, embedded in the attested credential data of the WebAuthn attestation object. The major passkey providers deliberately populate it (even with
noneattestation) precisely so apps can label credentials, and there's a community-maintained AAGUID → provider-name/icon mapping that GitHub and others use for exactly this.Two properties make this a registration-time-only concern:
domain,user_agent,authenticator_type,name,verified— no AAGUID, and no way to retrieve the original attestation later.Today the SDK receives the attestation object during
StytchClient.passkeys.register, POSTs it to Stytch, and discards it — so the one moment this data exists is invisible to callers.Changes:
registernow returns the raw attestation object alongside the response:(response: BasicResponse, attestationObject: Data). It's the same value the SDK already holds fromcredential.rawAttestationObject; nothing changes on the wire or in the ceremony.testRegisterasserts the passthrough.Net diff is 9 lines across 3 files.
Why raw, rather than parsed?
We considered the deeper version: parse the CBOR in the SDK and expose
aaguid/backup-state fields, or go further and bundle the community AAGUID list so the SDK returns provider names directly. We deliberately kept this PR to the raw bytes:aaguid/backup-flags convenience if maintainers would take it, but it shouldn't gate the raw exposure.Real-world validation
We (Augment) run this change in production via our fork: parse the attestation object client-side (AAGUID at authenticator-data offset 37 per WebAuthn §6.1), resolve it against the community list, and immediately rename the passkey via
passkeys.update— e.g. "Apple Passwords — Jul 17, 2026" — giving users a passkey list that says where each credential lives. Verified end-to-end with real Apple Passwords registrations on device.Note on API shape
As written this changes
register's return type, which is source-breaking for existing callers. If you'd prefer a non-breaking shape (an additive overload, or a field on a response wrapper), we're glad to rework it — the essential ask is just: don't discard the attestation object.Web parity
The JavaScript SDK's
webauthn.registerappears to have the same gap — the headless response carries only the registration ids, not the attestation response (callers drivingnavigator.credentials.create()themselves notwithstanding). For relying parties to build consistent passkey-management UIs across platforms, the JS SDK would need the equivalent change.Checklist:
🤖 Generated with Claude Code