Flask Biography Tutorial Part VII : Adding Sign In Form Using Bootstrap 3 and Login Feature Using Flask-Login

 

Figure VII-1 Persuasive Signin Form in a Bootstrap 3 Navbar

In adding Sign In feature for our Bio Application, I have decided one important thing : the login status or form should always available and already opened in Navbar. Because Bootstrap 3 Navbar if properly setup will always available at the top of every page, this will result in a persuasive sign in form : a kind of form that engage user to directly fill its input fields and hit Submit button! Wherever (s)he is...

A. Designing Sign In Flow

There are lots of ways where you can design an application sign in flow for your next killer web application. Here, I am going to guide you on implementing our application sign in flow. 

A1. Building Persuasive Sign In Form

In our previous article talking about Bootstrap 3 form layout, I said that to include a form in a navbar we can utilize .form-inline. Turn out, Bootstrap 3 offer a much more robust approach by adding another mixin class .navbar-form. This allow a better implementation of form reside in a navbar. Have a look at our newly added sign in form code below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-header">
<a class="navbar-brand" href="/"><span>iography</span></a>
</div>
<div class="collapse navbar-collapse">
<form action="{{ url_for('signin')}}" class="navbar-form navbar-right" method="POST">
        {{ signin_form.hidden_tag() }}                
        <div class="form-group">
        {{ signin_form.username(class="form-control", placeholder="Username") }}
        </div>
        <div class="form-group">
        {{ signin_form.password(class="form-control", placeholder="Password") }}
        </div>
        <div class="checkbox">
        {{ signin_form.remember_me }} 
        {{ signin_form.remember_me.label(style="color:rgb(240,240,240);") }}
        </div>
        <div class="form-group">
        <button class="btn btn-default" type="submit">Sign In</button>
        </div>
</form>
</div>
</div>

The above navbar code will result in a Fixed Top Inverse Navbar as depicted in Figure VII-1. Observe also that for the symbol B in application brand, I use one of Bootstrap 3 glyphicon class. For the action method for the form we use url_for()  to render correct URL for signin route/view method. Below is our initial signin method.

1
2
3
@application.route('/signin', methods=['POST'])
def signin():
    pass

Try to test your application until this point!

A2. Handling Failed Login with A Full Page Sign In Form

Great. Now we can persuasively invite our visitor to sign in into our application. But what if the login process failed? Shall we display a modal dialog? A simple alert? Or.. what? Here, I opted to use Twitter way of doing it : present user with a full page sign in form.

Full page sign in form with error message

Figure VII-2 Full page sign in form with error message

After a failed login, things can become pretty serious for our users. (S)he must be inform on why the login error occured, how to overcome lost password or maybe an instruction to call local police. Who knows? That's why we present our users with a full page sign in form.

Also note that, in a full page sign in form we don't display our persuasive sign in form in the Navbar. Users may get confused seeing two identical form in the same page with the same functionality.

A3. Handling Successful Login with A Full Page Sign In Form

Shall we greet our user when they pass our secure sign in process? Give them an applause? Well, you can do anything you like, but here I simply change our Navbar to hide Sign In form and display our authenticated user's username, complete with drop down menu specific to them.

Authenticated user menu

Figure VII-3 Authenticated user menu

A signed user can access and modify his/her Bio Page, settings page and directly logout from our application. We use Bootstrap 3 Navbar feature intensively to create those functional drop down menu. 

B. Using Flask-Login Package

In the first iteration of signin method shown in section A1, we only allow POST request to call this method. Now, our next question is, "How do we implement signin/login feature in our Bio Application? Shall I code it from the beginning or .. simply import existing login libary?". Well, I am not quite sure about you, but if you asked me, I am going to simply import it. After all, this is the world of Python, right?

If previously you have write a website using PHP or any other web programming language, I am sure you know how to implement signin feature in a web application. It consist of:

  1. Querying user credential for the correct username and password
  2. Modify certain session variable as a mark that the rest of the request froma user will come from an authenticated user
  3. Securing certain application URL to be accessible only for authenticated user
  4. Modifying application user interface based on login status.

Implementing it from scratch may bring complexity that should always be separated into another package. In Python way of thinking, lets import an existing package. And, Flask-login sole existence is to solved just those problem. By using it, your application will be freed from all code complexities of implementing your own login mechanism.

 

Flask-login is highly adaptable to your application need, but it still needs code adjustment to add login capability to your application. There are several requirement before you can fully harness its power. We are going to add those requirements step by step in the following passages.

B1. Creation of LoginManager Instance

Let us start with import statements required in our existing main.py file, shown below: 

1
2
3
from flask.ext.login import LoginManager, login_user, logout_user 
from flask.ext.login import current_user, login_required
from flask import redirect, url_for, session

Flask-login first step is to instantiate an instance of class LoginManager.

1
2
3
login_manager = LoginManager()
login_manager.init_app(application)
login_manager.login_view = '/signin'

Every Flask application that needs Flask-login feature, will have to instantiate LoginManager class, and save its instance somewhere. Here, we save it in a variable named login_manager. As it needs an existing application object, we must add those lines of code after we initialize our application object. The important thing is to told Flask-login what route/view method that will responsible of implementing login/signin process. Here, we told Flask-login that our just created /signin route/view method will be the method that responsible for our login process. Pretty neat, right? 

B3. Modifying Users Class

Flask-login required you to modify class that hold your user model. We only needs to add four methods. Lets inspect our newly modified User class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Users(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(60), unique=True)
    firstname = db.Column(db.String(20))
    lastname = db.Column(db.String(20))
    password = db.Column(db.String)
    email = db.Column(db.String(100), unique=True)
    time_registered = db.Column(db.DateTime)
    tagline = db.Column(db.String(255))
    bio = db.Column(db.Text)
    avatar = db.Column(db.String(255))
    active = db.Column(db.Boolean)
def __init__(self, username = None, password = None, email = None, firstname = None, lastname = None, tagline = None, bio = None, avatar = None, active = None):
    self.username = username
    self.email = email
    self.firstname = firstname
    self.lastname = lastname
    self.password = password
    self.tagline = tagline
    self.bio = bio
    self.avatar = avatar
    self.active = active
def is_authenticated(self):
    return True
def is_active(self):
    return self.active
def is_anonymous(self):
    return False
def get_id(self):
    return unicode(self.id)

Do a comparison with your existing Users class. And you will know that we have added:

  1. active field, to mark whether this user is still active or not. The term active is highly depends on your application need.
  2. def is_authenticated,  a method that will always return True.
  3. def is_active,  return active field defined earlier.
  4. def is_anonymous,  return False, as our application didn't have concept of anonymous user.
  5. def get_id,  return unicode format of your user primary key field. Hence, we simply use unicode method to convert our existing primary key field to unicode.

That's all the modifications that we need in our Users

B4. Create user_loader Method

We already modified our Users class to conform to Flask-login requirement. But how do Flask-login knows how to query our authenticated user? We must implement method decorated as user_loader.

1
2
3
@login_manager.user_loader
def load_user(id):
    return Users.query.get(id)

Here, we simply use SQLAlchemy query method from our Users class to query our authenticated users. 

B5. Create SigninForm Class

With our previous article talking about how to use WTForms package using Flask-WTF, it's quite understandable that we also need a SigninForm class to hold our signin form input fields data and its validation. The class is shown below.

1
2
3
4
5
6
7
8
9
10
class SigninForm(Form):
    username = TextField('Username', validators=[
Required(),
    validators.Length(min=3, message=(u'Your username must be a minimum of 3'))
])
    password = PasswordField('Password', validators=[
Required(),
    validators.Length(min=6, message=(u'Please give a longer password'))
])
    remember_me = BooleanField('Remember me', default = False)

Pretty self explanatory, right? And this is a perfect example from what we already know, that it's not always our application models can be mapped directly to its web form. Although we know that a sign in form will eventually query our Users class, but of course what we need to match is just its username and password. Hence, we create a SignForm class to be use in form input fields generation and its validation process.

"What about that remember_me fields ?", you may asked. It's a built-in feature for Flask-login where your application can have, well, Remember Me feature. That is, upon opening our site, an existing user that already sign in previously will not have to repeat his/her signing process again. 

B6. Create Sign In Process

With sign in process flow defined in section A of this article, here is a complete def signin() method that implements it. I will guide you on how the process works!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@application.route('/signin', methods=['GET', 'POST'])
def signin():
    if request.method=='POST':
        if current_user is not None and current_user.is_authenticated():
            return redirect(url_for('index'))
        form = SigninForm(request.form)
        if form.validate():
            user = Users.query.filter_by(username = form.username.data).first()
            if user is None:
                form.username.errors.append('Username not found')
                return render_template('signinpage.html',  signinpage_form = form)
            if user.password != form.password.data:
                form.password.errors.append('Passwod did not match')
                return render_template('signinpage.html',  signinpage_form = form)
            login_user(user, remember = form.remember_me.data)
            session['signed'] = True
            session['username']= user.username
            if session.get('next'):                
                next_page = session.get('next') 
                session.pop('next')
                return redirect(next_page) 
            else:
                return redirect(url_for('index'))
        return render_template('signinpage.html',  signinpage_form = form)
    else:
        session['next'] = request.args.get('next')
        return render_template('signinpage.html', signinpage_form = SigninForm())

Try to understand it first! Except that you are a complete beginner in programming, I always confident that a Python code is self explanatory. But here goes on the explanation of the code above:

  1. We have change the decorator to let signin() method to handle request of GET method also. While the POST method (LINE 3 ~ 27) will handle user signing process by dealing with data POSTed from web from, the GET method sole responsibility is to a) save secure application URL (s)he is trying to access earlier and b) render full page sign in form (The last line) .
  2. The core login process occurred in LINE 18. But before that, in LINE 4-5, we first detect if user trying to access /signin method is actually an authenticated user (meaning, (s)he already signed to our application). Of course we won't display any sign in form there. We simply return user to homepage.
  3. Our next step is validating data posted from sign in form (LINE 7). And then we queried Users table based in its username data sent (LINE 8).
  4. LINE 10 ~ LINE 16 are codes that dealing with giving feedback to users why the sign in process failed. We simply give error message based on username and password mismatch. Other intelligent process that may worth added here is detecting how many sign in process failed which in turn may also deactivating sign in process for that user. Or, another reCaptcha field can be added to avoid spammer trying to sign in to our application. 
  5. After sign in process occurred in LINE  18, in LINE 19-20 we set certain session variables to let our application appearance and process customized for signed user.
  6. Another interesting part of sign in process occurred in LINE 21 ~ 16. I will explain it shortly after this, but this is the code that responsible in redirecting user to a protected page that (s)he is trying to access before signed to our application. Flask will append a next URL variable in /signin if our user is trying to access a protected page. For example I will show you how to protect /profile page that let our users customize his/her profile. If an authenticated user is trying to access /profile page, our signin url will be changed by flask into /signin?next=profile.

 

B7. Changing Application Interface for Authenticated User

After we have already setup certain session variables for authenticated user, we can use it to present our application interface specific to this authenticated user. For example, showing his/her username in navbar as depicted in Figure VII-3, along with specific dropdown menu for authenticated user. We can do this easily by using Jinja2 {% if %} keyword. Below is a snippet of base-layout.html file that responsible of displaying authenticated user dropdown menu.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{% if session['signed'] %}
<ul class="nav navbar-nav navbar-right">
            
        <li class="dropdown">                    
                <a class="dropdown-toggle" href="#">
                    <span></span>
                Signed in as {{ session['username'] }} <strong class="caret"></strong>
                </a>
                
        <ul class="dropdown-menu">
                            
                <li><a href="/{{session['username']}}">Your Bio Page</a></li>
                            
                <li><a href="http://www.pythonblogs.com/%7B%7B%20url_for(%27profile%27)%20%7D%7D">Settings..</a></li>
                            
                <li class="divider"></li>
                            
                <li><a href="http://www.pythonblogs.com/%7B%7B%20url_for(%27signout%27)%20%7D%7D">Logout</a></li>
                        
        </ul>
            </li>
</ul>

Observed that by using a simple {% if session['signed'] %} statement in our template, we can customize the appearance and functionality of our bio application. 

B8. Protecting Profile Page

Protected profile page

Figure VII-4 Protected profile page

Previously in section B6 I have explain that our signin method is capable of returning user to protected page that (s)he is trying to access before authenticating to our application. How do we implement this feature? Quite easy. Just add a login_required decorator in methods that you wish to protect from unauthenticated user.

1
2
3
4
@application.route('/profile')
@login_required
def profile():
    return render_template('profile.html', page_title='Customize your profile')

The above code shows you that our profile method will be protected from unauthenticated user. If those users trying to access it, Flask-Login will direct them to login_view method (which is signin() method) defined earlier in our application.config dict.


B9. Signing Out Users

If the core of signin process is the login_user() method, then the core of sign out process is the logout_user  method.

1
2
3
4
5
6
@application.route('/signout')
def signout():
    session.pop('signed')
    session.pop('username')
    logout_user()
    return redirect(url_for('index'))

A signout process core function is to remove all session variables related to authenticated user. Here, we only have two variables : signed and username. And, after logging out user with logout_user() method, we simply redirect him/her to our application home page. 

C. Conclusion

In context or our Bio Application tutorial series, this is the most complex topic I encounter so far. Although I have try to present this material as clear as possible, there are a lot of ways where this article may not quite easily understandable. If that is the case, feel free to ask question on the comment section.

As usual, you can download source code of the application here : bio-part-7.zip.

And our live Bio Application here : http://bio-ekowibowo.rhcloud.com. Remember, it will always be our latest version of Bio Application .

Stay tuned on our next article series!




Leave comments

authimage
  • Hi Abdula,

    Glad that series in the blog did give benefit to readers like you :)
    Currently I am still trying to spruce up the design a little bit, so you can easily navigate to the content of this blog.

    Yeah, I think I kinda skip a little explanation B symbol :)

    Thanks and enjoy!
    - Eko

    • eko
  • Hi,
    Thanks for the great series of tutorials. They're very useful and I enjoy them by doing practical things with python.

    In this part I think you forgot, at least I was confused about glyphicon class. You didn't include it in the example and 'B' character didn't appear until I realized that class isn't used.

    • Abdula

Copyright(c) 2014 - PythonBlogs.com
By using this website, you signify your acceptance of Terms and Conditions and Privacy Policy
All rights reserved