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:
You see the snippet from the _init_.py file below:
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:
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.
runserver.py:
the index.html:
denied.html
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.
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
Post a Comment