instructure/canvas-lms · error · ArgumentError

Must have a domain and a user to build a JWT

Error message

Must have a domain and a user to build a JWT

What it means

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.

Solutions

  1. Guard that domain is present and user is not nil before calling for_user
  2. Load/return the user first (authenticate! or find) and bail out when nil
  3. Derive the domain explicitly (e.g. from the account/root account) instead of a possibly-empty request.host

Example fix

// before
jwt = CanvasSecurity::ServicesJwt.for_user(request.host, @current_user)
// after
if request.host.present? && @current_user
  jwt = CanvasSecurity::ServicesJwt.for_user(request.host, @current_user)
else
  return render json: { error: 'unauthorized' }, status: :unauthorized
end
Defensive patterns

Strategy: validation

Validate before calling

raise ArgumentError, 'need domain and user' if domain.blank? || user.nil?

Type guard

def jwt_buildable?(domain, user) = domain.present? && !user.nil?

Prevention

When it happens

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

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

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/28415f873fc2ff6c. Report an issue: GitHub.

Appendix: source

Thrown at gems/canvas_security/lib/canvas_security/services_jwt.rb:105

                      )
                    elsif symmetric
                      CanvasSecurity.create_encrypted_jwt(payload, signing_secret, encryption_secret)
                    else
                      CanvasSecurity.create_encrypted_jwt(
                        payload,
                        CanvasSecurity::ServicesJwt::KeyStorage.present_key,
                        encryption_secret,
                        :autodetect
                      )
                    end
    return crypted_token unless base64

    CanvasSecurity.base64_encode(crypted_token)
  end

  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)
    if domain.blank? || user.nil?
      raise ArgumentError, "Must have a domain and a user to build a JWT"
    end

    payload = {
      sub: user.global_id,
      user_uuid: user.uuid,
      domain:
    }
    payload[:masq_sub] = real_user.global_id if real_user
    if workflows.present?
      payload[:workflows] = workflows
      state = CanvasSecurity::JWTWorkflow.state_for(workflows, context, user)
      payload[:workflow_state] = state unless state.empty?
    end
    if context
      payload[:context_type] = context.class.name
      payload[:context_id] = context.id.to_s
    end
    if audience

View on GitHub (pinned to 1c9f0bb801)