{"record":{"id":"b93da648c92033b0","repo":"excalidraw/excalidraw","slug":"failed-to-verify-jwt","errorCode":null,"errorMessage":"Failed to verify JWT","messagePattern":"Failed to verify JWT","errorType":"exception","errorClass":"ExcalidrawError","httpStatus":null,"severity":"error","filePath":"excalidraw-app/ExcalidrawPlusIframeExport.tsx","lineNumber":175,"sourceCode":"    const handleMessage = async (event: MessageEvent<MESSAGE_FROM_PLUS>) => {\n      if (event.origin !== EXCALIDRAW_PLUS_ORIGIN) {\n        throw new ExcalidrawError(\"Invalid origin\");\n      }\n\n      if (event.data.type === EVENT_REQUEST_SCENE) {\n        if (!event.data.jwt) {\n          throw new ExcalidrawError(\"JWT is missing\");\n        }\n\n        try {\n          try {\n            await verifyJWT({\n              token: event.data.jwt,\n              publicKey: import.meta.env.VITE_APP_PLUS_EXPORT_PUBLIC_KEY,\n            });\n          } catch (error: any) {\n            console.error(`Failed to verify JWT: ${error.message}`);\n            throw new ExcalidrawError(\"Failed to verify JWT\");\n          }\n\n          const parsedSceneData: MESSAGE_SCENE_DATA = await parseSceneData({\n            rawAppStateString: localStorage.getItem(\n              STORAGE_KEYS.LOCAL_STORAGE_APP_STATE,\n            ),\n            rawElementsString: localStorage.getItem(\n              STORAGE_KEYS.LOCAL_STORAGE_ELEMENTS,\n            ),\n          });\n\n          event.source!.postMessage(parsedSceneData, {\n            targetOrigin: EXCALIDRAW_PLUS_ORIGIN,\n          });\n        } catch (error) {\n          const responseData: MESSAGE_ERROR = {\n            type: \"ERROR\",\n            message:","sourceCodeStart":157,"sourceCodeEnd":193,"githubUrl":"https://github.com/excalidraw/excalidraw/blob/abeeaeba217ab3b5193b78c8d8d63c373b518ced/excalidraw-app/ExcalidrawPlusIframeExport.tsx#L157-L193","documentation":"ExcalidrawError('Failed to verify JWT') thrown when verifyJWT() rejects during the ExcalidrawPlus iframe export handshake. Any failure inside verifyJWT — missing public key env var, malformed token, signature mismatch, expired token, or invalid JSON payload — is caught, logged with the underlying message, and rewrapped as this generic error so the specific cryptographic reason is not leaked to the requester.","triggerScenarios":"event.data.jwt is expired, signed with the wrong key, malformed (missing header/payload/signature), or VITE_APP_PLUS_EXPORT_PUBLIC_KEY is unset/invalid; or crypto.subtle.importKey/verify rejects. The error is thrown before any scene data is exposed.","commonSituations":"Clock skew causing a valid token to appear expired, the Plus backend rotating its signing key without updating VITE_APP_PLUS_EXPORT_PUBLIC_KEY, a tampered token, an environment where the public key env var was not set at build time, or an old token reused after expiry.","solutions":["Check the console for 'Failed to verify JWT: <message>' which carries the underlying reason (expired, invalid signature, etc.).","Ensure VITE_APP_PLUS_EXPORT_PUBLIC_KEY is set at build time and matches the current Plus backend signing key.","Request a fresh JWT from the Plus parent and retry.","Verify the client clock is accurate (exp check is time-sensitive)."],"exampleFix":"// before\nawait verifyJWT({ token: jwt, publicKey: import.meta.env.VITE_APP_PLUS_EXPORT_PUBLIC_KEY });\n// throws -> generic 'Failed to verify JWT'\n\n// after: surface the real reason locally for debugging\nif (!import.meta.env.VITE_APP_PLUS_EXPORT_PUBLIC_KEY) {\n  console.error(\"VITE_APP_PLUS_EXPORT_PUBLIC_KEY is not set\");\n}\ntry {\n  await verifyJWT({ token: jwt, publicKey: import.meta.env.VITE_APP_PLUS_EXPORT_PUBLIC_KEY });\n} catch (e) {\n  console.error(\"JWT verification failed:\", e.message);\n}","handlingStrategy":"validation","validationCode":"if (!import.meta.env.VITE_APP_PLUS_EXPORT_PUBLIC_KEY) {\n  // cannot verify — do not attempt; key must be set at build time\n}\n// verify expiry client-side to give a clearer error pre-flight\nconst payload = JSON.parse(atob(event.data.jwt.split(\".\")[1]));\nif (payload.exp && payload.exp < Math.floor(Date.now()/1000)) {\n  // expired — request a new token before calling verifyJWT\n}","typeGuard":"const looksLikeJwt = (t: string): boolean =>\n  typeof t === \"string\" && t.split(\".\").length === 3;","tryCatchPattern":"try {\n  await verifyJWT({ token: jwt, publicKey: import.meta.env.VITE_APP_PLUS_EXPORT_PUBLIC_KEY });\n} catch (e) {\n  // console has 'Failed to verify JWT: <reason>' — surface generic error to requester\n}","preventionTips":["Set VITE_APP_PLUS_EXPORT_PUBLIC_KEY at build time and rotate with the backend.","Request fresh JWTs for each Plus-export session.","Keep client clocks accurate; exp is enforced.","Never expose verification details to the requesting parent."],"tags":["plus-export","jwt","security","authentication","iframe"],"analyzedSha":"abeeaeba217ab3b5193b78c8d8d63c373b518ced","analyzedAt":"2026-08-12T02:40:20.097Z","schemaVersion":2},"datasetVersion":"2026-08-12T08:17:17.861Z"}