{"record":{"id":"0ef1ff0fe0d37edf","repo":"instructure/canvas-lms","slug":"provider-error-message","errorCode":null,"errorMessage":"@provider.error_message","messagePattern":"@provider\\.error_message","errorType":"exception","errorClass":"Canvas::OAuth::InvalidRequestError","httpStatus":null,"severity":"error","filePath":"lib/canvas/oauth/grant_types/client_credentials.rb","lineNumber":77,"sourceCode":"        Canvas::OAuth::SymmetricClientCredentialsProvider.new(client_id, host, scopes: scopes_from_opts(opts), protocol:)\n      end\n\n      def secret_for(provider, opts)\n        provider.try(:secret) || opts[:client_secret]\n      end\n\n      def key_for(client_id)\n        DeveloperKey.find_cached(client_id)\n      rescue ::ActiveRecord::RecordNotFound\n        nil\n      end\n\n      def validate_type\n        unless @provider.assertion_method_permitted?\n          raise Canvas::OAuth::InvalidRequestError, \"assertion method not supported for this grant_type\"\n        end\n\n        raise Canvas::OAuth::InvalidRequestError, @provider.error_message unless @provider.valid?\n        raise Canvas::OAuth::InvalidScopeError, @provider.missing_scopes unless @provider.valid_scopes?\n      end\n\n      def generate_token\n        @provider.generate_token\n      end\n\n      def basic_auth?(opts)\n        opts[:client_assertion_type] != \"urn:ietf:params:oauth:client-assertion-type:jwt-bearer\"\n      end\n\n      def scopes_from_opts(opts)\n        (opts[:scope] || opts[:scopes] || \"\").split\n      end\n    end\n  end\nend\n","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/lib/canvas/oauth/grant_types/client_credentials.rb#L59-L95","documentation":"Raised as Canvas::OAuth::InvalidRequestError with the provider's error_message after assertion_method_permitted? passes but @provider.valid? fails. For the symmetric provider, valid? is simply key.present? and the message is 'Unknown client_id'; other providers similarly surface a validation failure (bad JWT, bad secret, unknown key). This guards the client_credentials grant against unauthenticated or unrecognized clients.","triggerScenarios":"POST to /login/oauth2/token with grant_type=client_credentials where the client_id does not match an existing/cached DeveloperKey, the client_secret is wrong, or the JWT client_assertion fails signature/claims validation in the asymmetric provider.","commonSituations":"Typos or stale client_id/secret after rotating credentials; key deleted or moved to another account; key not cached yet (find_cached miss); JWT signed with the wrong private key or expired; environment pointing at a Canvas instance that lacks the key.","solutions":["Check the error_message returned with the response (e.g. 'Unknown client_id') and correct the client_id/client_secret in your client config.","Confirm the DeveloperKey exists, is active, and lives on the account/shard you are authenticating against.","For JWT assertion, re-sign the JWT with the public key registered on the developer key and check exp/iat/aud claims.","Clear/wait out the developer-key cache (find_cached) after creating or updating the key, then retry."],"exampleFix":"// before\n{ client_id: '99000000000000', client_secret: 'old-secret' }\n// after\n{ client_id: '99000000000001', client_secret: '<rotated-secret>' }","handlingStrategy":"try-catch","validationCode":"key = DeveloperKey.find_cached(client_id) rescue nil\nabort 'Unknown client_id' unless key&.present?","typeGuard":"def known_client?(client_id)\n  DeveloperKey.find_cached(client_id)\nrescue ActiveRecord::RecordNotFound\n  nil\nend","tryCatchPattern":"begin\n  token = exchange_client_credentials(opts)\nrescue Canvas::OAuth::InvalidRequestError => e\n  handle_invalid_client(e.message) # e.g. 'Unknown client_id'\nend","preventionTips":["Keep client_id/client_secret in config/env, never hardcode","After rotating a secret, update all clients before deleting the old one","Confirm the key exists on the account/shard you authenticate against","For JWT, sign with the private key matching the key's registered public key and keep exp short"],"tags":["oauth2","canvas-lms","client-credentials","invalid-client","authentication"],"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"}