instructure/canvas-lms · error · InvalidAuthJwt

the Tool Proxy must be using a split secret

Error message

the Tool Proxy must be using a split secret

What it means

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.

Solutions

  1. Re-register the tool proxy including 'Security.splitSecret' (or 'OAuth.splitSecret') in enabled_capabilities and store the secret split in half.
  2. Update the tool proxy's raw_data to add the splitSecret capability, then re-split the shared secret (each half stored separately) via console.
  3. If the tool cannot use split secrets, fall back to the legacy OAuth 1.0 signature flow instead of the JWT OAuth2 endpoint.

Example fix

// before
proxy['enabled_capability'] # => ['Security.halfSharedSecret']
// after (re-register / patch raw_data)
proxy['enabled_capability'] = ['Security.halfSharedSecret', 'Security.splitSecret']
tp.update!(raw_data: proxy.to_json)
Defensive patterns

Strategy: validation

Validate before calling

caps = JSON.parse(tp.raw_data)['enabled_capability'] || []
unless caps.intersect?(['Security.splitSecret', 'OAuth.splitSecret'])
  raise 'tool proxy must declare splitSecret capability'
end

Type guard

def split_secret?(tool_proxy)
  caps = JSON.parse(tool_proxy.raw_data)['enabled_capability'] || []
  caps.any? { |c| %w[Security.splitSecret OAuth.splitSecret].include?(c) }
end

Try / catch

begin
  token = oauth2.request_access_token
rescue Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt => e
  Rails.logger.warn("split secret required: #{e.message}")
end

Prevention

When it happens

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

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

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at lib/lti/oauth2/authorization_validator.rb:64

          unless validator.valid?
            raise InvalidAuthJwt, validator.error_message
          end

          validated_jwt
        end
      end

      alias_method :validate!, :jwt

      def tool_proxy
        @tool_proxy ||=
          if (tp = ToolProxy.where(guid: unverified_jwt[:sub], workflow_state: "active").first)
            developer_key = tp.product_family.developer_key
            raise InvalidAuthJwt, "the Developer Key is not active or available in this environment" if developer_key.present? && !developer_key.usable?

            ims_tool_proxy = ::IMS::LTI::Models::ToolProxy.from_json(tp.raw_data)
            unless ims_tool_proxy.enabled_capabilities.intersect?(["Security.splitSecret", "OAuth.splitSecret"])
              raise InvalidAuthJwt, "the Tool Proxy must be using a split secret"
            end

            tp
          end
      end

      def developer_key
        @_developer_key ||= begin
          dev_key = DeveloperKey.find_cached(unverified_jwt[:sub])
          raise MissingAuthorizationCode if dev_key && @code.blank?

          dev_key
        rescue ActiveRecord::RecordNotFound
          nil
        end
      end

      def sub

View on GitHub (pinned to 1c9f0bb801)