Enrolment is the first half of WebAuthn: getting a credential onto the user's device and the matching public key onto the platform. Once enrolled, the credential is usable as a sign-in factor or a step-up factor.
The user does not see "WebAuthn"; they see "Set up Touch ID" or "Use a security key". Your UI uses that language. The SDK call below is the wire underneath.
The SDK call
Section titled “The SDK call”const { startMfaEnrolment } = useIntelliAuth()
async function enrollPasskey() { const result = await startMfaEnrolment('webauthn') if (result.kind === 'success') { // The credential is registered. Show a confirmation. } else if (result.kind === 'cancelled') { // The user dismissed the platform prompt. } else { // Inspect result.error for the specific failure code. }}The startMfaEnrolment call does the full WebAuthn ceremony:
- The SDK fetches a challenge + relying-party config from the platform.
- The SDK calls the browser's
navigator.credentials.create()API with that data. - The browser shows the platform's native prompt — Touch ID on macOS, Windows Hello on Windows, "Insert your security key" if a hardware key is configured.
- The user confirms. The browser returns a credential.
- The SDK ships the resulting public key back to the platform.
- The platform stores it against the user record.
If you're writing an SDK port for a stack we don't ship for, the OpenAPI spec is the contract for the underlying calls.
What the user sees
Section titled “What the user sees”Three different prompts depending on platform:
- macOS Safari / Chrome — a "Sign in with Touch ID" sheet from the bottom of the screen.
- Windows Chrome / Edge — Windows Hello dialog. PIN, face, or fingerprint depending on user setup.
- iOS Safari — Face ID / Touch ID sheet, and (if Bluetooth is on) the option to use a hardware key or "Continue on another device".
- Android Chrome — Google Password Manager sheet, with fingerprint / face options.
For hardware keys (YubiKey, Titan), the browser asks the user to insert and tap the key. This works on all platforms.
Naming the credential
Section titled “Naming the credential”The platform stores a label alongside the public key — "MacBook Pro", "Work YubiKey", "Phone". You should let the user choose this so they can recognise the credential later when the platform lists their MFA factors. The default label is "Passkey" + the credential's authenticator type.
const result = await startMfaEnrolment('webauthn', { label: 'My MacBook Pro',})Resident keys (passkeys) vs non-resident
Section titled “Resident keys (passkeys) vs non-resident”Two flavours:
- Resident keys / discoverable credentials (modern passkey behaviour). The credential is fully discoverable by the browser — at sign-in time, the user does not need to type a username. The browser presents the credentials it knows about and the user picks one.
- Non-resident keys (older WebAuthn pattern). The credential is identified by a server-issued credential id. To sign in, the user provides their username; the platform looks up the credential ids; the browser asks the authenticator about those specific ids.
For a new tenant, prefer resident keys — the user experience is dramatically smoother. The platform creates resident keys by default; you do not need to do anything special.
Cross-device enrolment
Section titled “Cross-device enrolment”Modern WebAuthn lets a user enroll a credential on one device that lives on another. Example: the user is on their Mac and wants to enroll a credential that lives on their iPhone. The prompt offers "Use a different device", which shows a QR code. The user scans it with their iPhone, completes Face ID on the iPhone, and the credential is enrolled.
This is built into the browser; you do not need to do anything in your app. It works on macOS Sonoma+ Safari/Chrome, iOS 16+, recent Chrome on Android.
Failure modes
Section titled “Failure modes”Most common, in order of likelihood:
| Symptom | Cause | What to do |
|---|---|---|
| User dismisses the prompt | Intentional cancel | Allow them to try again or pick another method |
NotAllowedError | User denied; or rate-limited from too many recent attempts | Wait, retry |
InvalidStateError | The authenticator already has a credential for this user | Tell them they're already enrolled |
NotSupportedError | The browser does not support WebAuthn at all | Fall back to TOTP / SMS |
SecurityError | Page is not over HTTPS, or the relying-party id is wrong | Fix the deployment; WebAuthn requires HTTPS |
WebAuthn over plain http://localhost is allowed for development. WebAuthn over http:// anywhere else fails.
Encouraging enrolment
Section titled “Encouraging enrolment”WebAuthn is the strongest factor available. The platform supports MFA enforcement policies that gently nudge users to enrol after their first sign-in. Configure the prompt cadence in the tenant admin console; the SDK surfaces it as a step in the sign-in flow.
Common policy: prompt at every sign-in for the first week, then "remind me later" for a year. Aggressive enough to drive adoption, gentle enough to avoid annoying users who genuinely want to defer.