jnunemaker/httparty · error · ArgumentError

:digest_auth must be a hash

Error message

:digest_auth must be a hash

What it means

Request#validate raises ArgumentError ':digest_auth must be a hash' when options[:digest_auth] is truthy but does not respond to #to_hash. Like basic_auth, the digest option must be a Hash (with username/password); httparty then runs its Net digest auth middleware to answer the server's WWW-Authenticate challenge. Malformed values fail validation before any network round trip.

Source

Thrown at lib/httparty/request.rb:420

      options[:headers]['Cookie'] = cookies_hash.to_cookie_string
    end

    # Uses the HTTP Content-Type header to determine the format of the
    # response It compares the MIME type returned to the types stored in the
    # SupportedFormats hash
    def format_from_mimetype(mimetype)
      if mimetype && parser.respond_to?(:format_from_mimetype)
        parser.format_from_mimetype(mimetype)
      end
    end

    def validate
      raise HTTParty::RedirectionTooDeep.new(last_response), 'HTTP redirects too deep' if options[:limit].to_i <= 0
      raise ArgumentError, 'only get, post, patch, put, delete, head, and options methods are supported' unless SupportedHTTPMethods.include?(http_method)
      raise ArgumentError, ':headers must be a hash' if options[:headers] && !options[:headers].respond_to?(:to_hash)
      raise ArgumentError, 'only one authentication method, :basic_auth or :digest_auth may be used at a time' if options[:basic_auth] && options[:digest_auth]
      raise ArgumentError, ':basic_auth must be a hash' if options[:basic_auth] && !options[:basic_auth].respond_to?(:to_hash)
      raise ArgumentError, ':digest_auth must be a hash' if options[:digest_auth] && !options[:digest_auth].respond_to?(:to_hash)
      raise ArgumentError, ':query must be hash if using HTTP Post' if post? && !options[:query].nil? && !options[:query].respond_to?(:to_hash)
    end

    def post?
      Net::HTTP::Post == http_method
    end

    def set_basic_auth_from_uri
      if path.userinfo
        username, password = path.userinfo.split(':')
        options[:basic_auth] = {username: username, password: password}
        @credentials_sent = true
      end
    end

    def decompress(body, encoding)
      Decompressor.new(body, encoding).decompress
    end

View on GitHub (pinned to 8f4a09e343)

Solutions

  1. Pass a Hash: `digest_auth: { username: user, password: pass }`.
  2. Split ENV-style creds: `digest_auth: Hash[%w[username password].zip(ENV['CREDS'].split(':'))]`.
  3. Use the class-level `digest_auth user, pass` DSL for static credentials.

Example fix

# before
Foo.get(url, digest_auth: 'user:pass')   # String -> ArgumentError

# after
Foo.get(url, digest_auth: { username: 'user', password: 'pass' })
Defensive patterns

Strategy: validation

Validate before calling

auth = { username: user, password: pass } unless auth.respond_to?(:to_hash)
raise ArgumentError, ':digest_auth must be a hash' unless auth.respond_to?(:to_hash)
Foo.get(url, digest_auth: auth)

Type guard

hash_like = ->(v) { v.respond_to?(:to_hash) }

Try / catch

begin
  Foo.get(url, digest_auth: auth)
rescue ArgumentError => e
  raise unless e.message.include?('digest_auth')
  Foo.get(url, digest_auth: { username: auth[:user], password: auth[:pass] })
end

Prevention

When it happens

Trigger: `Foo.get(url, digest_auth: 'user:pass')`, `digest_auth: [user, pass]`, or passing the response's WWW-Authenticate header string instead of credentials.

Common situations: Migrating from gems that accept positional or string credentials, credentials loaded from ENV as 'user:pass', and copy-pasting a digest challenge header into the option.

Understand the failure class

Background: Invalid argument type errors: "must be of type string", "expected X, got Y", and ERR_INVALID_ARG_TYPE explained — this error's family across 15 libraries.

Related errors


AI-assisted analysis of jnunemaker/httparty@8f4a09e343 (2026-08-21). Data as JSON: /api/errors/3519b4c4a5edf142. Report an issue: GitHub.