{"record":{"id":"427c4ad018df32b6","repo":"laurent22/joplin","slug":"could-not-verify-your-identity-s","errorCode":null,"errorMessage":"Could not verify your identity: %s","messagePattern":"Could not verify your identity: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/app-mobile/components/biometrics/biometricAuthenticate.ts","lineNumber":26,"sourceCode":"\tlogger.info('Authenticate...');\n\tconst result = await authenticateAsync({ promptMessage: _('Verify your identity') });\n\n\tif (result.success === false) {\n\t\tconst errorName = result.error;\n\t\tif (errorName === 'not_enrolled' || errorName === 'not_available') {\n\t\t\t// In that case we skip the check because the device biometric unlock has been disabled\n\t\t\t// by the user. It should be safe to skip the check since in order to disable it, they\n\t\t\t// must have full access to the phone, and should have to enter their pin. Not skipping\n\t\t\t// the check would be a problem if biometric unlock was disabled as a result of being\n\t\t\t// broken. In this case, the user will never be able to unlock Joplin.\n\t\t\t// Ref: https://github.com/laurent22/joplin/issues/10926\n\n\t\t\tlogger.warn('Biometric unlock is not setup on the device - skipping check');\n\t\t\treturn;\n\n\t\t\t// errorMessage = _('Biometric unlock is not setup on the device. Please set it up in order to unlock Joplin. If the device is on lockout, consider switching it off and on to reset biometrics scanning.');\n\t\t}\n\t\tthrow new Error(_('Could not verify your identity: %s', errorName));\n\t}\n\n\tlogger.info('Authenticate done');\n};\n","sourceCodeStart":8,"sourceCodeEnd":31,"githubUrl":"https://github.com/laurent22/joplin/blob/2654b33620775080d1d59c552259d41e33dad3d2/packages/app-mobile/components/biometrics/biometricAuthenticate.ts#L8-L31","documentation":"Thrown by `biometricAuthenticate` when the underlying biometric API rejects the authentication attempt. The `%s` placeholder is filled with `errorName` from the OS biometrics call. The earlier branch that handles 'biometric unlock not setup' returns early; reaching this throw means a biometric prompt was shown and failed (e.g. no match, user cancelled, hardware error).","triggerScenarios":"User submits a fingerprint/face that is not enrolled, cancels the prompt, or the sensor times out / lockout after too many failed attempts. Any non-recoverable failure from the native biometrics API after the 'not setup' early-return has been skipped.","commonSituations":"Wrong finger presented; face unlock obscured; biometric sensor hardware fault; device policy enforcing biometric lockout after repeated failures; the user dismissed the system prompt.","solutions":["Catch the error and offer the fallback unlock path (master password / PIN).","Detect a 'user cancelled' errorName and silently return rather than surfacing it as a hard failure.","If errorName indicates lockout, inform the user to wait or restart the device to reset the sensor (ref #10926).","Ensure biometrics are actually enrolled on the device before prompting."],"exampleFix":"// before\nawait biometricAuthenticate();\n\n// after\ntry {\n  await biometricAuthenticate();\n} catch (error) {\n  if (/cancelled/i.test(error.message)) return;\n  await showFallbackPasswordUnlock();\n}","handlingStrategy":"fallback","validationCode":"null","typeGuard":"null","tryCatchPattern":"try {\n  await biometricAuthenticate();\n} catch (error) {\n  const msg = error.message || '';\n  if (/cancelled/i.test(msg)) return; // user-initiated cancel\n  if (/lockout/i.test(msg)) {\n    await showLockoutGuidance();\n  }\n  await fallbackToPasswordUnlock();\n}","preventionTips":["Always provide a non-biometric fallback unlock path.","Detect cancellation errors and treat them as a no-op rather than a failure.","Check that biometrics are enrolled before prompting."],"tags":["biometrics","security","mobile","auth"],"backgroundTag":null,"analyzedSha":"2654b33620775080d1d59c552259d41e33dad3d2","analyzedAt":"2026-08-12T14:26:46.263Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}