{"record":{"id":"750e34f2a1637489","repo":"instructure/canvas-lms","slug":"either-the-tool-proxy-or-developer-key-were-not-found","errorCode":null,"errorMessage":"either the tool proxy or developer key were not found","messagePattern":"either the tool proxy or developer key were not found","errorType":"exception","errorClass":"SecretNotFound","httpStatus":401,"severity":"error","filePath":"lib/lti/oauth2/authorization_validator.rb","lineNumber":94,"sourceCode":"          dev_key\n        rescue ActiveRecord::RecordNotFound\n          nil\n        end\n      end\n\n      def sub\n        tool_proxy&.guid || developer_key&.global_id || unverified_jwt[:sub]\n      end\n\n      private\n\n      def jwt_secret\n        secret = tool_proxy&.shared_secret\n        secret ||= developer_key&.api_key\n        secret ||= (RegistrationRequestService.retrieve_registration_password(@context, unverified_jwt[:sub]) || {})[:reg_password]\n        return secret if secret.present?\n\n        raise SecretNotFound, \"either the tool proxy or developer key were not found\"\n      end\n\n      def unverified_jwt\n        @_unverified_jwt ||= begin\n          decoded_jwt = JSON::JWT.decode(@raw_jwt, :skip_verification)\n          decoded_jwt\n        end\n      end\n    end\n  end\nend\n","sourceCodeStart":76,"sourceCodeEnd":106,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/lib/lti/oauth2/authorization_validator.rb#L76-L106","documentation":"The LTI OAuth2 validator derives the JWT signing secret by trying, in order: the active tool proxy's shared secret, the developer key's api_key, and a registration password from RegistrationRequestService. If none resolves to a present value it raises SecretNotFound, meaning the JWT cannot be verified because Canvas has no shared secret on record for the token's subject.","triggerScenarios":"Decoding a signed JWT whose 'sub' matches no active ToolProxy, whose product family has no developer key with an api_key, and which does not correspond to a pending registration request with a stored reg_password.","commonSituations":"Tool proxy not yet activated (workflow_state != 'active') so the lookup misses; wrong guid/iss placed in the JWT sub claim; developer key deleted so its api_key is gone; client sending a JWT for a registration flow before the registration password exists.","solutions":["Confirm the JWT 'sub' exactly matches an active ToolProxy guid (check workflow_state = 'active' in the lti_tool_proxies table).","Ensure the tool proxy's product_family has a developer key with a valid api_key.","If this is an OIDC/registration request, verify RegistrationRequestService has a stored registration password for the context and sub.","Reinstall/re-register the tool so a valid secret is provisioned, then retry the token request."],"exampleFix":"// before (check in console)\nLti::ToolProxy.where(guid: sub, workflow_state: 'active').first # => nil\n// after\nLti::ToolProxy.where(guid: sub).first.update!(workflow_state: 'active') # or fix the sub claim","handlingStrategy":"try-catch","validationCode":"tp = Lti::ToolProxy.where(guid: jwt['sub'], workflow_state: 'active').first\ndk = tp&.product_family&.developer_key\nabort 'no secret on record' if tp.nil? && dk.nil? && reg_password.nil?","typeGuard":"def secret_available?(sub, context)\n  Lti::ToolProxy.where(guid: sub, workflow_state: 'active').exists? ||\n    Lti::DeveloperKey.where(id: nil).exists? # replace with actual key lookup\nend","tryCatchPattern":"begin\n  secret = validator.jwt_secret\nrescue Lti::Oauth2::AuthorizationValidator::SecretNotFound => e\n  render json: { error: 'unknown JWT subject' }, status: :unauthorized\nend","preventionTips":["Verify the JWT sub claim matches the tool proxy guid exactly.","Ensure tool proxies are activated after registration.","Do not delete developer keys still referenced by active tool proxies."],"tags":["lti","oauth2","jwt","secret-not-found"],"backgroundTag":"missing-credentials","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"}