{"record":{"id":"061c34254d377fcf","repo":"instructure/canvas-lms","slug":"missing-claims-claims","errorCode":null,"errorMessage":"Missing claims %{claims}","messagePattern":"Missing claims %(.+?)","errorType":"validation","errorClass":"OAuthValidationError","httpStatus":null,"severity":"error","filePath":"app/models/authentication_provider/open_id_connect.rb","lineNumber":277,"sourceCode":"\n      nil\n    rescue JSON::JWK::Set::KidNotFound => e\n      tries += 1\n      if tries == 2\n        download_jwks(force: true)\n        retry\n      end\n      e.message\n    rescue JSON::JWT::VerificationFailed => e\n      e.message\n    end\n\n    def claims(token)\n      token.options[:claims] ||= begin\n        id_token = unverified_id_token(token)\n\n        unless (missing_claims = %w[aud iss iat exp nonce] - id_token.keys).empty?\n          raise OAuthValidationError, t({ one: \"Missing claim %{claims}\", other: \"Missing claims %{claims}\" },\n                                        count: missing_claims.length,\n                                        claims: missing_claims.join(\", \"))\n        end\n\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\")","sourceCodeStart":259,"sourceCodeEnd":295,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/models/authentication_provider/open_id_connect.rb#L259-L295","documentation":"Raised in open_id_connect.rb claims() when the provider's ID token lacks one or more of the mandatory OIDC claims: aud, iss, iat, exp, nonce. Canvas validates these before using the token, because a spec-compliant ID token must contain them for audience/issuer/time/nonce verification.","triggerScenarios":"unique_id, persist_to_session, or provider_attributes call claims(token); unverified_id_token decodes the JWT payload and the %w[aud iss iat exp nonce] - keys diff is non-empty (e.g. a nonstandard IdP omits nonce or iat), raising with the missing claim names joined by comma.","commonSituations":"Identity provider that is not fully OIDC compliant omits nonce (common with hand-rolled or older IdPs); token endpoint returns an access token where an ID token is expected; misconfigured response_type causing the callback to inspect the wrong token; IdP strips claims via token transformation policies.","solutions":["Fix the IdP configuration so the ID token includes aud, iss, iat, exp, and nonce (enable nonce issuance on the IdP).","Verify the callback is decoding the id_token, not the access token (check response_type/protocol mapping in provider settings).","Check for IdP-side claims-mapping policies that drop standard claims and re-add them.","Test the raw ID token (e.g. jwt.io) to confirm which claims are actually present."],"exampleFix":"// before: IdP emits ID token without nonce\n{ \"aud\": \"client-id\", \"iss\": \"https://idp\", \"iat\": 1700000000, \"exp\": 1700003600 }\n// after: enable nonce in IdP OIDC settings\n{ \"aud\": \"client-id\", \"iss\": \"https://idp\", \"iat\": 1700000000, \"exp\": 1700003600, \"nonce\": \"abc123\" }","handlingStrategy":"validation","validationCode":"claims = JSON.parse(Base64.urlsafe_decode64(id_token.split(\".\")[1]))\nmissing = %w[aud iss iat exp nonce] - claims.keys\nraise \"ID token missing claims: #{missing.join(', ')}\" unless missing.empty?","typeGuard":null,"tryCatchPattern":"begin\n  provider.claims(token)\nrescue OAuthValidationError => e\n  Rails.logger.error(\"OIDC claims validation failed: #{e.message}\")\n  redirect_to login_path, alert: \"Login failed: identity provider token invalid.\"\nend","preventionTips":["Verify IdP OIDC compliance (nonce, iat, exp issuance) before integration","Decode a sample ID token at jwt.io during setup","Ensure the callback handles id_token, not the access token","Check IdP claims-mapping policies do not drop standard claims"],"tags":["oidc","jwt","sso","claims-validation"],"backgroundTag":"unexpected-response-shape","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"}