This is an authentication module that presents a connecting client with an NTLM
challenge. A client that can speak NTLM will then respond to the challenge
according to protocol. All the challenge/response stuff is handled by the
module. At the end of the exchange the server will have received the Windows
user account name from the client. The module saves the user name to the
environment variable REMOTE_USER
.
If the exchange failed, the REMOTE_USER
variable will be either left empty or
assigned to a default value, dependent on the property default_remote_user
.
NOTE: The code only causes the server to respond with an NTLM exchange. It doesn't actually verify any of the stuff that the browser sends. I've been using this only as a way to get the Windows account name (which is configured once during IE setup and then locked here) through NTLM. In a situation where the remote user can input a user name and password herself (i.e. not in a controlled environment), the result is not to be trusted. To verify the LM hash in the response you might be able to pass it to Samba's winbind.
In a rails application you should write an initialiser first. Create a file config/initializers/ntlm-sso.rb
with the following contents:
require 'rack'
require 'rack/auth/ntlm-sso'
require 'continuation'
class NTLMAuthentication
def initialize(app)
@app = app
end
def call(env)
req = Rack::Request.new(env)
env = callcc do |cont|
auth = Rack::Auth::NTLMSSO.new(cont)
# optional configuration
#auth.domain = "MYDOMAIN"
#auth.default_remote_user = "John Lennon"
return auth.call(env)
end
@app.call(env)
end
end
You will also need to enable this snippet as middleware to be used in your
application by adding the line config.middleware.use "NTLMAuthentication"
to
config/application.rb
.
Of course you also have to add gem 'ntlm-sso', '=0.0.1'
to your application's
Gemfile
after building the gem from source.
After restarting your application, all requests should automatically pass through this authentication code.
Note that due to the nature of NTLM, authentication cannot be made optional. NTLM always responds with error 401 plus a message in the header. A client that does not understand the NTLM protocol will interpret this as plain "Access denied".
If you want to bypass this module when the client is not a browser, you can
modify the call
function:
def call(env)
req = Rack::Request.new(env)
# Try NTLM auth only if the client is a browser.
# Their user-agent strings all start with "Mozilla" for historical reasons.
if req.user_agent[/^Mozilla/]
env = callcc do |cont|
auth = Rack::Auth::NTLMSSO.new(cont)
# optional configuration
#auth.domain = "MYDOMAIN"
#auth.default_remote_user = "John Lennon"
return auth.call(env)
end
end
@app.call(env)
end