{"record":{"id":"28415f873fc2ff6c","repo":"instructure/canvas-lms","slug":"must-have-a-domain-and-a-user-to-build-a-jwt","errorCode":null,"errorMessage":"Must have a domain and a user to build a JWT","messagePattern":"Must have a domain and a user to build a JWT","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"gems/canvas_security/lib/canvas_security/services_jwt.rb","lineNumber":105,"sourceCode":"                      )\n                    elsif symmetric\n                      CanvasSecurity.create_encrypted_jwt(payload, signing_secret, encryption_secret)\n                    else\n                      CanvasSecurity.create_encrypted_jwt(\n                        payload,\n                        CanvasSecurity::ServicesJwt::KeyStorage.present_key,\n                        encryption_secret,\n                        :autodetect\n                      )\n                    end\n    return crypted_token unless base64\n\n    CanvasSecurity.base64_encode(crypted_token)\n  end\n\n  def self.for_user(domain, user, real_user: nil, workflows: nil, context: nil, symmetric: false, encrypt: true, audience: nil, root_account_uuid: nil, base64: true)\n    if domain.blank? || user.nil?\n      raise ArgumentError, \"Must have a domain and a user to build a JWT\"\n    end\n\n    payload = {\n      sub: user.global_id,\n      user_uuid: user.uuid,\n      domain:\n    }\n    payload[:masq_sub] = real_user.global_id if real_user\n    if workflows.present?\n      payload[:workflows] = workflows\n      state = CanvasSecurity::JWTWorkflow.state_for(workflows, context, user)\n      payload[:workflow_state] = state unless state.empty?\n    end\n    if context\n      payload[:context_type] = context.class.name\n      payload[:context_id] = context.id.to_s\n    end\n    if audience","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/gems/canvas_security/lib/canvas_security/services_jwt.rb#L87-L123","documentation":"ServicesJwt.for_user builds a services JWT whose payload includes the domain and the user's global_id/uuid. Both a domain and a user object are mandatory; if either is blank/nil the JWT would be unusable, so ArgumentError is raised before any signing work.","triggerScenarios":"Calling CanvasSecurity::ServicesJwt.for_user(nil_or_blank_string, user) or for_user(domain, nil), e.g. when the current_user is nil (unauthenticated request) or the request host is empty.","commonSituations":"Background jobs without a loaded user object; controllers hitting the method when current_user is nil; domain derived from request.host being empty in console/test contexts.","solutions":["Guard that domain is present and user is not nil before calling for_user","Load/return the user first (authenticate! or find) and bail out when nil","Derive the domain explicitly (e.g. from the account/root account) instead of a possibly-empty request.host"],"exampleFix":"// before\njwt = CanvasSecurity::ServicesJwt.for_user(request.host, @current_user)\n// after\nif request.host.present? && @current_user\n  jwt = CanvasSecurity::ServicesJwt.for_user(request.host, @current_user)\nelse\n  return render json: { error: 'unauthorized' }, status: :unauthorized\nend","handlingStrategy":"validation","validationCode":"raise ArgumentError, 'need domain and user' if domain.blank? || user.nil?","typeGuard":"def jwt_buildable?(domain, user) = domain.present? && !user.nil?","tryCatchPattern":null,"preventionTips":["Authenticate the user before minting service JWTs","Pass an explicit domain, not request.host, in console/job contexts","Guard controllers with authenticate! before token generation"],"tags":["ruby","jwt","argument-validation"],"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"}