{"record":{"id":"9580f299f94cfc58","repo":"toeverything/AFFiNE","slug":"invalid-authentication-url","errorCode":null,"errorMessage":"Invalid authentication url","messagePattern":"Invalid authentication url","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/frontend/apps/ios/src/app.tsx","lineNumber":748,"sourceCode":"  notify.error({\n    title: I18n['com.affine.auth.toast.title.failed'](),\n    message: getErrorMessage(error, fallback),\n  });\n};\n\nconst handleAuthenticationCallback = async (url: string) => {\n  const urlObj = new URL(url);\n\n  if (urlObj.hostname !== 'authentication') {\n    return;\n  }\n\n  const method = urlObj.searchParams.get('method');\n  const payload = JSON.parse(urlObj.searchParams.get('payload') ?? 'false');\n  const serverBaseUrl = urlObj.searchParams.get('server');\n\n  if (!method || (method !== 'magic-link' && method !== 'oauth') || !payload) {\n    throw new Error('Invalid authentication url');\n  }\n\n  let authService = frameworkProvider\n    .get(DefaultServerService)\n    .server.scope.get(AuthService);\n\n  if (serverBaseUrl) {\n    const serversService = frameworkProvider.get(ServersService);\n    const server = serversService.getServerByBaseUrl(serverBaseUrl);\n    if (!server) {\n      throw new Error(\n        `Authentication callback server not found: ${serverBaseUrl}`\n      );\n    }\n    authService = server.scope.get(AuthService);\n  }\n\n  if (method === 'oauth') {","sourceCodeStart":730,"sourceCodeEnd":766,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b6de0ad51b76f3daac2d3d6325369ea623ed7ed4/packages/frontend/apps/ios/src/app.tsx#L730-L766","documentation":"The iOS app's deep-link handler only processes URLs whose hostname is 'authentication'; it then requires a method query param of exactly 'magic-link' or 'oauth' and a truthy JSON-parsed payload param. Missing method, an unknown method, or a payload that parses to a falsy value throws 'Invalid authentication url' (app.tsx:724). A malformed payload param throws a SyntaxError from JSON.parse even earlier, at line 720.","triggerScenarios":"A callback URL like affine://authentication?foo=1 (no method), ...?method=saml (unsupported), ...?payload=false / payload=null, or payload absent. Also any payload that is valid JSON but evaluates falsy.","commonSituations":"Hand-built or truncated deep links during testing; URL-encoding bugs where the payload param is dropped or cut at an unencoded character (e.g. '{' or '&'); stale links generated by an older app/auth-flow version; custom-scheme routing mangling query strings.","solutions":["Capture the exact incoming URL and inspect its query params (method, payload, server) before anything else","Emit links in the canonical form: <scheme>://authentication?method=oauth|magic-link&payload=<encodeURIComponent(JSON)>&server=<baseUrl>","Make sure payload parses to a truthy JSON object ({code,state,provider} for oauth; {email,token} for magic-link)","Regenerate the callback with the current auth flow (updated app version) rather than a saved link"],"exampleFix":"// before: unencoded payload breaks the query string\nconst url = `affine://authentication?method=oauth&payload=${JSON.stringify(p)}`;\n// after: encode every parameter value\nconst url = `affine://authentication?method=oauth&payload=${encodeURIComponent(JSON.stringify(p))}`;","handlingStrategy":"validation","validationCode":"// validate the callback URL before acting on it\nconst u = new URL(url);\nif (u.hostname !== 'authentication') return;\nconst method = u.searchParams.get('method');\nlet payload: unknown;\ntry {\n  payload = JSON.parse(u.searchParams.get('payload') ?? 'null');\n} catch {\n  return notifyInvalidLink();\n}\nif (method !== 'magic-link' && method !== 'oauth') return notifyInvalidLink();\nif (!payload || typeof payload !== 'object') return notifyInvalidLink();\nawait handleAuthenticationCallback(url);","typeGuard":"const isAuthCallbackUrl = (url: string): boolean => {\n  try {\n    const u = new URL(url);\n    if (u.hostname !== 'authentication') return false;\n    const method = u.searchParams.get('method');\n    if (method !== 'magic-link' && method !== 'oauth') return false;\n    const p = u.searchParams.get('payload');\n    if (!p) return false;\n    const payload = JSON.parse(p);\n    return !!payload && typeof payload === 'object';\n  } catch {\n    return false;\n  }\n};","tryCatchPattern":"// already the app's pattern: notify, never crash the listener\nhandleAuthenticationCallback(url).catch(error =>\n  notifyAuthenticationError(error, 'Failed to handle authentication callback')\n);","preventionTips":["Always encodeURIComponent the payload JSON when constructing callback URLs","Keep one link-builder function so method/payload/server params are always consistent","Log the raw deep link on failure — the query string is the whole diagnosis"],"tags":["ios","deep-link","auth","url-parsing","capacitor"],"backgroundTag":"invalid-deep-link","analyzedSha":"b6de0ad51b76f3daac2d3d6325369ea623ed7ed4","analyzedAt":"2026-08-21T18:20:45.039Z","contentChangedAt":"2026-08-21T18:20:45.039Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}