SSO against Windows AD of a Flask app running on Apache

Hi Guys,

I just wanted to write down my experience of the last days (2017 December) about enabling a Single-Sign-On (SSO )against Windows AD in a Flask Application (Python Web-Framework) running on Apache in an  Intranet corporate Windows environment. 
where I say my approach will go just as well with Django or any the other Python web framework/application.

Motivation:
a Flask App will be  published onto an Apache Web Server, set up on a Windows Server in a corporate environment. It is going to be run exclusively in corporate Intranet. Since the authentication is provided already through Windows AD across the corporate, it should enable the SSO of the application avoiding a time-consuming secondary authentication.

Environment:

Server: Windows Server 2008 R2 Standard, 64 bit (with Internet access)
Web Server: Apache 2.4.23, 32 bit
Python 3.4.4, 32 bit
WSGI server for running Flask on Apache: mod_wsgi 

Approach:
the main idea is; getting the windows user name through Apache and look up and double check it in the AD.

I used the mod_authn_ntlm Apache module, which enables you to read the current  user name in the Apache Server. than you can access this user name in the Flask Application and authenticate it against the AD.

in order to get the user name I have installed the mod_authn_ntlm and followed the instructions.there. I have added "NTLMOmitDomain On" since I do not need the domain name (snippet below is out of the httpd.config file).

<Location / >
#AllowOverride None
AuthName "Private location"
AuthType SSPI
NTLMAuth On
NTLMAuthoritative On
NTLMOmitDomain On
Require valid-user
# use this to add the authenticated username to you header
# so any backend system can fetch the current user
# rewrite_module needs to be loaded then
# while X_ISRW_PROXY_AUTH_USER is your header name
RequestHeader set X_ISRW_PROXY_AUTH_USER expr=%{REMOTE_USER}

</Location>


Now you can go to your Flask app and do the following:

  • pip install ldap3 and and other missing modules
  • connect to ypur Active Directory / LDAP. 

You see the snippet from the _init_.py file below:


import ldap3

s = ldap3.Server('EXAMPLE.COM')
user="CN=AD_test_user,OU=France,OU=EMEA,OU=YYY,OU=XXX,DC=example,DC=com"ldap_conn = ldap3.Connection(s,user,password='xxx')

if not ldap_conn.bind():
    print('error in bind', ldap_conn.result)

from app import views

Go back to the flask, the user name will be available there, but after defining the authentication function under "app" folder. isUserEligible function looks up the username in the AD/LDAP, and returns 1 if only there is one treffer.  The authenticate.py looks like:

import ldap3

def isUserEligible(user, conn):
    filter_string = '(&(cn=%s)(objectCategory=person))' % (user)
    #print(filter_string)
    conn.search("DC=example,DC=com",search_scope = ldap3.SUBTREE,attributes = [ldap3.ALL_ATTRIBUTES],search_filter =filter_string)
    print('Länge: ' ,len(conn.entries))
    if(len(conn.entries)==1):
        return 1    return 0

Finally the snippet from the views.py. If the the SSO succeeds, the app will be redirected to the index.html with the username as parameter.

from flask import request
from app import ldap_conn
from app import authenticate as aut
import ldap3

@app.route('/')
@app.route('/index')
def index():
    username = request.environ.get('REMOTE_USER')
    if username is None:
        username = 'Guest User'    print("username: ", username)
    if(aut.isUserEligible(username,ldap_conn)):
        return render_template('index.html', user=username)
    return render_template('denied.html')

runserver.py:


from app import app

if __name__ == '__main__':
    app.run()

the index.html:

{% extends "base.html" %}
{% block content %}

    <h4> Good Day  {{ user }}!! </h4>

{% endblock %}

denied.html


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
Access Denied 403
</body>
</html>

The file structure looks like:
<app>
---<templates>
------base.html
------index.html
------denied.html
---views.py
---authenticate.py
---runserver.py


A last note about browser compatibility: Even if you have a flawless application, you still  have to take  care of your site registration as trusted site for every browser type.

Comments