{"record":{"id":"3b5f5f6d9bf449a6","repo":"instructure/canvas-lms","slug":"the-developer-key-is-not-active-or-available-in-this","errorCode":null,"errorMessage":"the Developer Key is not active or available in this environment","messagePattern":"the Developer Key is not active or available in this environment","errorType":"validation","errorClass":"InvalidAuthJwt","httpStatus":401,"severity":"error","filePath":"lib/lti/oauth2/authorization_validator.rb","lineNumber":60,"sourceCode":"      def jwt\n        @_jwt ||= begin\n          validated_jwt = JSON::JWT.decode @raw_jwt, jwt_secret\n          validator = Canvas::Security::JwtValidator.new jwt: validated_jwt, expected_aud: @authorization_url, override_sub: sub\n          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","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/lib/lti/oauth2/authorization_validator.rb#L42-L78","documentation":"During LTI OAuth2 JWT authorization, Canvas looks up the active ToolProxy by the JWT 'sub' (guid) and then fetches its ProductFamily's developer key. If a developer key exists but is not currently usable (inactive, banned, or unavailable in the current environment/shard), the validator rejects the auth request with InvalidAuthJwt. This prevents disabled or off-environment tools from obtaining access tokens even when their tool proxy record is still active.","triggerScenarios":"POST to the LTI OAuth2 token endpoint with a JWT whose sub matches an active ToolProxy whose product_family.developer_key is present but DeveloperKey#usable? returns false (workflow_state not active, expired, deleted, or bound to a different account/environment).","commonSituations":"A developer key was deactivated or deleted in the account admin UI after the tool proxy was installed; the key exists only on another root account or shard so it is not usable in this environment; keys were rotated during an environment migration (beta/test vs production).","solutions":["Open Account > Developer Keys in the root account of the tool's product family and reactivate (or re-create) the key bound to the tool proxy.","Verify the key exists on the same root account/shard the tool proxy belongs to; move or recreate it there if not.","Check the developer key's workflow_state and expiry; restore it to 'active' via the UI or console if it was disabled.","If the key is intentionally retired, reinstall the tool proxy with a new active key so a new tool proxy/key pair is created."],"exampleFix":"// before (console check)\ntp = Lti::ToolProxy.where(guid: jwt_sub).first\n\ntp.product_family.developer_key.usable? # => false\n// after\nk = tp.product_family.developer_key\nk.update!(workflow_state: 'active') # or create a new usable key and reinstall the tool\nk.usable? # => true","handlingStrategy":"try-catch","validationCode":"tp = Lti::ToolProxy.where(guid: sub, workflow_state: 'active').first\nkey = tp&.product_family&.developer_key\nraise 'developer key unusable' if key.present? && !key.usable?","typeGuard":"def usable_key?(tool_proxy)\n  key = tool_proxy&.product_family&.developer_key\n  key.nil? || key.usable?\nend","tryCatchPattern":"begin\n  token = oauth2.request_access_token\nrescue Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt => e\n  Rails.logger.warn(\"LTI auth rejected: #{e.message}\")\n  # surface 401 to the tool\nend","preventionTips":["Keep the developer key active on the same root account/shard as the tool proxy.","Monitor developer key workflow_state changes that affect installed tools.","Test the token endpoint after any key rotation or environment migration."],"tags":["lti","oauth2","developer-key","authentication"],"backgroundTag":"authentication-required","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"}