{"record":{"id":"4b9d7c05d57be20f","repo":"gitroomhq/postiz-app","slug":"invalid-state-4b9d7c","errorCode":null,"errorMessage":"Invalid state","messagePattern":"Invalid state","errorType":"error_code","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/backend/src/services/auth/auth.service.ts","lineNumber":310,"sourceCode":"    const providerInstance = this._providerManager.getProvider(provider);\n    return providerInstance.generateLink(query);\n  }\n\n  async checkExists(\n    provider: string,\n    code: string,\n    redirectUri?: string,\n    state?: string,\n    stateCookie?: string\n  ) {\n    // the mobile app passes redirect_uri and keeps no cookies, the web flow\n    // never passes it, so the state nonce is only enforced for the web flow\n    if (\n      !process.env.NOT_SECURED &&\n      !redirectUri &&\n      (!state || state !== stateCookie)\n    ) {\n      throw new Error('Invalid state');\n    }\n\n    const providerInstance = this._providerManager.getProvider(provider);\n    const token = await providerInstance.getToken(code, redirectUri);\n    const user = await providerInstance.getUser(token);\n    if (!user) {\n      throw new Error('Invalid user');\n    }\n    const checkExists = await this._userService.getUserByProvider(\n      user.id,\n      provider as Provider\n    );\n    if (checkExists) {\n      return { jwt: await this.jwt(checkExists) };\n    }\n\n    return { token };\n  }","sourceCodeStart":292,"sourceCodeEnd":328,"githubUrl":"https://github.com/gitroomhq/postiz-app/blob/0f1647f7491a217d43eb5ae7a480484bdf0aff3e/apps/backend/src/services/auth/auth.service.ts#L292-L328","documentation":"OAuth CSRF protection: checkExists compares the `state` query parameter against the state cookie written when the OAuth flow started. If they mismatch (or state is missing) the request is rejected. The check is skipped when NOT_SECURED is set or when a redirectUri (native flow) is supplied.","triggerScenarios":"Completing an OAuth callback where the `state` param was lost/changed, the state cookie is missing (cleared cookies, different domain, cross-browser), the flow started in one browser and finished in another, or the cookie expired before callback.","commonSituations":"Cookies blocked by browser settings or SameSite issues behind a proxy; opening the callback URL in a new browser/device; misconfigured frontend/backend domains so the cookie never reaches the callback request; native app flow passing the wrong arguments so redirectUri is empty.","solutions":["Restart the OAuth flow from the beginning in the same browser session so a fresh state cookie is set","Verify the frontend and backend share the domain/path expected by the state cookie (check cookie domain, SameSite, secure flags behind HTTPS)","Do not manually copy/paste callback URLs between browsers or incognito windows","For local testing only, set NOT_SECURED to bypass (never in production)","If implementing a native flow, pass redirectUri so the web-only state nonce isn't enforced"],"exampleFix":"// before — starting flow in one context, finishing in another\nwindow.location.href = authUrl; // cookie set here\n// callback opened in a different browser -> 'Invalid state'\n\n// after — ensure same browser session for both legs\nconst state = crypto.randomUUID();\ndocument.cookie = `oauth_state=${state}; path=/; SameSite=Lax`;\nwindow.location.href = `${authUrl}&state=${state}`;","handlingStrategy":"try-catch","validationCode":"const stateCookie = getCookie('state');\nif (!stateCookie) {\n  restartOAuthFlow(); // no cookie -> callback will always fail\n}","typeGuard":"null","tryCatchPattern":"try {\n  await authService.checkExists({ code, provider, state });\n} catch (e) {\n  if (e instanceof Error && e.message === 'Invalid state') {\n    // restart the flow from step 1; never retry the same callback URL\n    return redirectToLogin();\n  }\n  throw e;\n}","preventionTips":["Always start and finish the OAuth flow in the same browser session","Keep state cookies short-lived and scoped to the auth path","Don't deep-link or share callback URLs"],"tags":["oauth","csrf","state","cookies"],"backgroundTag":"oauth-state-mismatch","analyzedSha":"0f1647f7491a217d43eb5ae7a480484bdf0aff3e","analyzedAt":"2026-08-27T12:09:55.020Z","schemaVersion":2},"datasetVersion":"2026-08-27T13:17:12.746Z"}