gitroomhq/postiz-app · error · Error
Invalid state
Error message
Invalid state
What it means
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.
Source
Thrown at apps/backend/src/services/auth/auth.service.ts:310
const providerInstance = this._providerManager.getProvider(provider);
return providerInstance.generateLink(query);
}
async checkExists(
provider: string,
code: string,
redirectUri?: string,
state?: string,
stateCookie?: string
) {
// the mobile app passes redirect_uri and keeps no cookies, the web flow
// never passes it, so the state nonce is only enforced for the web flow
if (
!process.env.NOT_SECURED &&
!redirectUri &&
(!state || state !== stateCookie)
) {
throw new Error('Invalid state');
}
const providerInstance = this._providerManager.getProvider(provider);
const token = await providerInstance.getToken(code, redirectUri);
const user = await providerInstance.getUser(token);
if (!user) {
throw new Error('Invalid user');
}
const checkExists = await this._userService.getUserByProvider(
user.id,
provider as Provider
);
if (checkExists) {
return { jwt: await this.jwt(checkExists) };
}
return { token };
}View on GitHub (pinned to 0f1647f749)
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
Example fix
// before — starting flow in one context, finishing in another
window.location.href = authUrl; // cookie set here
// callback opened in a different browser -> 'Invalid state'
// after — ensure same browser session for both legs
const state = crypto.randomUUID();
document.cookie = `oauth_state=${state}; path=/; SameSite=Lax`;
window.location.href = `${authUrl}&state=${state}`; Defensive patterns
Strategy: try-catch
Validate before calling
const stateCookie = getCookie('state');
if (!stateCookie) {
restartOAuthFlow(); // no cookie -> callback will always fail
} Type guard
null
Try / catch
try {
await authService.checkExists({ code, provider, state });
} catch (e) {
if (e instanceof Error && e.message === 'Invalid state') {
// restart the flow from step 1; never retry the same callback URL
return redirectToLogin();
}
throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Organization not found
- Integration not allowed
- Function not found
- Integration not allowed
- This integration requires an external URL and is not support
AI-assisted analysis of gitroomhq/postiz-app@0f1647f749 (2026-08-27).
Data as JSON: /api/errors/4b9d7c05d57be20f.
Report an issue: GitHub.