medusajs/medusa · error · MedusaError
A 'state' value is required to build the authorization URL
Error message
A 'state' value is required to build the authorization URL
What it means
buildAuthorizationUrl requires a state value because OIDC uses it to protect against CSRF in the callback flow. The engine throws INVALID_DATA when input.state is falsy before it can construct the provider's authorization URL.
Source
Thrown at packages/modules/providers/auth-oidc/src/engine/engine.ts:111
this.options_ = options
this.discoveryCacheTtlMs_ =
options.discovery_cache_ttl_ms ?? DEFAULT_DISCOVERY_CACHE_TTL_MS
this.httpTimeoutMs_ = options.http_timeout_ms ?? DEFAULT_HTTP_TIMEOUT_MS
this.cache_ = cache
}
/**
* Builds the authorization URL to redirect the browser to, generating a fresh
* PKCE code verifier/challenge (S256) and nonce. The returned `nonce` and
* `codeVerifier` must be persisted alongside the state so they can be replayed
* when validating the callback.
*/
async buildAuthorizationUrl(
input: OidcBuildAuthorizationUrlInput
): Promise<OidcAuthorizationUrlResult> {
if (!input?.state) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"A 'state' value is required to build the authorization URL"
)
}
const client = await this.getClient_()
const codeVerifier = generators.codeVerifier()
const codeChallenge = generators.codeChallenge(codeVerifier)
const nonce = generators.nonce()
const scopes = input.scopes ?? this.options_.scopes ?? DEFAULT_SCOPES
const url = client.authorizationUrl({
scope: scopes.join(" "),
redirect_uri: input.callbackUrl ?? this.options_.callback_url,
state: input.state,
nonce,View on GitHub (pinned to 5e06e544a2)
Solutions
- Generate a random state value (e.g. crypto.randomUUID() or crypto.randomBytes(16).toString('hex')) and pass it in the input; persist it (cookie/session) so the callback can be validated.
- If you are not writing a custom flow, use the framework-provided OIDC authenticate route, which generates state for you.
- Check that the field is named exactly state in the OidcBuildAuthorizationUrlInput object.
Example fix
// before
const { url } = await engine.buildAuthorizationUrl({ /* no state */ })
// after
const state = crypto.randomUUID()
res.cookie("oidc_state", state, { httpOnly: true })
const { url } = await engine.buildAuthorizationUrl({ state }) Defensive patterns
Strategy: validation
Validate before calling
const state = crypto.randomUUID()
if (!state) throw new Error("state generation failed")
await engine.buildAuthorizationUrl({ state }) Type guard
const hasState = (i: OidcBuildAuthorizationUrlInput | undefined): i is OidcBuildAuthorizationUrlInput & { state: string } =>
typeof i?.state === "string" && i.state.length > 0 Try / catch
try { await engine.buildAuthorizationUrl(input) } catch (e) { if (e instanceof MedusaError && /'state' value is required/.test(e.message)) { /* regenerate state and retry once */ } throw e } Prevention
- Always generate state via crypto.randomUUID()/randomBytes before building the URL.
- Store state server-side (httpOnly cookie) in the same response that redirects.
- Use the framework-provided OIDC routes instead of hand-rolling the flow.
When it happens
Trigger: Calling engine.buildAuthorizationUrl({}) or with an undefined/empty state field, e.g. a route handler that forwards the request but forgot to generate or pass the state parameter.
Common situations: A custom auth route controller that manually calls buildAuthorizationUrl and assumes state is optional; a refactor that dropped the state generation step; passing the whole request body when state lives elsewhere.
Related errors
- An authorization 'code' is required to exchange for tokens
- --paths must be a directory - ${additionalPath}
- --base must be a file - ${baseFile}
- insufficient_inventory
- not_found
AI-assisted analysis of medusajs/medusa@5e06e544a2 (2026-08-27).
Data as JSON: /api/errors/9df8cd02c5ec05c3.
Report an issue: GitHub.