instructure/canvas-lms · error · Lti::OAuth2::InvalidTokenError

The ToolProxy security contract doesn't include #

Error message

The ToolProxy security contract doesn't include #{service_names.join(", or ")}

What it means

Guard in Lti::IMS::AccessTokenHelper#validate_services!: the LTI 2 ToolProxy's security contract lists no service matching the service names required by the endpoint being called (lti2_service_name), so Lti::OAuth2::InvalidTokenError is raised — the tool was never registered for this REST service.

Solutions

  1. Re-register/re-deploy the ToolProxy with the required service (e.g. ToolProxy.custom or Result service) in its security_contract.tool_services
  2. Call an endpoint matching a service the tool is actually authorized for
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at app/controllers/lti/ims/access_token_helper.rb:62 when the library encounters an invalid state.

Common situations: See trigger scenarios.


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

Appendix: source

Thrown at app/controllers/lti/ims/access_token_helper.rb:62

    end
  end

  def oauth2_request?
    pattern = /^Bearer /
    header = request.headers["Authorization"]
    header&.match?(pattern)
  end

  def tool_proxy
    @_tool_proxy ||= Lti::ToolProxy.find_by(guid: access_token.sub)
  end

  def validate_services!(tool_proxy)
    ims_tp = ::IMS::LTI::Models::ToolProxy.from_json(tool_proxy.raw_data)
    service_names = [*lti2_service_name]
    service = ims_tp.security_contract.tool_services.find(
      lambda do
        raise Lti::OAuth2::InvalidTokenError,
              "The ToolProxy security contract doesn't include #{service_names.join(", or ")}"
      end
    ) do |s|
      service_names.include? s.service.split(":").last.split("#").last
    end
    unless service.actions.map(&:downcase).include? request.method.downcase
      msg = "#{s.service.split(":").last.split("#").last}.#{request.method} not included in ToolProxy security Contract"
      raise Lti::OAuth2::InvalidTokenError, msg
    end
  end

  def developer_key
    @_developer_key ||= access_token && begin
      tp = Lti::ToolProxy.find_by(guid: access_token.sub)
      if tp.present?
        raise Lti::OAuth2::InvalidTokenError, "Tool Proxy is not active" if tp.workflow_state != "active"

        validate_services!(tp)

View on GitHub (pinned to 1c9f0bb801)