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

@provider.error_message

Error message

@provider.error_message

What it means

Raised as Canvas::OAuth::InvalidRequestError with the provider's error_message after assertion_method_permitted? passes but @provider.valid? fails. For the symmetric provider, valid? is simply key.present? and the message is 'Unknown client_id'; other providers similarly surface a validation failure (bad JWT, bad secret, unknown key). This guards the client_credentials grant against unauthenticated or unrecognized clients.

Solutions

  1. Check the error_message returned with the response (e.g. 'Unknown client_id') and correct the client_id/client_secret in your client config.
  2. Confirm the DeveloperKey exists, is active, and lives on the account/shard you are authenticating against.
  3. For JWT assertion, re-sign the JWT with the public key registered on the developer key and check exp/iat/aud claims.
  4. Clear/wait out the developer-key cache (find_cached) after creating or updating the key, then retry.

Example fix

// before
{ client_id: '99000000000000', client_secret: 'old-secret' }
// after
{ client_id: '99000000000001', client_secret: '<rotated-secret>' }
Defensive patterns

Strategy: try-catch

Validate before calling

key = DeveloperKey.find_cached(client_id) rescue nil
abort 'Unknown client_id' unless key&.present?

Type guard

def known_client?(client_id)
  DeveloperKey.find_cached(client_id)
rescue ActiveRecord::RecordNotFound
  nil
end

Try / catch

begin
  token = exchange_client_credentials(opts)
rescue Canvas::OAuth::InvalidRequestError => e
  handle_invalid_client(e.message) # e.g. 'Unknown client_id'
end

Prevention

When it happens

Trigger: POST to /login/oauth2/token with grant_type=client_credentials where the client_id does not match an existing/cached DeveloperKey, the client_secret is wrong, or the JWT client_assertion fails signature/claims validation in the asymmetric provider.

Common situations: Typos or stale client_id/secret after rotating credentials; key deleted or moved to another account; key not cached yet (find_cached miss); JWT signed with the wrong private key or expired; environment pointing at a Canvas instance that lacks the key.

Related errors


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

Appendix: source

Thrown at lib/canvas/oauth/grant_types/client_credentials.rb:77

        Canvas::OAuth::SymmetricClientCredentialsProvider.new(client_id, host, scopes: scopes_from_opts(opts), protocol:)
      end

      def secret_for(provider, opts)
        provider.try(:secret) || opts[:client_secret]
      end

      def key_for(client_id)
        DeveloperKey.find_cached(client_id)
      rescue ::ActiveRecord::RecordNotFound
        nil
      end

      def validate_type
        unless @provider.assertion_method_permitted?
          raise Canvas::OAuth::InvalidRequestError, "assertion method not supported for this grant_type"
        end

        raise Canvas::OAuth::InvalidRequestError, @provider.error_message unless @provider.valid?
        raise Canvas::OAuth::InvalidScopeError, @provider.missing_scopes unless @provider.valid_scopes?
      end

      def generate_token
        @provider.generate_token
      end

      def basic_auth?(opts)
        opts[:client_assertion_type] != "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
      end

      def scopes_from_opts(opts)
        (opts[:scope] || opts[:scopes] || "").split
      end
    end
  end
end

View on GitHub (pinned to 1c9f0bb801)