instructure/canvas-lms · error · Canvas::OAuth::RequestError

incorrect_client

incorrect_client

Error message

incorrect_client

What it means

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.

Solutions

  1. Use the client_id/client_secret of the same developer key that originally issued the refresh token
  2. Re-run the full OAuth authorization flow to obtain a fresh refresh token bound to the current developer key
  3. If the developer key was replaced, update the integration's stored credentials to the new key and re-authorize

Example fix

// before
POST /login/oauth2/token?grant_type=refresh_token&client_id=NEW_KEY&client_secret=...&refresh_token=OLD_TOKEN
// after
POST /login/oauth2/token?grant_type=refresh_token&client_id=ORIGINAL_KEY&client_secret=...&refresh_token=OLD_TOKEN
Defensive patterns

Strategy: validation

Validate before calling

raise 'client mismatch' unless refresh_token.developer_key_id == developer_key.id

Try / catch

begin
  token = provider.token_for_refresh_token(params[:refresh_token])
rescue Canvas::OAuth::RequestError => e
  return render json: { error: e.error }, status: :unauthorized if e.error == :incorrect_client
end

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/303979816f78d821. Report an issue: GitHub.

Appendix: source

Thrown at lib/canvas/oauth/grant_types/refresh_token.rb:23

    class RefreshToken < BaseType
      def supported_type?
        true
      end

      # Access tokens obtained by public clients through PKCE should
      # be refreshed using this grant type
      def allow_public_client?
        true
      end

      private

      def validate_type
        raise Canvas::OAuth::RequestError, :refresh_token_not_supplied unless @opts[:refresh_token]

        @_token = @provider.token_for_refresh_token(@opts[:refresh_token])
        raise Canvas::OAuth::RequestError, :invalid_refresh_token unless @_token
        raise Canvas::OAuth::RequestError, :incorrect_client unless @_token.access_token.developer_key_id == @_token.key.id
      end

      def generate_token
        @_token.access_token.regenerate_access_token

        if provider.key.public_client?
          # Access tokens for public clients have a (default) two-hour rolling window
          # in which tokens are eligible for refresh. When a refresh action is take for
          # a public client, extend that window by another two hours.
          @_token.access_token.set_permanent_expiration

          # For better token security, force public clients to rotate refresh tokens
          # after each use. This helps mitigate the risk of a leaked refresh token.
          @_token.access_token.generate_refresh_token(overwrite: true)
          @_token.access_token.save
        end

        @_token

View on GitHub (pinned to 1c9f0bb801)