{"record":{"id":"303979816f78d821","repo":"instructure/canvas-lms","slug":"incorrect-client-303979","errorCode":"incorrect_client","errorMessage":"incorrect_client","messagePattern":"incorrect_client","errorType":"error_code","errorClass":"Canvas::OAuth::RequestError","httpStatus":null,"severity":"error","filePath":"lib/canvas/oauth/grant_types/refresh_token.rb","lineNumber":23,"sourceCode":"    class RefreshToken < BaseType\n      def supported_type?\n        true\n      end\n\n      # Access tokens obtained by public clients through PKCE should\n      # be refreshed using this grant type\n      def allow_public_client?\n        true\n      end\n\n      private\n\n      def validate_type\n        raise Canvas::OAuth::RequestError, :refresh_token_not_supplied unless @opts[:refresh_token]\n\n        @_token = @provider.token_for_refresh_token(@opts[:refresh_token])\n        raise Canvas::OAuth::RequestError, :invalid_refresh_token unless @_token\n        raise Canvas::OAuth::RequestError, :incorrect_client unless @_token.access_token.developer_key_id == @_token.key.id\n      end\n\n      def generate_token\n        @_token.access_token.regenerate_access_token\n\n        if provider.key.public_client?\n          # Access tokens for public clients have a (default) two-hour rolling window\n          # in which tokens are eligible for refresh. When a refresh action is take for\n          # a public client, extend that window by another two hours.\n          @_token.access_token.set_permanent_expiration\n\n          # For better token security, force public clients to rotate refresh tokens\n          # after each use. This helps mitigate the risk of a leaked refresh token.\n          @_token.access_token.generate_refresh_token(overwrite: true)\n          @_token.access_token.save\n        end\n\n        @_token","sourceCodeStart":5,"sourceCodeEnd":41,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/lib/canvas/oauth/grant_types/refresh_token.rb#L5-L41","documentation":"Canvas::OAuth::RequestError :incorrect_client is raised during the refresh_token grant type validation. After the refresh token is resolved, the library verifies that the stored refresh token's access token belongs to the same developer key (client) that is presenting it. It exists to prevent tokens issued to one OAuth client from being refreshed by a different client.","triggerScenarios":"Calling the OAuth token endpoint with grant_type=refresh_token while supplying client_id/client_secret of a developer key different from the one that originally issued the refresh token; e.g. @_token.access_token.developer_key_id != @_token.key.id.","commonSituations":"Rotating or replacing a Canvas developer key and reusing old refresh tokens; pointing a staging environment at production-issued refresh tokens; configuring the wrong client_id in an LTI/integration's OAuth settings.","solutions":["Use the client_id/client_secret of the same developer key that originally issued the refresh token","Re-run the full OAuth authorization flow to obtain a fresh refresh token bound to the current developer key","If the developer key was replaced, update the integration's stored credentials to the new key and re-authorize"],"exampleFix":"// before\nPOST /login/oauth2/token?grant_type=refresh_token&client_id=NEW_KEY&client_secret=...&refresh_token=OLD_TOKEN\n// after\nPOST /login/oauth2/token?grant_type=refresh_token&client_id=ORIGINAL_KEY&client_secret=...&refresh_token=OLD_TOKEN","handlingStrategy":"validation","validationCode":"raise 'client mismatch' unless refresh_token.developer_key_id == developer_key.id","typeGuard":null,"tryCatchPattern":"begin\n  token = provider.token_for_refresh_token(params[:refresh_token])\nrescue Canvas::OAuth::RequestError => e\n  return render json: { error: e.error }, status: :unauthorized if e.error == :incorrect_client\nend","preventionTips":["Store client_id together with the refresh token and assert equality before refreshing","Re-authorize after rotating developer keys","Keep one credentials set per environment; never share tokens across keys"],"tags":["oauth","refresh-token","client-mismatch","authentication"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}