{"record":{"id":"32ff8b88553d2ce9","repo":"medusajs/medusa","slug":"could-not-validate-the-identity-provider-s-respons","errorCode":null,"errorMessage":"Could not validate the identity provider's response: ${error.message}","messagePattern":"Could not validate the identity provider's response: (.+?)","errorType":"exception","errorClass":"MedusaError","httpStatus":401,"severity":"error","filePath":"packages/modules/providers/auth-oidc/src/engine/engine.ts","lineNumber":169,"sourceCode":"        \"An authorization 'code' is required to exchange for tokens\"\n      )\n    }\n\n    const client = await this.getClient_()\n    const redirectUri = input.callbackUrl ?? this.options_.callback_url\n\n    let tokenSet: TokenSet\n    try {\n      // Forward every authorization-response parameter so\n      // openid-client can enforce all applicable checks. The `checks` argument\n      // carries the values we stored ourselves (PKCE verifier, nonce, state).\n      tokenSet = await client.callback(redirectUri, params, {\n        code_verifier: input.codeVerifier,\n        nonce: input.nonce,\n        state: input.state,\n      })\n    } catch (error) {\n      throw new MedusaError(\n        MedusaError.Types.UNAUTHORIZED,\n        `Could not validate the identity provider's response: ${error.message}`\n      )\n    }\n\n    // Without the `openid` scope, the token endpoint returns no ID token and\n    // `tokenSet.claims()` would throw an unhelpful TypeError.\n    if (!tokenSet.id_token) {\n      throw new MedusaError(\n        MedusaError.Types.UNAUTHORIZED,\n        \"The identity provider did not return an ID token; ensure the 'openid' scope is requested\"\n      )\n    }\n\n    const claims = tokenSet.claims()\n\n    return {\n      claims: { ...claims },","sourceCodeStart":151,"sourceCodeEnd":187,"githubUrl":"https://github.com/medusajs/medusa/blob/5e06e544a296b9033f20f71f11c559f81a0e5739/packages/modules/providers/auth-oidc/src/engine/engine.ts#L151-L187","documentation":"The engine wraps every failure from openid-client's client.callback() (token request, state/nonce mismatch, expired code, network error) as an UNAUTHORIZED MedusaError with the underlying message appended. It means the IdP's response could not be validated.","triggerScenarios":"The state or nonce sent back by the provider does not match what was issued; the authorization code was already used or expired; the token endpoint rejected the client (bad client_secret/redirect URI); PKCE code_verifier missing or wrong; network/TLS failure reaching the token endpoint.","commonSituations":"Cookies holding state/nonce were dropped (SameSite issues in cross-domain callbacks, Safari ITP, load balancer stripping cookies); double callback invocation reusing a code; client_secret rotated in the IdP but not in config; clock skew causing JWT validation failures.","solutions":["Read the appended underlying error.message — it names the exact cause (e.g. 'state mismatch', 'invalid_grant').","Verify the cookie/session that stored state and nonce survives the round trip (SameSite=None; Secure in production, shared session store behind load balancers).","Confirm client_secret, client_id and callback_url in medusa-config.js still match the IdP app registration.","Ensure codeVerifier and nonce from the authorization step are passed unchanged to exchangeCode, and that each code is exchanged exactly once."],"exampleFix":"// before\nconst result = await engine.exchangeCode({ params, state, nonce })\n// after\ntry {\n  const result = await engine.exchangeCode({ params, state, nonce, codeVerifier })\n} catch (e) {\n  logger.error(`OIDC callback failed: ${e.message}`) // inspect underlying reason\n  throw e\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const result = await engine.exchangeCode(input)\n} catch (e) {\n  if (e instanceof MedusaError && e.type === MedusaError.Types.UNAUTHORIZED) {\n    logger.warn(`OIDC callback rejected: ${e.message}`)\n    return res.redirect(`/auth?error=oidc_callback_failed`)\n  }\n  throw e\n}","preventionTips":["Keep state/nonce/codeVerifier in a durable, cookie-backed store with SameSite=None; Secure in production.","Exchange each authorization code exactly once.","Rotate client secrets in config at the same time as the IdP.","Log the appended underlying message; it pinpoints the cause."],"tags":["oidc","token-exchange","openid-client","unauthorized"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"5e06e544a296b9033f20f71f11c559f81a0e5739","analyzedAt":"2026-08-27T07:24:39.599Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}