NTLM Single Sign On authentication module for Rack
What does this do?
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
If the exchange failed, the
REMOTE_USER variable will be either left empty or
assigned to a default value, dependent on the property
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.
How do I use it?
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
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
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