{"record":{"id":"a767e7209a001cad","repo":"instructure/canvas-lms","slug":"the-following-assertions-are-missing-missing-assertions-join","errorCode":null,"errorMessage":"the following assertions are missing: #{missing_assertions.join(\",\")}","messagePattern":"the following assertions are missing: #(.+?)","errorType":"validation","errorClass":"InvalidTokenError","httpStatus":401,"severity":"error","filePath":"lib/lti/oauth2/access_token.rb","lineNumber":96,"sourceCode":"          body = {\n            iss: ISS,\n            sub:,\n            exp: 1.hour.from_now,\n            aud:,\n            iat: Time.zone.now.to_i,\n            nbf: 30.seconds.ago,\n            jti: SecureRandom.uuid,\n            shard_id:\n          }\n          body[:reg_key] = @reg_key if @reg_key\n          Canvas::Security.create_jwt(body)\n        end\n      end\n\n      def check_required_assertions(assertion_keys)\n        missing_assertions = (%w[iss sub exp aud iat nbf jti] - assertion_keys)\n        if missing_assertions.present?\n          raise InvalidTokenError, \"the following assertions are missing: #{missing_assertions.join(\",\")}\"\n        end\n      end\n    end\n  end\nend\n","sourceCodeStart":78,"sourceCodeEnd":102,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/lib/lti/oauth2/access_token.rb#L78-L102","documentation":"Lti::OAuth2::AccessToken#check_required_assertions (invoked from validate!) compares the JWT's claim keys against the required set [iss, sub, exp, aud, iat, nbf, jti] and raises InvalidTokenError listing whichever claims are missing. Canvas-minted tokens always include all of these, so absence signals a token not built by this class or a hand-rolled/incomplete JWT.","triggerScenarios":"Calling validate! on a JWT lacking one or more of iss/sub/exp/aud/iat/nbf/jti - the error message enumerates the missing keys.","commonSituations":"Hand-built or third-party JWTs fed to the Canvas-internal validator, SDKs emitting minimal claim sets, tests with truncated payloads, or tokens from a different Canvas version with a different claim set.","solutions":["Include all required claims (iss, sub, exp, aud, iat, nbf, jti) when minting the token","Prefer Lti::OAuth2::AccessToken.create_jwt / its jwt builder over hand-rolled JWT construction","Decode the offending token and diff its claim keys against the required list to see exactly what to add"],"exampleFix":"// before\nconst payload = {iss: 'Canvas', sub, exp, aud};\n// after\nconst payload = {iss: 'Canvas', sub, exp, aud, iat: Math.floor(Date.now()/1000), nbf: Math.floor(Date.now()/1000)-30, jti: crypto.randomUUID()};","handlingStrategy":"validation","validationCode":"required = %w[iss sub exp aud iat nbf jti]\npayload = JSON.parse(Base64.urlsafe_decode64(jwt.split('.')[1]))\nmissing = required - payload.keys\nraise \"missing claims: #{missing.join(',')}\" if missing.any?","typeGuard":"def complete_lti_jwt?(jwt)\n  payload = JSON.parse(Base64.urlsafe_decode64(jwt.split('.')[1]))\n  (%w[iss sub exp aud iat nbf jti] - payload.keys).empty?\nrescue JSON::ParserError, ArgumentError\n  false\nend","tryCatchPattern":null,"preventionTips":["Always mint via Lti::OAuth2::AccessToken.create_jwt so all claims are present","Diff token claims against the required list in tests before validate!","Update custom builders whenever Canvas adds required claims"],"tags":["lti","oauth2","jwt","missing-claims"],"backgroundTag":"missing-required-argument","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"}