{"record":{"id":"2fa078af4f052637","repo":"medusajs/medusa","slug":"an-authorization-code-is-required-to-exchange-fo","errorCode":null,"errorMessage":"An authorization 'code' is required to exchange for tokens","messagePattern":"An authorization 'code' is required to exchange for tokens","errorType":"exception","errorClass":"MedusaError","httpStatus":400,"severity":"error","filePath":"packages/modules/providers/auth-oidc/src/engine/engine.ts","lineNumber":149,"sourceCode":"      code_challenge_method: \"S256\",\n    })\n\n    return { url, nonce, codeVerifier }\n  }\n\n  /**\n   * Exchanges the authorization code for tokens and performs full ID-token\n   * validation via `openid-client` (signature through JWKS, `iss`, `aud`/`azp`,\n   * `exp`/`iat`/`nbf` with clock tolerance, and `nonce`). Returns the validated\n   * claims plus the tokens.\n   */\n  async exchangeCode(\n    input: OidcExchangeCodeInput\n  ): Promise<OidcExchangeCodeResult> {\n    const params = input?.params ?? {}\n\n    if (!params.code) {\n      throw new MedusaError(\n        MedusaError.Types.INVALID_DATA,\n        \"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      })","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/medusajs/medusa/blob/5e06e544a296b9033f20f71f11c559f81a0e5739/packages/modules/providers/auth-oidc/src/engine/engine.ts#L131-L167","documentation":"exchangeCode is the step that swaps the authorization code for tokens, so a code is mandatory. The engine throws INVALID_DATA when params.code is missing before contacting the token endpoint.","triggerScenarios":"Calling engine.exchangeCode({ params: {...} }) where params has no code key, e.g. the callback route was hit without ?code= in the query, or the query params were not forwarded from the provider redirect.","commonSituations":"The identity provider redirected with an error (e.g. access_denied) instead of a code and the callback handler blindly calls exchangeCode; a callback route that reads the wrong query field; integration tests that mock the callback without a code.","solutions":["Inspect the callback request's query string: if code is absent there is usually an error or error_description query param — surface that to the user instead.","Only call exchangeCode when req.query.code is present; otherwise re-initiate the flow via buildAuthorizationUrl.","Pass the full provider query params object through: exchangeCode({ params: req.query as Record<string, string>, ... })."],"exampleFix":"// before\nconst result = await engine.exchangeCode({ params: req.query as any, state, nonce, codeVerifier })\n// after\nif (!req.query.code) {\n  return res.status(400).json({ error: req.query.error ?? \"missing_authorization_code\" })\n}\nconst result = await engine.exchangeCode({ params: req.query as Record<string, string>, state, nonce, codeVerifier })","handlingStrategy":"type-guard","validationCode":"const code = (req.query as Record<string,string>).code\nif (!code) return res.redirect(\"/auth?error=missing_code\")\nawait engine.exchangeCode({ params: req.query as Record<string,string>, ... })","typeGuard":"const hasCode = (p: Record<string, unknown> | undefined): p is Record<string, string> & { code: string } =>\n  typeof p?.code === \"string\" && p.code.length > 0","tryCatchPattern":"try { await engine.exchangeCode(input) } catch (e) { if (e instanceof MedusaError && e.type === MedusaError.Types.INVALID_DATA && /'code'/.test(e.message)) { res.status(400).json({ error: \"missing_code\" }); return } throw e }","preventionTips":["Check req.query.error before processing the callback.","Only invoke exchangeCode when a code query param exists.","Forward the entire provider query string, not hand-picked fields."],"tags":["oidc","authorization-code","callback","validation"],"backgroundTag":"missing-oauth-authorization-code","analyzedSha":"5e06e544a296b9033f20f71f11c559f81a0e5739","analyzedAt":"2026-08-27T07:24:39.599Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}