Single Sign On In Windows Domains
The original of this HowTo can be found at Diary Products.
One of my clients runs his own intranet for his employees. The intranet currently consists of a bunch of static HTML pages which are written and maintained by a single editor. This approach might have been sufficient five years ago but as the intranet grew it became apparent that the static HTML would become unmanageable in the near future. I therefore suggested replacing the existing solution with a community-oriented CMS. Due to certain requirements the set of contestants was cut down to two: Drupal and Plone. I favored Plone because a) I am slightly disappointed with the direction and pace that Drupal’s development has recently taken and b) I wanted to learn Zope and Plone anyway.
Table Of Contents
State Of The Art
Step 1 - NTLM authentication for Apache: mod_ntlm
Step 2 - Zope/Plone and Apache
Step 3 - FastCGI
Step 4 - Enable NTLM authentication for requests to Zope
Step 5 - Install Remote User Folder
Appendix 1 - Exempting the ZMI from NTLM authentication
Appendix 2 - Schizophrenia
Appendix 3 - Disabling Zope's http server
Appendix 4 - Virtual Host Monster (VHM)
Appendix 5 - What about Group User Folder?
Appendix 6 - Problems with mod_ntlm under load
Appendix 7 - Multiple Windows Domains
Appendix 8 - Kerberos
The most important requirement was the integration of Plone’s user management into the client’s Windows domain. This requirement had two implications:
- Unified logon: Any domain user should automatically be a Plone user. There should not be any additional user administration in Plone.
- Seamless authentication: Users should only have to logon once. After a user is logged into the domain, Plone should not ask for a password a second time.
The above requirements are usually summarized as single-sign-on. This article describes how single-sign-on can be achieved by gluing together various pieces of software in addition to Zope/Plone; namely, Apache, mod_ntlm, mod_fastcgi, a Samba or Windows 2000 Server and Zope’s Remote User Folder.
In coming to my conclusions I examined several possible solutions:
- LDAPUserFolder could be used to fulfill the unified logon requirement by integrating Active Directory (Microsoft’s extended LDAP directory implementation for Windows domains) with Plone. However, with LDAPUserFolder the user would need to login into the CMS separately. This is basically because there is no trust relationship between the Zope Server that Plone runs on and the Windows workstation that the client logs into. Currently, such trust can only be established using Microsoft’s own proprietary HTTP authentication protocol, called NTLM authentication. NTLM auth was initially supported by Internet Explorer (IE) and Microsoft’s web server, Internet Information Server (IIS). The NTLM authentication scheme is sneered at by many because it’s thought to be rather weak (I remember reading somewhere that there is a stronger Kerberos extension to it). Notwithstanding, it has been adopted by a variety of open source projects, probably because of user demand and usefulness. The Squid proxy server supports it, Firefox 1.0 supports it and for Apache there is mod_ntlm. I successfully used all of these in conjunction with NTLM authentication in my client’s intranet. The security risk imposed by NTLM authentication in a controlled LAN environment is acceptable.
- The Plone product exUserFolder can authenticate against a windows domain but it doesn’t support NTLM authentication either.
- The Plone product PluggableAuthService (PAS) looks very promising but I doubt that at the time of this writing it’s ready for production or that it even supports NTLM auth. There were posts on the corresponding mailing list that indicate someone is working on NTLM auth though.
This leaves us with one remaining option: Running Zope behind Apache with mod_ntlm installed. The remainder of this article focuses on the details of how to get Apache 2.0 to do the authentication and yet let Zope serve the content for the Plone CMS. The procedure for Version 1.3 of the Apache web server should be very similar but is not addressed in this article.
Update, Feb 1st, 2005: Read this section and then immediately read Appendix 6!
Apache’s mod_ntlm is
available for Apache 1.3 and 2.0 and although still in beta it is pretty much usable
as long as you use it in one domain only. I prefer loading it as a module, which
is the default build setting. Download the modntlm2 source package  and
compile and install it according to the instructions found either on the home page  or
in the package itself. The compilation requires the Apache header files. The installation
uses the apxs program to install the compiled module but that didn’t work
on my Suse Linux system. If this is the case for your system too, there are two
things you will have to do. First, you will need to copy the mod_ntlm.so file to
the directory that contains the other Apache modules, e.g
Looking into httpd.conf for
LoadModule statements of other modules
should give you an idea.
LoadModule ntlm_module /usr/lib/apache2/mod_ntlm.so
The bold part will probably need to be adjusted to match your system’s module path. Restart Apache to see if it starts up correctly. Then add this to your httpd.conf:
Instead of putting the above statements into a
you can also put them into an existing
Point your browser to
/ntlmtest on your server (
or to the directory to which you added the above http.conf statements. If you get
a password prompt or an “Authentication Required” error page, something
isn’t working. If you get an “Object Not Found” 404-error page,
that’s good. Note that you must use Mozilla Firefox 1.0 for Windows or Internet
Explorer when you test this and that you have to be logged on as a domain user.
Firefoxies, read this: In order to use NTLM auth on direct HTTP connections (as
opposed to proxy connections that support NTLM out of the box) you need to add
your server to the list of trusted URIs. To do so, start Firefox, type
the address text field, find the setting
double-click it and enter
http://yourserver (no slash at the end).
This might seem tedious but for security reasons it’s a good idea to limit
the servers that a user agent exchanges NTLM challenges with because NTLM auth
is susceptible to man-in-the-middle attacks. Better safe then sorry. If you want
to be sorry, you can set the trusted URIs setting to
slashes at the end).
Also, check your server access logs (usually somewhere in
and see whether your test requests were logged with your user name. If not, something
is not working properly or your log format is non-standard (unlikely). You need
to fix this before you continue.
If you haven’t done so yet, you should install Zope and Plone now and setup up your Plone site. Don’t add any users yet. When install a Zope instance, you need to specify the credentials of the instance manager. The manager should be the only user available in Zope and Plone.
OK, we got NTLM authentication working and we have a functioning Plone and Zope installation. What’s left is chaining Apache and Zope such that
- Apache forwards the requests to Zope,
- Apache passes the authenticated user’s name to Zope and
- Plone accepts that user as a member of the Plone site.
There are plenty of ways to run Zope behind Apache:
- CGI -- For every request, Apache runs the python binary, passing the request headers in environment variables and the request body in a pipe (I think). The Zope response is sent back through a pipe. After this, the Zope process finishes. Extremely slow and not recommended.
- FastCGI – Apache creates a socket connection to an already running Zope and passes the request info through that connection. Zope handles the request and sends the response back through that connection. The connection is closed and re-opened for every request but the Zope process continues to run. Much faster. Additional environment variables can be transferred through the socket connection.
- mod_proxy alone – This is conceptually similar but technically different from FastCGI. Apache works as a proxy and forwards the HTTP requests to Zope. This method is as fast as FastCGI. No environment variables can be passed along with the forwarded request. The URLs that Apache receives have the same postfix as the ones that Apache sends to Zope but the prefix can be different.
- mod_rewrite together with mod_proxy – Very similar to the previous solution but it allows for rewriting the URLs.
Although all of the above techniques have pros and cons, there is one killer requirement that rules out all but one technique: transferring the authenticated user from Apache over to Zope. So before we investigate how we are going to connect Apache and Zope, let’s have a look at the Zope authentication and permission system.
Disclaimer: I am more or less a Zope newbie so from here on out we will be moving into unknown territory. Don’t take everything I say for granted but don’t lose your faith either – the solution described in this article does actually work!
Zope (and hence Plone) organizes users in special folders called user folders. When added to an object, the user folder determines the available set of users for this object. Generally speaking, if a user folder is added to the Plone object, only the users in that user folder can access the Plone site. Well, not quite. Firstly, the Plone site can also be accessed by users of user folders further up in the hierarchical Zope database. For example, the users in the acl_users object at the Root Folder are granted access to the Plone site as well. Secondly, the users can be assigned Roles that further restrict the operations available to the users.
Different authentication mechanisms are added to Zope by adding specialized types
of user folders to a Zope instance. The default user folder is acl_users at the
Root Folder of any vanilla Zope instance. Another type of user folder is
LDAP user folder.
This user folder type doesn’t store the users in the Zope database but in
an LDAP directory such that LDAP user entries appear as Zope users. I also want
to mention GRUF (Group User Folder) as it is a very common user folder for Plone
sites. (See Appendix 5 – What about Group User Folder? )
Another type of user folder, the so-called Remote
User Folder is of particular interest to us. It doesn’t contain any
persistent user objects at all. Instead, it assumes that authentication has already
been completed for an incoming HTTP request and it looks up the request’s
authenticated user in its own list of users. A Remote User Folder obtains the
name of the authenticated user from the
variable. As mentioned earlier, environment variables are not part of a standard
HTTP request. For this reason we cannot use either of the two mod_proxy solutions
because mod_proxy uses HTTP only. We’ll have to stick to either CGI or
If the user isn’t present, a Remote User Folder will either reject the user (this is the default setting) or create a matching user entry automatically, provided that this feature is enabled. The on-the-fly creation of users is particularly helpful for our purposes, as it completely delegates authentication and the user management to some other instance. Finally, we have all the ingredients:
- The browser sends a request to the Apache web-server together with an invitation for NTLM authentication of the currently logged on user
- Apache receives the request
- The ntlm_auth module authenticates the request by mediating the NTLM challenge/response handshake between the user’s browser and the domain controller
- Once the user is authenticated, Apache dispatches the request to the Zope server, using the FastCGI protocol and including the authenticated user’s name in the REMOTE_USER environment variable
- Zope receives the request and - based on the path of the requested object – passes the request to the appropriate user folders for authorization
- The Remote User Folder obtains the user’s name from
REMOTE_USER, scans its user list for that user name and adds an entry for the user if it is not yet present. Depending on the type of the requested object and the roles assigned to the user, the request is either denied with a 401 response or granted and executed.
Although this all sounds very lengthy, it is in fact rather easy to achieve.
Download mod_fastcgi package from the FastCGI homepage and follow the instructions in the INSTALL file (or INSTALL.AP2 for Apache 2). Compiling and installing FastCGI was a bit of headache for me because FastCGI uses Apache’s build system and I didn’t have a proper set of Apache headers and makefiles. My Apache installation is a backport RPM for my ancient Suse 7.1 so I think my difficulties are more the packager’s fault than FastCGI’s.
LoadModule fastcgi_module /usr/lib/apache2-prefork/mod_fastcgi.so
FastCgiExternalServer /usr/local/httpd/htdocs/zope -host localhost:8081 -pass-header Authorization
FastCgiExternalServer statement tells FastCGI which request paths
should be handled by mod_fastcgi. I’m not sure why it’s specified as
a file system path instead of a URL path but the file name is just a dummy and
the file does not need to exist. You can choose any other name instead of zope
but if you do that, you’ll need to adjust the following instructions accordingly.
Location statement tells Apache that requests to
handled by FastCGI.
-pass-header option tells Apache to send authorization information
along with the requests. This doesn’t affect NTLM authentication because
REMOTE_USER will be set regardless of this option.
It only affects whether standard HTTP authentication headers will be handed over
to Zope, thus enabling us to use the Zope management interface (ZMI) through Apache
(see Appendix 1 – Exempting the ZMI from NTLM auth and Appendix
2 – Schizophrenia).
-host option denotes the host name and port number to which FastCGI
request will be sent. We need to enable Zope’s FastCGI server and configure
it to listen on the given port. Add the following lines to the
file which is usually located in the Zope instance’s
Restart Apache and the Zope instance.
You already tested NTLM auth. in Apache by adding a dummy
section to Apache’s configuration file. Edit http.conf one more time and
<Location /ntlmtest> section we added earlier:
It is important that the intranet component of the location path matches the identity of the Plone site object. You can verify the identity using the ZMI.
Also note that we now have two Location sections: one with FastCGI for
one with NTLM for
/zope is a
/zope/intranet, FastCGI and NTLM auth will
be active for
/zope/intranet. (For an explanation of why we do not
use NTLM auth for
/zope see Appendix 1 – Exempting
the ZMI from NTLM auth)
Restart Apache. For the remainder of this article I will assume that you know the basics about ZMI, like creating and deleting objects or changing object properties. (In case you don’t, there is plenty of information about ZMI on the web.)
- Download and install the Remote User Folder product in your Zope instance directory.
- Point your browser to the ZMI and log in as manager. Use Zope’s HTTP port (default is 8080) because we are not quite ready to connect via Apache/FastCGI.
- Remove the existing user folder from the Plone site object.
- Add a Remote User Folder to the Plone site object. It will be called
acl_users. Make sure to add it to the Plone site, not the
- Enable automatic creation of user objects in Plone. This option is called
Auto Add Usersand can be found on the property tab of the
acl_usersfolder that we just created.
In Step 3 – FastCGI ) and Step 4 – Enable NTLM authentication for
requests to Zope ) we used distinct location sections, one for FastCGI and one
for NTLM auth. We did this for one main reason: Although Apache wraps the entire
Zope instance, NTLM auth should only be performed for URLs that point to the Plone
site. It’s important that
/zope and especially
exempt from NTLM auth because we do not want to lock out the Zope manager. The
Zope manager should be able to login using standard HTTP authentication. Furthermore,
there might be other Zope objects beside your Plone site for which NTLM auth would
not be appropriate.
Theoretically speaking, it should be possible to add the Remote User Folder to
the Zope instance root instead of the Plone site and to enable NTLM auth for the
entire Zope instance but there’s a caveat. I wasn’t able to replace
acl_users folder with a different one, say Remote User
Folder (RUF) because after removing the
acl_users folder, I couldn’t
do anything at all because the manager (me) was locked out. There may be a trick
to get around this, though.
When accessing the ZMI through Apache/FastCGI, you will notice that you’ll
still have to enter the credentials of the manager. When you click on the intranet
icon representing the Plone site in the tree view on the left hand side frame,
you may notice that you turn from Zope manager into the Plone user that corresponds
with your Windows domain account. This is because the Plone part of the ZMI also
/zope/intranet URL prefix for which NTLM authentication is
enabled in Apache. For requests to this URL the browser sends NTLM authentication
headers instead of HTTP authentication. I’m not quite sure as to why NTLM
authentication overrides HTTP authentication, as there clearly is a conflict between
the two. If your domain account is not assigned the Manager role in Plone, you
will not get the management view of the Plone folder. Instead you will get the
Plone home page. In order to fix this you need to connect to the ZMI directly via
Zope’s HTTP port and assign the Manager role to the user object in Plone’s
that corresponds to your Windows domain account.
Unless you already disabled the HTTP server in zope.conf, your Zope instance is listening on both the FastCGI and the HTTP ports. Using HTTP you should still be able to access everything in Zope including the management interface and excluding the Plone site. But because a Remote User Folder depends on the REMOTE_USER environment variable, access to the Plone site is only possible via FastCGI and thus through Apache. I recommend disabling the HTTP port in zope.conf because we don’t need it. The entire Zope site is accessible through Apache and FastCGI. Because HTTP is a more direct and robust way of connecting to your Zope instance, it might be necessary to temporarily enable the HTTP port for maintenance and debugging.
… is not needed apparently. VHM or SiteRoot objects are used whenever Zope runs behind something that disguises Zope’s internal URLs. VHM and SiteRoot make sure that URLs generated by Zope are mapped to the external URL format. Although this also applies to the solution described here, it seems that the URL mapping is taken care of by Zope’s FastCGI server.
Group User Folder (GRUF) can store users and groups. Instead of maintaining its own list of users and groups, it delegates the user and group storage to any number of user folders of any type. In that respect it is some kind of compound user folder, because it contains other user folders. The contained user folders are called sources. At the time of writing, Remote User Folder and GRUF are not compatible, i.e. Remote User Folder cannot be used as a source for GRUF. Stay tuned for updates on this issue …
People have reported that repeatedly refreshing the page causes a basic authentication dialog to appear. I initially though that it was a browser problem so I tested both Firefox and Internet Explorer. I was able to reproduce that problem under both browsers. The forum and bugtracking area sourceforge project page contains numerous posts describing the same symptoms, so the problem must be in mod_ntlm. In this post, Michael Cai and Jamie Kerwick announce their own improved version of mod_ntlm and mod_ntlm2 , which supposedly fixes this and other issues. I tried it out and was not able to reproduce the problem anymore. The bottom line is that you shouldn't use the official version. Instead, use Michael Cai's unofficial MOD_NTLM Apache module.
Sidenote: The mod_ntlm project managers seems to have abondened their baby. The last sign of life from them was spotted in May 2004, announcing a new version to be due "in a few days". Since then, various people have attempted to fix/rewrite mod_ntlm (see the project forum ).
The mod_ntlm version described in Appendix 6 has support for multiple domains. If anybody has tested this feature, I'd appreciate it if they could contact me.
Andrew Bartlett has rewritten mod_ntlm_winbind and he claims that it's coded more cleanly than mod_ntlm and that
"It is also a very good base to add a SPENGO (Negotiate) module, that accepts NTLMSSP as well as kerberos".
There is no official homepage and it may only work with Samba3. You're on your own. Let me know if you succeed.