{"record":{"id":"e4711f8450c23de0","repo":"alibaba/nacos","slug":"token-exchange-failed","errorCode":null,"errorMessage":"Token exchange failed: ","messagePattern":"Token exchange failed: ","errorType":"exception","errorClass":"AccessException","httpStatus":null,"severity":"error","filePath":"plugin-default-impl/nacos-oidc-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/oidc/authenticate/AuthorizationCodeHandler.java","lineNumber":242,"sourceCode":"        \n        // Client authentication\n        ClientAuthentication clientAuth = new ClientSecretBasic(\n            new ClientID(config.getClientId()),\n            new Secret(config.getClientSecret()));\n        \n        // Send token request\n        TokenRequest tokenRequest = new TokenRequest(\n            URI.create(tokenEndpoint),\n            clientAuth,\n            grant);\n        \n        TokenResponse tokenResponse =\n            OIDCTokenResponseParser.parse(tokenRequest.toHTTPRequest().send());\n        \n        if (!tokenResponse.indicatesSuccess()) {\n            String error = tokenResponse.toErrorResponse().getErrorObject().getDescription();\n            LOGGER.error(\"Token exchange failed: {}\", error);\n            throw new AccessException(\"Token exchange failed: \" + error);\n        }\n        \n        OIDCTokenResponse oidcResponse = (OIDCTokenResponse) tokenResponse.toSuccessResponse();\n        return oidcResponse.getOIDCTokens();\n    }\n    \n    /**\n     * Generate a secure random token for state/nonce.\n     *\n     * @return base64-encoded random token\n     */\n    private String generateSecureToken() {\n        byte[] bytes = new byte[32];\n        secureRandom.nextBytes(bytes);\n        return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);\n    }\n    \n    /**","sourceCodeStart":224,"sourceCodeEnd":260,"githubUrl":"https://github.com/alibaba/nacos/blob/9b989acdf181d00898f2e8839257bb2b2a3cefe3/plugin-default-impl/nacos-oidc-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/oidc/authenticate/AuthorizationCodeHandler.java#L224-L260","documentation":"Thrown when the IdP's token endpoint returned a non-success (error) response to the authorization-code exchange. The IdP's own error description is appended to the message. This is the IdP rejecting the code-for-token exchange, not a Nacos-side validation.","triggerScenarios":"Calling exchangeCodeForUser after the IdP callback when the authorization code is expired/already-used, the redirect_uri does not exactly match the one sent in the authorization request, the client_id/client_secret are wrong, or the code was issued for a different client.","commonSituations":"Clock skew causing code expiry; redirect URI mismatch between the authorization request and the callback handler; rotated client secret not updated in Nacos config; replaying a code after a retry; IdP behind a proxy rewriting redirect URIs.","solutions":["Read the appended IdP error description (e.g. 'invalid_grant', 'invalid_client') — it names the exact cause.","For 'invalid_grant': ensure the callback redirect_uri exactly matches buildAuthorizationUrl's redirectUri argument and the code is fresh (single-use).","For 'invalid_client': verify client-id and client-secret in the OIDC config match the IdP registration.","Resync server clocks if code-expiry errors appear with valid timing.","Avoid retrying the same authorization code — generate a new login."],"exampleFix":"// before: redirect URI mismatch between auth request and callback\nString authUrl = handler.buildAuthorizationUrl(\"https://nacos/callback\");\n// ... user returns to a different path ...\nhandler.exchangeCodeForUser(code, state, \"https://nacos/oidc/callback\");\n// after: use the identical redirect URI for both steps\nString redirect = \"https://nacos.example.com/oidc/callback\";\nString authUrl = handler.buildAuthorizationUrl(redirect);\nhandler.exchangeCodeForUser(code, state, redirect);","handlingStrategy":"try-catch","validationCode":"// Ensure the redirect URI used for exchange matches the one used to build the auth URL\nif (!redirectUri.equals(originalRedirectUri)) {\n    throw new IllegalArgumentException(\"redirect_uri mismatch will be rejected by IdP\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    handler.exchangeCodeForUser(code, state, redirectUri);\n} catch (AccessException e) {\n    String msg = e.getMessage();\n    if (msg.startsWith(\"Token exchange failed: \")) {\n        // Do NOT retry the same authorization code — it is single-use\n        log.warn(\"IdP rejected code exchange: {}\", msg);\n        redirectUserToLogin(); // start a fresh authorization-code flow\n    } else {\n        throw e;\n    }\n}","preventionTips":["Pin the exact redirect URI in a constant and reuse it for both buildAuthorizationUrl and exchangeCodeForUser.","Never retry an authorization-code exchange after a failure — request a fresh code.","Keep client-id/client-secret in sync with the IdP registration."],"tags":["oidc","authentication","authorization-code","network","idp-error"],"backgroundTag":null,"analyzedSha":"9b989acdf181d00898f2e8839257bb2b2a3cefe3","analyzedAt":"2026-08-14T07:17:31.569Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}