{"record":{"id":"e5291fb78c11834f","repo":"instructure/canvas-lms","slug":"the-tool-proxy-must-be-using-a-split-secret","errorCode":null,"errorMessage":"the Tool Proxy must be using a split secret","messagePattern":"the Tool Proxy must be using a split secret","errorType":"validation","errorClass":"InvalidAuthJwt","httpStatus":401,"severity":"error","filePath":"lib/lti/oauth2/authorization_validator.rb","lineNumber":64,"sourceCode":"          unless validator.valid?\n            raise InvalidAuthJwt, validator.error_message\n          end\n\n          validated_jwt\n        end\n      end\n\n      alias_method :validate!, :jwt\n\n      def tool_proxy\n        @tool_proxy ||=\n          if (tp = ToolProxy.where(guid: unverified_jwt[:sub], workflow_state: \"active\").first)\n            developer_key = tp.product_family.developer_key\n            raise InvalidAuthJwt, \"the Developer Key is not active or available in this environment\" if developer_key.present? && !developer_key.usable?\n\n            ims_tool_proxy = ::IMS::LTI::Models::ToolProxy.from_json(tp.raw_data)\n            unless ims_tool_proxy.enabled_capabilities.intersect?([\"Security.splitSecret\", \"OAuth.splitSecret\"])\n              raise InvalidAuthJwt, \"the Tool Proxy must be using a split secret\"\n            end\n\n            tp\n          end\n      end\n\n      def developer_key\n        @_developer_key ||= begin\n          dev_key = DeveloperKey.find_cached(unverified_jwt[:sub])\n          raise MissingAuthorizationCode if dev_key && @code.blank?\n\n          dev_key\n        rescue ActiveRecord::RecordNotFound\n          nil\n        end\n      end\n\n      def sub","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/lib/lti/oauth2/authorization_validator.rb#L46-L82","documentation":"After validating the tool proxy's developer key, the LTI OAuth2 authorization validator parses the stored tool proxy JSON and requires that its enabled_capabilities include 'Security.splitSecret' or 'OAuth.splitSecret'. JWT-based token exchange for tool proxies is only supported when the tool stores its secret in split (half/half) form; proxies registered with a plain shared secret are rejected with InvalidAuthJwt.","triggerScenarios":"Requesting an OAuth2 access token via LTI JWT authorization using a ToolProxy whose raw_data 'enabled_capability' list lacks both 'Security.splitSecret' and 'OAuth.splitSecret'.","commonSituations":"Legacy tool proxies registered before split-secret became standard; tools manually registered with a full shared secret; tool proxy JSON regenerated/edited losing the capability entry; migrating an old TC-registered proxy to JWT auth.","solutions":["Re-register the tool proxy including 'Security.splitSecret' (or 'OAuth.splitSecret') in enabled_capabilities and store the secret split in half.","Update the tool proxy's raw_data to add the splitSecret capability, then re-split the shared secret (each half stored separately) via console.","If the tool cannot use split secrets, fall back to the legacy OAuth 1.0 signature flow instead of the JWT OAuth2 endpoint."],"exampleFix":"// before\nproxy['enabled_capability'] # => ['Security.halfSharedSecret']\n// after (re-register / patch raw_data)\nproxy['enabled_capability'] = ['Security.halfSharedSecret', 'Security.splitSecret']\ntp.update!(raw_data: proxy.to_json)","handlingStrategy":"validation","validationCode":"caps = JSON.parse(tp.raw_data)['enabled_capability'] || []\nunless caps.intersect?(['Security.splitSecret', 'OAuth.splitSecret'])\n  raise 'tool proxy must declare splitSecret capability'\nend","typeGuard":"def split_secret?(tool_proxy)\n  caps = JSON.parse(tool_proxy.raw_data)['enabled_capability'] || []\n  caps.any? { |c| %w[Security.splitSecret OAuth.splitSecret].include?(c) }\nend","tryCatchPattern":"begin\n  token = oauth2.request_access_token\nrescue Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt => e\n  Rails.logger.warn(\"split secret required: #{e.message}\")\nend","preventionTips":["Always include Security.splitSecret in enabled_capabilities when registering tool proxies.","Never hand-edit raw_data without re-verifying capabilities.","Use current Canvas tool registration flows that default to split secrets."],"tags":["lti","oauth2","tool-proxy","split-secret"],"backgroundTag":"invalid-config-value","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"}