instructure/canvas-lms · error · ArgumentError

requestor_user is required for JWT generation

Error message

requestor_user is required for JWT generation

What it means

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.

Solutions

  1. Always pass requestor_user: when constructing the service, e.g. PageViews::FetchResultService.new(config, requestor_user: current_user)
  2. In controllers, guard that current_user is present (authenticate before reaching the service) or render 401 early
  3. In background jobs, load and pass the user object that originally initiated the page-views query
  4. Add an explicit check at the call site (raise early in initialize) if you want the failure at construction time rather than request time

Example fix

// before
service = PageViews::FetchResultService.new(config)
result = service.call(query_id) # ArgumentError: requestor_user is required

// after
raise "page_views requestor missing" unless current_user
service = PageViews::FetchResultService.new(config, requestor_user: current_user)
result = service.call(query_id)
Defensive patterns

Strategy: validation

Validate before calling

raise "requestor_user required for page_views" unless defined?(current_user) && current_user
service = PageViews::FetchResultService.new(config, requestor_user: current_user)

Try / catch

begin
  result = service.call(query_id)
rescue ArgumentError => e
  raise unless e.message.include?("requestor_user")
  render json: { error: "authentication_required" }, status: :unauthorized
end

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/74744b1e1452fd5e. Report an issue: GitHub.

Appendix: source

Thrown at app/services/page_views/service_base.rb:39

  class ServiceBase
    def initialize(configuration, requestor_user: nil)
      @configuration = configuration
      @requestor_user = requestor_user
    end

    protected

    attr_reader :configuration

    def request_headers
      request_id = RequestContext::Generator.request_id
      jwt_token = generate_jwt_token
      { "Authorization" => "Bearer #{jwt_token}",
        "X-Request-Context-Id" => request_id }
    end

    def generate_jwt_token
      raise ArgumentError, "requestor_user is required for JWT generation" unless @requestor_user

      CanvasSecurity::ServicesJwt.for_user(
        HostUrl.default_host,
        @requestor_user,
        encrypt: false,
        base64: false
      )
    end

    def get_with_clean_redirect(uri, headers, &)
      http = Net::HTTP.new(uri.host, uri.port)
      http.use_ssl = uri.scheme == "https"
      http.ssl_timeout = http.open_timeout = CanvasHttp::OPEN_TIMEOUT
      http.read_timeout = CanvasHttp::READ_TIMEOUT
      http.write_timeout = CanvasHttp::WRITE_TIMEOUT
      http.max_retries = 0
      response = http.request(Net::HTTP::Get.new(uri.request_uri, headers))

View on GitHub (pinned to 1c9f0bb801)