transloadit/uppy · error · Error
Invalid OAuth state payload
Error message
Invalid OAuth state payload
What it means
decodeState decrypts the OAuth `state` parameter, base64-decodes and JSON.parses it, then validates it with isOAuthState. If the shape check fails, it throws 'Invalid OAuth state payload' — the state did not originate from Companion's generateState.
Source
Thrown at packages/@uppy/companion/src/server/helpers/oauth-state.ts:30
authCallbackToken?: string
}
export const encodeState = (
state: OAuthState,
secret: string | Buffer,
): string => {
const encodedState = Buffer.from(JSON.stringify(state)).toString('base64')
return encrypt(encodedState, secret)
}
export const decodeState = (
state: string,
secret: string | Buffer,
): OAuthState => {
const encodedState = decrypt(state, secret)
const parsed: unknown = JSON.parse(atob(encodedState))
if (!isOAuthState(parsed)) {
throw new Error('Invalid OAuth state payload')
}
return parsed
}
export const generateState = (): OAuthState => {
return {
id: crypto.randomBytes(10).toString('hex'),
}
}
function isOAuthState(value: unknown): value is OAuthState {
return isRecord(value) && typeof value['id'] === 'string'
}
export const getFromState = <T extends keyof OAuthState>(
state: string,
name: T,
secret: string | Buffer,View on GitHub (pinned to 5d4dedd02a)
Solutions
- Ensure all Companion instances behind a load balancer share the same COMPANION_SECRET so state encrypts/decrypts consistently
- Restart the OAuth flow to obtain a fresh state rather than replaying an old callback URL
- Verify COMPANION_CLIENT_ID/SECRET and the provider OAuth app's redirect URI match the Companion host serving the callback
Example fix
# before (two companions with different secrets) COMPANION_SECRET=aaaa # instance 1 starts flow COMPANION_SECRET=bbbb # instance 2 receives callback -> Invalid OAuth state payload # after COMPANION_SECRET=shared-secret # both instances
Defensive patterns
Strategy: validation
Validate before calling
const stateOk = (s: string) => { try { return Boolean(decodeState(s, secret)) } catch { return false } }
if (!stateOk(req.query.state)) return res.status(400).send('bad state') Try / catch
try { getFromState(req, key, secret) } catch (err) {
if (err.message === 'Invalid OAuth state payload') { res.redirect('/connect/provider') /* restart flow */ }
} Prevention
- Share COMPANION_SECRET across all Companion replicas
- Never bookmark/replay OAuth callback URLs; restart the flow instead
When it happens
Trigger: Hitting an OAuth callback URL with a `state` query param that is malformed, truncated, from a different secret/environment, or hand-crafted — anything that decrypts+parses but doesn't satisfy isOAuthState.
Common situations: OAuth callbacks crossing environments (token created with a different COMPANION_SECRET), load-balanced Companions with mismatched secrets, stale/cached callback URLs, or manual testing with fabricated state values.
Related errors
- Missing S3 object key for aborting upload
- Missing S3 object key for resuming upload
- Missing S3 object key for uploading part
- Missing S3 object key for completing multipart upload
- companionEndpoint must be a string
AI-assisted analysis of transloadit/uppy@5d4dedd02a (2026-08-28).
Data as JSON: /api/errors/1e9a3055a67b99c8.
Report an issue: GitHub.