49
NTLM authentication with Ruby and Faraday
Everyone knows how HTTP basic authentication works. A lot of services use it often. Once I happened upon with NTLM auth. Getting access was challenging, but I coped with it. After working on the task I would like to share the solution how to work with NTLM and Faraday library.
For instance, with curl
you can specify the user name and password for server authentication. The command looks like this: curl -u <USERNAME>:<PASSWORD> https://resource_with_ntlm_auth.com/path_to_resource.xml -I
. Unfortunately it doesn't work with NTLM. The response would be:
HTTP/1.1 401 Unauthorized
Connection: Keep-Alive
Content-Length: 1292
Date: Tue, 01 Nov 2021 12:00:00 GMT
Content-Type: text/html
Server: Microsoft-IIS/10.0
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
X-Powered-By: ASP.NET
In order to authenticate by curl, we have to use --ntlm flag. curl -u <USERNAME>:<PASSWORD> --ntlm https://resource_with_ntlm_auth.com/path_to_resource.xml -I
The response is:
HTTP/1.1 401 Unauthorized
Connection: Keep-Alive
Content-Length: 341
Date: Tue, 01 Nov 2021 12:00:00 GMT
Content-Type: text/html; charset=us-ascii
Server: Microsoft-HTTPAPI/2.0
WWW-Authenticate: NTLM <SECRET_STRING>
HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Length: 321981
Date: Tue, 01 Nov 2021 12:00:00 GMT
Content-Type: text/xml
ETag: "d783128d33d5d22"
Server: Microsoft-IIS/10.0
Accept-Ranges: bytes
Last-Modified: Tue, 01 Nov 2021 10:00:00 GMT
Persistent-Auth: true
X-Powered-By: ASP.NET
Noticed that we have two responses? 😅 It's the feature of NTLM.
So, how to authenticate via Ruby, not using curl?
There is a library on GitHub ruby-ntlm. Looking into the code you can see an example of how to authenticate. Even so, I don't like the solution because it monkey patches Net::HTTP
class.
It's better to see another example that uses Net::HTTP#start
. Looking into the source code, we have noticed that NTLM auth is tricky because it requires that the connection must be keep-alive.
require 'ntlm'
require 'net/http'
Net::HTTP.start('www.example.com') do |http|
request = Net::HTTP::Get.new('/')
request['authorization'] = 'NTLM ' + NTLM.negotiate.to_base64
response = http.request(request)
# The connection must be keep-alive!
challenge = response['www-authenticate'][/NTLM (.*)/, 1].unpack('m').first
request['authorization'] = 'NTLM ' + NTLM.authenticate(challenge, 'User', 'Domain', 'Password').to_base64
response = http.request(request)
p response
print response.body
end
Net::HTTP
is good. Even so, how to authenticate with popular gem, for instance Faraday? I started to figure out how to implement the protocol with Faraday 🤔
As the NTLM auth algorithm requires to keep the connection alive, we have to use the gem net-http-persistent. Also we have to use the ruby-ntlm gem because coding/encoding logic is tricky.
After spending some time I have found the solution. The solution is here:
require 'faraday'
require 'ntlm'
# It requires gems 'ruby-ntlm' and 'net-http-persistent'
connection = Faraday.new('https://resource_with_ntlm_auth.com') do |f|
# The connection must be keep-alive
f.adapter :net_http_persistent
end
response = connection.get do |req|
req.url '/path_to_resource.xml'
req.headers['Content-Type'] = 'application/xml'
req.headers['authorization'] = 'NTLM ' + NTLM::Message::Negotiate.new({}).to_base64
end
puts response.status # we received 401 status
challenge = response['www-authenticate'][/NTLM (.*)/, 1].unpack1('m')
response = connection.get do |req|
req.url '/path_to_resource.xml'
req.headers['Content-Type'] = 'application/xml'
req.headers['authorization'] = 'NTLM ' + NTLM.authenticate(challenge, ENV['USER'], nil, ENV['PASSWORD']).to_base64
end
puts response.status # we have got 200 status 🥳
That's all! I hope the article will be useful for you.
49