{"record":{"id":"9df8cd02c5ec05c3","repo":"medusajs/medusa","slug":"a-state-value-is-required-to-build-the-authoriza","errorCode":null,"errorMessage":"A 'state' value is required to build the authorization URL","messagePattern":"A 'state' value is required to build the authorization URL","errorType":"exception","errorClass":"MedusaError","httpStatus":400,"severity":"error","filePath":"packages/modules/providers/auth-oidc/src/engine/engine.ts","lineNumber":111,"sourceCode":"\n    this.options_ = options\n    this.discoveryCacheTtlMs_ =\n      options.discovery_cache_ttl_ms ?? DEFAULT_DISCOVERY_CACHE_TTL_MS\n    this.httpTimeoutMs_ = options.http_timeout_ms ?? DEFAULT_HTTP_TIMEOUT_MS\n    this.cache_ = cache\n  }\n\n  /**\n   * Builds the authorization URL to redirect the browser to, generating a fresh\n   * PKCE code verifier/challenge (S256) and nonce. The returned `nonce` and\n   * `codeVerifier` must be persisted alongside the state so they can be replayed\n   * when validating the callback.\n   */\n  async buildAuthorizationUrl(\n    input: OidcBuildAuthorizationUrlInput\n  ): Promise<OidcAuthorizationUrlResult> {\n    if (!input?.state) {\n      throw new MedusaError(\n        MedusaError.Types.INVALID_DATA,\n        \"A 'state' value is required to build the authorization URL\"\n      )\n    }\n\n    const client = await this.getClient_()\n\n    const codeVerifier = generators.codeVerifier()\n    const codeChallenge = generators.codeChallenge(codeVerifier)\n    const nonce = generators.nonce()\n\n    const scopes = input.scopes ?? this.options_.scopes ?? DEFAULT_SCOPES\n\n    const url = client.authorizationUrl({\n      scope: scopes.join(\" \"),\n      redirect_uri: input.callbackUrl ?? this.options_.callback_url,\n      state: input.state,\n      nonce,","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/medusajs/medusa/blob/5e06e544a296b9033f20f71f11c559f81a0e5739/packages/modules/providers/auth-oidc/src/engine/engine.ts#L93-L129","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst { url } = await engine.buildAuthorizationUrl({ /* no state */ })\n// after\nconst state = crypto.randomUUID()\nres.cookie(\"oidc_state\", state, { httpOnly: true })\nconst { url } = await engine.buildAuthorizationUrl({ state })","handlingStrategy":"validation","validationCode":"const state = crypto.randomUUID()\nif (!state) throw new Error(\"state generation failed\")\nawait engine.buildAuthorizationUrl({ state })","typeGuard":"const hasState = (i: OidcBuildAuthorizationUrlInput | undefined): i is OidcBuildAuthorizationUrlInput & { state: string } =>\n  typeof i?.state === \"string\" && i.state.length > 0","tryCatchPattern":"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 }","preventionTips":["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."],"tags":["oidc","csrf","authorization-url","validation"],"backgroundTag":"missing-oauth-state-parameter","analyzedSha":"5e06e544a296b9033f20f71f11c559f81a0e5739","analyzedAt":"2026-08-27T07:24:39.599Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}