{"record":{"id":"f3e6538490cf9ea8","repo":"Wei-Shaw/sub2api","slug":"passkeys-are-not-supported-by-this-browser","errorCode":null,"errorMessage":"Passkeys are not supported by this browser","messagePattern":"Passkeys are not supported by this browser","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/api/passkey.ts","lineNumber":21,"sourceCode":"\nexport interface PasskeyCredentialSummary {\n  id: number\n  name: string\n  created_at: string\n  last_used_at?: string\n  backup: boolean\n}\n\ninterface CeremonyOptionsResponse {\n  session_token: string\n  options: {\n    publicKey: Record<string, unknown>\n  }\n}\n\nfunction requirePasskeySupport(): void {\n  if (!window.PublicKeyCredential || !navigator.credentials) {\n    throw new Error('Passkeys are not supported by this browser')\n  }\n}\n\nfunction base64URLToBuffer(value: string): ArrayBuffer {\n  const normalized = value.replace(/-/g, '+').replace(/_/g, '/')\n  const padded = normalized + '='.repeat((4 - (normalized.length % 4)) % 4)\n  const binary = atob(padded)\n  const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0))\n  return bytes.buffer\n}\n\nfunction bufferToBase64URL(value: ArrayBuffer | null): string | null {\n  if (value === null) return null\n  const bytes = new Uint8Array(value)\n  let binary = ''\n  for (const byte of bytes) binary += String.fromCharCode(byte)\n  return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/g, '')\n}","sourceCodeStart":3,"sourceCodeEnd":39,"githubUrl":"https://github.com/Wei-Shaw/sub2api/blob/073e92d17178a1ccdb0a27017f572f10c9c7ab62/frontend/src/api/passkey.ts#L3-L39","documentation":"requirePasskeySupport() in frontend/src/api/passkey.ts:21 throws when window.PublicKeyCredential or navigator.credentials is undefined. These APIs (WebAuthn) are only exposed in secure contexts (HTTPS or localhost) and in browsers that implement WebAuthn (all modern ones since ~2018). The check runs before every login()/register() call, so any environment lacking WebAuthn fails fast before a network round trip.","triggerScenarios":"Calling login() or register() from passkey.ts while: the page is served over plain HTTP on a non-localhost host; the browser is pre-2018 (IE11, old Safari < 16.4 on Windows, legacy Android WebView); WebAuthn is disabled by enterprise policy; or the code runs in a non-browser/jsdom test environment where the globals were never installed.","commonSituations":"Dev servers accessed over http://192.168.x.x from a phone (not a secure context); older in-app browsers; unit tests in Vitest/Jest that render components calling passkey login without stubbing PublicKeyCredential; captive-portal/proxied environments that strip HTTPS.","solutions":["Serve the frontend over HTTPS (or use http://localhost / http://127.0.0.1 during development) so WebAuthn APIs are exposed.","Before showing passkey UI, feature-detect and hide passkey buttons when window.PublicKeyCredential is missing rather than letting login() throw.","In tests, polyfill/stub window.PublicKeyCredential and navigator.credentials in the test setup file.","If users are on old browsers, offer a password/OTP fallback sign-in path."],"exampleFix":"// before\nfunction requirePasskeySupport(): void {\n  if (!window.PublicKeyCredential || !navigator.credentials) {\n    throw new Error('Passkeys are not supported by this browser')\n  }\n}\n\n// after\nexport function isPasskeySupported(): boolean {\n  return typeof window.PublicKeyCredential === 'function' && !!navigator.credentials;\n}\nfunction requirePasskeySupport(): void {\n  if (!isPasskeySupported()) {\n    throw new Error('Passkeys are not supported by this browser')\n  }\n}\n// template: <button v-if=\"isPasskeySupported()\">Sign in with passkey</button>","handlingStrategy":"validation","validationCode":"export function isPasskeySupported(): boolean {\n  return typeof window.PublicKeyCredential === 'function' && !!navigator.credentials && window.isSecureContext;\n}\n// call before rendering passkey UI:\nif (!isPasskeySupported()) hidePasskeyButtons();","typeGuard":"function isPasskeySupported(): boolean {\n  return typeof window.PublicKeyCredential === 'function'\n    && !!navigator.credentials\n    && window.isSecureContext === true;\n}","tryCatchPattern":"try { await login(); } catch (e) {\n  if (e instanceof Error && e.message === 'Passkeys are not supported by this browser') {\n    showPasswordFallback(); return;\n  }\n  throw e;\n}","preventionTips":["Always develop/test passkey flows on HTTPS or localhost","Feature-detect before showing passkey buttons, not only before the API call","Stub window.PublicKeyCredential/navigator.credentials in unit test setup files","Keep a password/OTP fallback route for users on old browsers"],"tags":["webauthn","passkeys","browser-support","secure-context","frontend"],"backgroundTag":null,"analyzedSha":"073e92d17178a1ccdb0a27017f572f10c9c7ab62","analyzedAt":"2026-08-15T14:33:00.750Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}