{"record":{"id":"74744b1e1452fd5e","repo":"instructure/canvas-lms","slug":"requestor-user-is-required-for-jwt-generation","errorCode":null,"errorMessage":"requestor_user is required for JWT generation","messagePattern":"requestor_user is required for JWT generation","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"app/services/page_views/service_base.rb","lineNumber":39,"sourceCode":"  class ServiceBase\n    def initialize(configuration, requestor_user: nil)\n      @configuration = configuration\n      @requestor_user = requestor_user\n    end\n\n    protected\n\n    attr_reader :configuration\n\n    def request_headers\n      request_id = RequestContext::Generator.request_id\n      jwt_token = generate_jwt_token\n      { \"Authorization\" => \"Bearer #{jwt_token}\",\n        \"X-Request-Context-Id\" => request_id }\n    end\n\n    def generate_jwt_token\n      raise ArgumentError, \"requestor_user is required for JWT generation\" unless @requestor_user\n\n      CanvasSecurity::ServicesJwt.for_user(\n        HostUrl.default_host,\n        @requestor_user,\n        encrypt: false,\n        base64: false\n      )\n    end\n\n    def get_with_clean_redirect(uri, headers, &)\n      http = Net::HTTP.new(uri.host, uri.port)\n      http.use_ssl = uri.scheme == \"https\"\n      http.ssl_timeout = http.open_timeout = CanvasHttp::OPEN_TIMEOUT\n      http.read_timeout = CanvasHttp::READ_TIMEOUT\n      http.write_timeout = CanvasHttp::WRITE_TIMEOUT\n      http.max_retries = 0\n      response = http.request(Net::HTTP::Get.new(uri.request_uri, headers))\n","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/services/page_views/service_base.rb#L21-L57","documentation":"PageViews::ServiceBase#generate_jwt_token raises ArgumentError when @requestor_user is nil. Every service request signs itself with a CanvasSecurity::ServicesJwt issued for the requesting user, and that JWT is built in request_headers, so constructing a PageViews service (e.g. FetchResultService, ServiceBase subclass) without requestor_user: makes authenticated calls impossible.","triggerScenarios":"Instantiating any PageViews::ServiceBase subclass (FetchResultService, query/polling services) without the requestor_user: keyword argument, then invoking a method that builds request_headers (call, poll, etc.). initialize accepts requestor_user: nil silently; the error only fires when a request is actually made.","commonSituations":"A controller passes current_user but the user is nil for an unauthenticated/API-token request path; a background job calls the service with only configuration and forgets the user; refactoring renamed the keyword argument (was positional or differently named) so the value silently falls back to the nil default.","solutions":["Always pass requestor_user: when constructing the service, e.g. PageViews::FetchResultService.new(config, requestor_user: current_user)","In controllers, guard that current_user is present (authenticate before reaching the service) or render 401 early","In background jobs, load and pass the user object that originally initiated the page-views query","Add an explicit check at the call site (raise early in initialize) if you want the failure at construction time rather than request time"],"exampleFix":"// before\nservice = PageViews::FetchResultService.new(config)\nresult = service.call(query_id) # ArgumentError: requestor_user is required\n\n// after\nraise \"page_views requestor missing\" unless current_user\nservice = PageViews::FetchResultService.new(config, requestor_user: current_user)\nresult = service.call(query_id)","handlingStrategy":"validation","validationCode":"raise \"requestor_user required for page_views\" unless defined?(current_user) && current_user\nservice = PageViews::FetchResultService.new(config, requestor_user: current_user)","typeGuard":null,"tryCatchPattern":"begin\n  result = service.call(query_id)\nrescue ArgumentError => e\n  raise unless e.message.include?(\"requestor_user\")\n  render json: { error: \"authentication_required\" }, status: :unauthorized\nend","preventionTips":["Always pass requestor_user: explicitly at every construction site; avoid relying on defaults","Authenticate users before controller code reaches the service layer","In jobs, persist and reload the initiating user instead of passing nil","Grep for `PageViews::.*Service.new(` in code review to check requestor_user is supplied"],"tags":["jwt","authentication","argument-error","page-views"],"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"}