{"record":{"id":"934ef515341e78cb","repo":"instructure/canvas-lms","slug":"invalid-signature-signature-error","errorCode":null,"errorMessage":"Invalid signature: %{signature_error}","messagePattern":"Invalid signature: %(.+?)","errorType":"validation","errorClass":"OAuthValidationError","httpStatus":null,"severity":"critical","filePath":"app/models/authentication_provider/open_id_connect.rb","lineNumber":299,"sourceCode":"\n        unless Array(id_token[\"aud\"]).include?(client_id)\n          raise OAuthValidationError, t(\"Invalid JWT audience: %{audience}\", audience: id_token[\"aud\"].inspect)\n        end\n\n        if self.class.validate_issuer?\n          if issuer.blank?\n            raise OAuthValidationError, t(\"No issuer configured for OpenID Connect provider\")\n          end\n          unless issuer === id_token[\"iss\"] # rubocop:disable Style/CaseEquality -- may be a string or a RegEx\n            raise OAuthValidationError, t(\"Invalid JWT issuer: %{issuer}\", issuer: id_token[\"iss\"])\n          end\n        end\n        unless id_token[\"nonce\"] == token.options[:nonce]\n          raise OAuthValidationError, t(\"Invalid nonce claim in ID Token\")\n        end\n\n        if (signature_error = validate_signature(id_token))\n          raise OAuthValidationError, t(\"Invalid signature: %{signature_error}\", signature_error:)\n        end\n\n        # we have a userinfo endpoint, and we don't have everything we want,\n        # then request more\n        if userinfo_endpoint.present? && !(requested_claims - id_token.keys).empty?\n          userinfo = token.get(userinfo_endpoint).parsed\n          debug_set(:userinfo, userinfo.to_json) if instance_debugging\n          # but only use it if it's for the user we logged in as\n          # see http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse\n          if userinfo[\"sub\"] == id_token[\"sub\"]\n            id_token.merge!(userinfo)\n          end\n        end\n        id_token\n      end\n    end\n\n    protected","sourceCodeStart":281,"sourceCodeEnd":317,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/models/authentication_provider/open_id_connect.rb#L281-L317","documentation":"Raised in claims() when validate_signature(id_token) returns an error, meaning the ID token's JWS signature could not be verified against the IdP's published keys (JWKS). Canvas rejects tokens it cannot cryptographically attribute to the configured issuer's keys.","triggerScenarios":"claims() calls validate_signature during login; common causes inside it: the kid in the token is not in the fetched JWKS, the IdP rotated keys and Canvas cached old keys, the token is signed with an unsupported algorithm (e.g. HS256 vs RS256), or the JWKS endpoint URL is wrong/unreachable, and the returned error string is interpolated into the message.","commonSituations":"IdP key rotation while Canvas caches JWKS keys; admin configured the provider with the wrong jwks/discovery URL; IdP switched signing algorithm after an upgrade; self-signed/intermediate CA issues fetching the JWKS over HTTPS; token manually forged or from a test IdP.","solutions":["Confirm the provider's discovery/JWKS URL is correct and reachable and that its keys include the kid in the failing token.","Clear/refresh the cached JWKS keys (restart or expire the provider's cached keys) after an IdP key rotation.","Ensure the token's signing algorithm (alg header) is one Canvas supports (typically RS256); configure the IdP to use RS256.","Check TLS trust on the server fetching the JWKS (update CA bundle if self-signed/enterprise CA).","Decode the token at jwt.io using the IdP's public key to confirm the signature manually before changing config."],"exampleFix":"# before\nsettings = { \"client_id\" => \"abc\", \"issuer\" => \"https://idp.example.com\" } # no/discovery wrong\n# after\nsettings = { \"client_id\" => \"abc\", \"issuer\" => \"https://idp.example.com/realms/main\",\n             \"discovery_url\" => \"https://idp.example.com/realms/main/.well-known/openid-configuration\" }","handlingStrategy":"try-catch","validationCode":"jwks = JSON.parse(Net::HTTP.get(URI(jwks_url)))\nkid = JSON.parse(Base64.urlsafe_decode64(id_token.split(\".\")[0]))[\"kid\"]\nraise \"kid #{kid} not in JWKS\" unless jwks[\"keys\"].any? { |k| k[\"kid\"] == kid }","typeGuard":null,"tryCatchPattern":"begin\n  provider.claims(token)\nrescue OAuthValidationError => e\n  Rails.logger.error(\"OIDC signature validation failed: #{e.message}\")\n  head :unauthorized\nend","preventionTips":["Point the provider at the correct discovery/JWKS URL","Expire/refresh cached JWKS keys to survive IdP key rotation","Require RS256 (or a supported alg) on the IdP","Keep server CA bundle updated for JWKS fetches"],"tags":["oidc","jwt","signature","jwks","sso"],"backgroundTag":"checksum-mismatch","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"}