-
Notifications
You must be signed in to change notification settings - Fork 460
chore(clerk-js): Improve error handling of passkeys #3025
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
425406d
142b9a7
bdaa534
335ba7f
481e07d
405f162
0718cc9
29b05e3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| --- | ||
| '@clerk/localizations': minor | ||
| '@clerk/clerk-js': minor | ||
| '@clerk/types': minor | ||
| --- | ||
|
|
||
| Improved error handling for registration and retrieval of passkeys. | ||
| ClerkRuntimeError codes introduced: | ||
| - `passkey_not_supported` | ||
| - `passkeys_pa_not_supported` | ||
| - `passkey_invalid_rpID_or_domain` | ||
| - `passkey_already_exists` | ||
| - `passkey_operation_aborted` | ||
| - `passkey_retrieval_cancelled` | ||
| - `passkey_retrieval_failed` | ||
| - `passkey_registration_cancelled` | ||
| - `passkey_registration_failed` | ||
|
|
||
| Example usage: | ||
|
|
||
| ```ts | ||
| try { | ||
| await __experimental_authenticateWithPasskey(...args); | ||
| }catch (e) { | ||
| if (isClerkRuntimeError(e)) { | ||
| if (err.code === 'passkey_operation_aborted') { | ||
| ... | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -113,3 +113,9 @@ export function clerkInvalidRoutingStrategy(strategy?: string): never { | |
| export function clerkUnsupportedReloadMethod(className: string): never { | ||
| throw new Error(`${errorPrefix} Calling ${className}.reload is not currently supported. Please contact support.`); | ||
| } | ||
|
|
||
| export function clerkMissingWebAuthnPublicKeyOptions(name: 'create' | 'get'): never { | ||
| throw new Error( | ||
| `${errorPrefix} Missing publicKey. When calling 'navigator.credentials.${name}()' it is required to pass a publicKey object.`, | ||
| ); | ||
|
Comment on lines
+118
to
+120
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❓ Maybe we should be more descriptive of what the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where this function is used @panteliselef wrote |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,12 +25,18 @@ type WebAuthnGetCredentialReturn = | |
| CredentialReturn<__experimental_PublicKeyCredentialWithAuthenticatorAssertionResponse>; | ||
|
|
||
| type ClerkWebAuthnErrorCode = | ||
| | 'passkey_exists' | ||
| | 'passkey_retrieval_aborted' | ||
| // Generic | ||
| | 'passkey_not_supported' | ||
| | 'passkeys_pa_not_supported' | ||
| | 'passkey_invalid_rpID_or_domain' | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should this be all lower case? or is it aligning with a code sent back from FAPI?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FAPI also uses a lower case / snake case format. But these have nothing to do with FAPI as they will be thrown when something goes wrong when interacting with WebAuthN which is client only. |
||
| | 'passkey_already_exists' | ||
| | 'passkey_operation_aborted' | ||
| // Retrieval | ||
| | 'passkey_retrieval_cancelled' | ||
| | 'passkey_retrieval_failed' | ||
| // Registration | ||
| | 'passkey_registration_cancelled' | ||
| | 'passkey_credential_create_failed' | ||
| | 'passkey_credential_get_failed'; | ||
| | 'passkey_registration_failed'; | ||
|
|
||
| function isWebAuthnSupported() { | ||
| return ( | ||
|
|
@@ -92,7 +98,7 @@ async function webAuthnCreateCredential( | |
| if (!credential) { | ||
| return { | ||
| error: new ClerkWebAuthnError('Browser failed to create credential', { | ||
| code: 'passkey_credential_create_failed', | ||
| code: 'passkey_registration_failed', | ||
| }), | ||
| publicKeyCredential: null, | ||
| }; | ||
|
|
@@ -148,7 +154,7 @@ async function webAuthnGetCredential({ | |
|
|
||
| if (!credential) { | ||
| return { | ||
| error: new ClerkWebAuthnError('Browser failed to get credential', { code: 'passkey_credential_get_failed' }), | ||
| error: new ClerkWebAuthnError('Browser failed to get credential', { code: 'passkey_retrieval_failed' }), | ||
| publicKeyCredential: null, | ||
| }; | ||
| } | ||
|
|
@@ -159,33 +165,40 @@ async function webAuthnGetCredential({ | |
| } | ||
| } | ||
|
|
||
| function handlePublicKeyError(error: Error): ClerkWebAuthnError | ClerkRuntimeError | Error { | ||
| if (error.name === 'AbortError') { | ||
| return new ClerkWebAuthnError(error.message, { code: 'passkey_operation_aborted' }); | ||
| } | ||
| if (error.name === 'SecurityError') { | ||
| return new ClerkWebAuthnError(error.message, { code: 'passkey_invalid_rpID_or_domain' }); | ||
| } | ||
| return error; | ||
| } | ||
|
|
||
| /** | ||
| * Map webauthn errors from `navigator.credentials.create()` to Clerk-js errors | ||
| * @param error | ||
| */ | ||
| function handlePublicKeyCreateError(error: Error): ClerkWebAuthnError | ClerkRuntimeError | Error { | ||
| if (error.name === 'InvalidStateError') { | ||
| // Note: Firefox will throw 'NotAllowedError' when passkeys exists | ||
| return new ClerkWebAuthnError(error.message, { code: 'passkey_exists' }); | ||
| } else if (error.name === 'NotAllowedError') { | ||
| return new ClerkWebAuthnError(error.message, { code: 'passkey_already_exists' }); | ||
| } | ||
| if (error.name === 'NotAllowedError') { | ||
| return new ClerkWebAuthnError(error.message, { code: 'passkey_registration_cancelled' }); | ||
| } | ||
| return error; | ||
| return handlePublicKeyError(error); | ||
| } | ||
|
|
||
| /** | ||
| * Map webauthn errors from `navigator.credentials.get()` to Clerk-js errors | ||
| * @param error | ||
| */ | ||
| function handlePublicKeyGetError(error: Error): ClerkWebAuthnError | ClerkRuntimeError | Error { | ||
| if (error.name === 'AbortError') { | ||
| return new ClerkWebAuthnError(error.message, { code: 'passkey_retrieval_aborted' }); | ||
| } | ||
|
|
||
| if (error.name === 'NotAllowedError') { | ||
| return new ClerkWebAuthnError(error.message, { code: 'passkey_retrieval_cancelled' }); | ||
| } | ||
| return error; | ||
| return handlePublicKeyError(error); | ||
| } | ||
|
|
||
| function convertJSONToPublicKeyCreateOptions(jsonPublicKey: PublicKeyCredentialCreationOptionsJSON) { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'll probably want to add a changeset