instructure/canvas-lms · error · Lti::OAuth2::InvalidTokenError
# .# not included in ToolProxy security Contract
Error message
#{s.service.split(":").last.split("#").last}.#{request.method} not included in ToolProxy security Contract What it means
Guard in Lti::IMS::AccessTokenHelper#validate_services!: after finding a matching tool service in the ToolProxy's security contract, the service's allowed actions don't include the current HTTP method, so the request (e.g. a GET to a POST-only service) is rejected with Lti::OAuth2::InvalidTokenError.
Solutions
- Use the HTTP method the ToolProxy declares for that service action
- Update the ToolProxy's security contract to include the needed action
Defensive patterns
Strategy: validation
When it happens
Trigger: Thrown at app/controllers/lti/ims/access_token_helper.rb:70 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/e415d77dac7ac1b9.
Report an issue: GitHub.
Appendix: source
Thrown at app/controllers/lti/ims/access_token_helper.rb:70
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)
tp.product_family.developer_key
else
DeveloperKey.find_cached(access_token.sub)
end
rescue ActiveRecord::RecordNotFound
nil
end
endView on GitHub (pinned to 1c9f0bb801)