jnunemaker/httparty · error · ArgumentError

:basic_auth must be a hash

Error message

:basic_auth must be a hash

What it means

Request#validate raises ArgumentError ':basic_auth must be a hash' when options[:basic_auth] is truthy but does not respond to #to_hash. basic_auth is accepted as a keyword option on any verb call and must be a Hash with :username/:password (or 'username'/'password') keys; strings, arrays or auth objects from other libraries are rejected before the request runs.

Source

Thrown at lib/httparty/request.rb:419

      options[:headers] ||= {}
      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

View on GitHub (pinned to 8f4a09e343)

Solutions

  1. Pass the documented Hash: `basic_auth: { username: user, password: pass }`.
  2. Convert string creds: `basic_auth: Hash[%w[username password].zip(creds.split(':'))]`.
  3. Prefer the class-level DSL `basic_auth user, pass` for static credentials.

Example fix

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

# after
Foo.get(url, basic_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, ':basic_auth must be a hash' unless auth.respond_to?(:to_hash)
Foo.get(url, basic_auth: auth)

Type guard

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

Try / catch

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

Prevention

When it happens

Trigger: `Foo.get(url, basic_auth: 'user:pass')`, `basic_auth: [user, pass]`, or `basic_auth: SomeGem::Credentials.new` (an object without to_hash).

Common situations: Pasting 'user:pass' strings from curl examples, storing credentials as arrays/tuples in config, and adapting code from gems with different auth option shapes.

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