Python, Django and LDAP authentication

We've recently had to integrate one of our Django web apps to an LDAP authentication server. Not having done this before, it took a little while longer than expected. There are many subtle stumbling blocks along the way. Here's a list of how we eventually got it working, and some of the issues we encountered. Let's assume your Django project is already setup and working on say a Debian based linux server. To get LDAP working, you'll need to install the following packages:

  • yum install openldap-clients        
  • yum install openssl                      
  • yum install python-ldap               
  • django-auth-ldap  (using pip or easy_install or manually)    
Make sure your apache/httpd user has read/execute permissions to the django_auth_ldap module and to all other modules and packages. Insufficient permissions can manifest in very non-descriptive error messages...

Get your LDAP server connection details. You will need:
  • AUTH_LDAP_SERVER_URI -- the hostname of your ldap server, e.g. "ldaps://ldap.company.com:636"
  • AUTH_LDAP_BIND_DN -- an application specific user that is used to query the ldap server, e.g. "'cn=myappname,ou=apps,o=company'"
  • AUTH_LDAP_BIND_PASSWORD -- password for above application specific user record to authenticate queries
  • AUTH_LDAP_USER_SEARCH -- the search string that will resolve the django username to the correct location in the LDAP server, e.g. "LDAPSearch("ou=people,o=company", ldap.SCOPE_SUBTREE, "(uid=%(user)s)")"
    • so this will search in the company/people directory for an object where the uid == django login username (whatever the user types in the login form), the password is also sent along and used by the ldap server to authenticate the request
Once you have all these details, you need to add the necessary configuration options to your settings.py file, for example:

==============================
import ldap
from django_auth_ldap.config import LDAPSearch, GroupOfNamesType

#AUTH_LDAP_START_TLS = True          
AUTH_LDAP_GLOBAL_OPTIONS = {
    ldap.OPT_X_TLS_REQUIRE_CERT: False,
    ldap.OPT_REFERRALS: False,
}

AUTH_LDAP_SERVER_URI = "ldaps://ldap.company.com:636"
AUTH_LDAP_BIND_DN = 'cn=myapp,ou=apps,o=company'  
AUTH_LDAP_BIND_PASSWORD = 'myapp_password'    
AUTH_LDAP_USER_SEARCH = LDAPSearch("ou=people,o=company", ldap.SCOPE_SUBTREE, "(uid=%(user)s)")

AUTH_LDAP_ALWAYS_UPDATE_USER = False

AUTH_LDAP_USER_ATTR_MAP = {
    "first_name": "givenName",
    "last_name": "sn",
    "email": "mail"
}

AUTHENTICATION_BACKENDS = (
    'django_auth_ldap.backend.LDAPBackend',
    'django.contrib.auth.backends.ModelBackend',
)
=====================================

A few more things to note here:
  • If using secure ldap (ldaps), you need to either set AUTH_LDAP_START_TLS = True  or prefix your AUTH_LDAP_SERVER_URI with "ldaps://...", 
    • You can't do both though, as you'll likely get an OPERATIONS_ERROR saying SSL has already been started
  • When a user is correctly authenticated against your LDAP server, Django will first try to retrieve an existing user record with the same username, or if it doesn't exist, it will create one
    • however, it will leave the is_staff and is_superuser fields as False by default, and hence the user won't have access to your admin interface
    • an existing admin will need to go in and enable these two fields, before the user can login and do anything
    • note that you can also set the is_staff and is_superadmin fields as part of the LDAP authentication if you have the necessary information in your LDAP server (see the dhang_auth_ldap docs for this), however, if you want to manage access to this django app from within the app itself, then you'll need to follow this two-step registration/authorization process
  • Also note the ldap.OPT_X_TLS_REQUIRE_CERT: False option, which basically bypasses validation of the SSL certificate file. You can use this to get around self-signed certificates, or certificates granted by a non-trusted or non-recognized authority. 

If you can't get any of these to work, you can try some lower-level manual debugging. Namely, create an empty python file and use the following code:

=====================
import ldap
l = ldap.initialize("ldaps://ldap.company.com:636")
out = l.simple_bind_s("uid=username,ou=people,o=company", "password")
print out
exit()
========================

Here username and password are equivalent to whatever someone would type in in the django admin login form. This basically attempts to bind to the LDAP server with the credentials of the user attempting to login. If this doesn't work, the code will print out error messages that should point you in the right directions.

Comments

  1. This bit: If using secure ldap (ldaps), you need to either set AUTH_LDAP_START_TLS = True or prefix your AUTH_LDAP_SERVER_URI with "ldaps://..."

    ... probably just saved me 8 hours of frustration. THANK YOU!

    ReplyDelete

Post a Comment

Popular posts from this blog

Wkhtmltopdf font and sizing issues

Import Google Contacts to Nokia PC Suite

Can't delete last blank page from Word