This content originally appeared on Envato Tuts+ Tutorials and was authored by Shalabh Aggarwal
Flask is a Python-based micro web framework which allows you to write your web applications quickly and efficiently. By micro, it doesn't mean that Flask lacks in functionality. It simply refers to the fact that Flask has kept its core small and highly extensible.
LDAP (Lightweight Directory Access Protocol) can have different meanings for different people depending upon their usage. It is an Internet Protocol for looking up contact information about users, information about certificates, network pointers, etc., from a server where the data is stored in a directory style structure. Its most popular use is to provide a "single sign on" where a user can access multiple applications by logging in once as the password would be shared across services.
In this tutorial, I will take you through how to implement authentication of users in your Flask application using LDAP. To demonstrate this, I will create a small application with a home page and a login page. The user would need to enter the login details on the login page. If the credentials entered by the user are successfully authenticated on the provided LDAP server, the user will be logged in. If not, the user will be shown an appropriate message.
I assume that you have a basic understanding of Flask, LDAP, Flask-Login extension and environment setup best practices using virtualenv to be followed while developing a Python application.
LDAP Server
To use LDAP, you will need a server; OpenLDAP is an open-source implementation of LDAP. Other alternatives are the Microsoft Azure Active Directory, a premium service. Since we only need a test server, there will be no need to set up a local server. We will use a publicly available LDAP testing server from Forum Systems.
Installing Dependencies
The following packages need to installed for the application that we'll be developing.
$ pip install ldap3 $ pip install Flask-WTF flask-sqlalchemy Flask-Login
The above commands should install all the required packages that are needed for this application to work.
Application Structure
First the application needs to be structured in a way that it is easy to understand.
flask_app/ my_app/ - __init__.py auth/ - __init__.py - models.py - views.py static/ - css/ - js/ templates/ - base.html - home.html - login.html - run.py
All the files will be discussed below. The static
folder contains the standard Bootstrap CSS and JS files.
The Application Itself
First, the configuration file needs to be written:
flask_app/my_app/__init__.py
from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_login import LoginManager app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db' app.config['WTF_CSRF_SECRET_KEY'] = 'random key for form' app.config['LDAP_PROVIDER_URL'] = 'ldap://ldap.forumsys.com:389/' app.config['LDAP_PROTOCOL_VERSION'] = 3 db = SQLAlchemy(app) app.secret_key = 'randon_key' login_manager = LoginManager() login_manager.init_app(app) login_manager.login_view = 'auth.login' ctx = app.test_request_context() ctx.push() from my_app.auth.views import auth app.register_blueprint(auth) db.create_all()
In the file above, the application has been configured with different options as needed by the extensions as well as by the LDAP configuration. This is followed by the initialisation of the extensions and finally creation of the database.
The last statement creates a new database at the location provided against SQLALCHEMY_DATABASE_URI
if a database does not already exist at that location, otherwise it loads the application with the same database.
flask_app/my_app/auth/models.py
import ldap3 from flask_wtf import Form from wtforms import StringField,PasswordField from wtforms import validators from my_app import db, app def get_ldap_connection(): conn = ldap3.initialize(app.config['LDAP_PROVIDER_URL']) return conn class User(db.Model): id = db.Column(db.Integer, primary_key = True) username = db.Column(db.String(100)) def __init__(self, username, password): self.username = username @staticmethod def try_login(username, password): conn = get_ldap_connection() conn.simple_bind_s( 'cn=%s,ou=mathematicians,dc=example,dc=com' % username,password ) def is_authenticated(self): return True def is_active(self): return True def is_anonymous(self): return False def get_id(self): return self.id class LoginForm(Form): username = StringField('Username',[validators.DataRequired()]) password = PasswordField('Password',[validators.DataRequired()])
The file above starts with the creation of a User
model which contains just a username
field for demonstration purpose. You can add as many fields as needed according to the context of the application.
The methods is_authenticated()
, is_active()
, is_anonymous()
and get_id()
are needed by the Flask-Login extension. The try_login()
method does the actual authentication process by first creating a connection with the LDAP server and then using the username and password to log in the server by creating a simple bind.
The first argument taken by the simple_bind_s()
method is the DN, which is provided by the LDAP server and varies with the LDAP record configuration. The form handling is taken care of by the LoginForm
, which extends the Form
class provided by Flask-WTForms.
flask_app/my_app/auth/views.py
import ldap3 from flask import (request,render_template,flash, redirect, url_for, Blueprint,g ) from flask_login import (current_user,login_user,logout_user,login_required) from my_app import login_manager,db auth = Blueprint('auth', __name__) from my_app.auth.models import User,LoginForm @login_manager.user_loader def load_user(id): return User.query.get(int(id)) @auth.before_request def get_current_user(): g.user = current_user @auth.route('/') @auth.route('/home') def home(): return render_template('home.html') @auth.route('/login', methods = ['GET','POST']) def login(): if current_user.is_authenticated: flash('You are already logged in.') return redirect(url_for('auth.home')) form = LoginForm(request.form) if request.method == 'POST' and form.validate(): username = request.form['username'] password = request.form['password'] try: User.try_login(username,password) except: flash( 'Invalid username or password. Please try again.', 'danger') return render_template('login.html', form=form) user = User.query.filter_by(username = username) if not user: user = User(username = username, password = password) db.session.add(user) db.commit() login_user(user) flash('You have successfully logged in.', 'success') return redirect(url_for('auth.home')) if form.errors: flash(form.errors, 'danger') return render_template('login.html', form=form) @auth.route('/logout') @login_required def logout(): logout_user() return redirect(url_for('auth.home'))
In the above file, the methods load_user()
and get_current_user()
are needed by Flask-Login extension. Next are the handlers for our application, which are decorated by their respective routes.
home()
just renders the home page for the user. The content of the home page is determined by the template flask_app/my_app/templates/home.html
, which we'll discuss shortly.
The handler of primary interest is login()
as it handles the complete login process. If a logged in user tries to access this page, the page will automatically redirect to the home page. Otherwise, the login process will begin where the LDAP username and password of the user is taken as form input from flask_app/my_app/templates/login.html
.
Using these credentials, the application tries to authenticate the user from the LDAP server provided in the configuration we saw earlier. If the user is authenticated, the application creates a new record for the user if a first time user is accessing the application, otherwise it just logs the user in with the existing record of the user.
Flash messages are shown to the user as and when required to keep the user engaged with the application.
logout()
hander simply clears the session of the currently logged in user as a result of which the user is logged out.
flask_app/my_app/templates/base.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Flask Authentication with LDAP Tutorial</title> <link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet"> <link href="{{ url_for('static', filename='css/main.css') }}" rel="stylesheet"> </head> <body> <div class="navbar navbar-inverse navbar-fixed-top" role="navigation"> <div class="container"> <div class="navbar-header"> <a class="navbar-brand" href="{{ url_for('auth.home') }}">Flask LDAP Demo</a> </div> </div> </div> <div class="container"> <br/> <div> {% for category, message in get_flashed_messages(with_categories=true) %} <div class="alert alert-{{category}} alert-dismissable"> <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button> {{ message }} </div> {% endfor %} </div> {% block container %}{% endblock %} </div> <!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script> <script src="{{ url_for('static', filename='js/bootstrap.min.js') }}"></script> {% block scripts %} {% endblock %} </body> </html>
Above is the base file which contains the header, footer and other basic components which remain common throughout the application. This helps in keeping the templates very modular and easy to understand as each template only contains the code relevant to its handler and functionality.
Even though all the common components are defined here, I have added an empty block for scripts
which can be extended in any template which inherits base.html
. Notice how the flashing of messages is being done above and how the Bootstrap CSS classes are dynamically being played with to make the flash message alert box appropriately styled.
The container
block will be extended by the rest of the templates to add their respective contents.
flask_app/my_app/templates/home.html
{% extends 'base.html' %} {% block container %} <h1>Welcome to the Flask-LDAP Authentication Demo</h1> {% if current_user.is_authenticated %} <h3>Hey {{ current_user.username }}!!</h3> <a href="{{ url_for('auth.logout') }}">Click here to logout</a> {% else %} Click here to <a href="{{ url_for('auth.login') }}">login with LDAP</a> {% endif %} {% endblock %}
Notice how the base template has been extended and content for the home page has been added inside the block container.
If the user is logged in, the user is greeted with the username and shown a message to log out. Otherwise, the user is shown a message to log in with the link to login page.
flask_app/my_app/templates/login.html
{% extends 'home.html' %} {% block container %} <div class="top-pad"> <form method="POST" action="{{ url_for('auth.login') }}" role="form"> {{ form.csrf_token }} <div class="form-group">{{ form.username.label }}: {{ form.username() }}</div> <div class="form-group">{{ form.password.label }}: {{ form.password() }}</div> <button type="submit" class="btn btn-default">Submit</button> </form> </div> {% endblock %}
The login template simply contains a form with fields for username
and password
.
Running the Application
To run the application, execute the script run.py
. The contents of this script are:
from my_app import app app.run(debug=True)
Now just execute from the command line:
python run.py
Finally open up http://127.0.0.1:5000/ in your preferred browser and see your application in action. Everything should be self-explanatory. In order to log in, you can use the following users:
- riemann
- gauss
- euler
- euclid
All user passwords are password.
Conclusion
Over the course of this tutorial, we built a small but effective web application using Flask with the help of the Flask-Login extension. This application simply takes a username and password and authenticates the user against the LDAP server provided. Use your imagination to tweak and extend the application as per your needs.
Resources
This content originally appeared on Envato Tuts+ Tutorials and was authored by Shalabh Aggarwal
Shalabh Aggarwal | Sciencx (2015-01-19T07:28:13+00:00) Flask Authentication With LDAP. Retrieved from https://www.scien.cx/2015/01/19/flask-authentication-with-ldap/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.