Flask Login-Register-Logout Implementation
Tags: python, security, html, sql
Published: 2020-07-25 17:04:07

* This is the Part-2 of the Flask Series. I have explained [What is Rest API and Implementation in Flask](https://medium.com/@dpdhruvprajapati/constructing-restful-crud-apis-using-flask-and-sqlalchemy-with-understandings-of-http-methods-part-237058fe9443). Please refer that article first for better understanding of rest apis.

* Today we are going to implement Login-Register Flow in Flask.So without any further let's jump into it. You can check whole code on [Github Repository](https://github.com/imdhruv99/Flask-LogIn-Register). 

* First of all, we will  install dependencies which we are going to use are listed below.
> Flask = Building Web Application
> Flask-SQLAlchemy =  Provide support for SQLAlchemy to your application
> Flask-WTF = To Create Interactive User Interface
> SQLAlchemy = Provide ORM for our Application
> Werkzeug = WSGI Web Application Library, it will provide security to encrypt password and matching Password
> WTForms = Form Validation and Generation Library

* To install dependencies in your project use pip command.
```pip install <library-name>```

* Now Create directory named as `Flask-Login-Register`. our project structure is like below image.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/2t04p7m3qqvc5clrya02.PNG)

* Now we create Login and Registration Forms using `wtforms`.
* create `forms.py` file inside the directory. 
```python
# Importing Require Module

from wtforms import Form, BooleanField, StringField, PasswordField, validators, TextAreaField, IntegerField

from wtforms.validators import DataRequired

# Creating Login Form contains email and password
class LoginForm(Form):

    email = StringField("Email", validators=[validators.Length(min=7, max=50), validators.DataRequired(message="Please Fill This Field")])

    password = PasswordField("Password", validators=[validators.DataRequired(message="Please Fill This Field")])

# Creating Registration Form contains username, name, email, password and confirm password.

class RegisterForm(Form):
    
    name = StringField("Ad", validators=[validators.Length(min=3, max=25), validators.DataRequired(message="Please Fill This Field")])
    
    username = StringField("Username", validators=[validators.Length(min=3, max=25), validators.DataRequired(message="Please Fill This Field")])
    
    email = StringField("Email", validators=[validators.Email(message="Please enter a valid email address")])
    
    password = PasswordField("Password", validators=[
    
        validators.DataRequired(message="Please Fill This Field"),
    
        validators.EqualTo(fieldname="confirm", message="Your Passwords Do Not Match")
    ])
    
    confirm = PasswordField("Confirm Password", validators=[validators.DataRequired(message="Please Fill This Field")])
```
* This file will create Forms when we will use Jinja2 Template to create our templates. Here, `DataRequired` function will throw an error if field is empty.

* For password and confirm password we have used `PasswordField` function and to check that password and confirm password is same we have used `EqualTo` function.

* Let's, generate our User model and API Endpoints. For the simplicity purpose i will write whole code in one file.

* Create `app.py` under the directory and place below code inside the file.

```python
# Importing require libraries
from flask import Flask, render_template, flash, redirect, request, session, logging, url_for

from flask_sqlalchemy import SQLAlchemy

from forms import LoginForm, RegisterForm

from werkzeug.security import generate_password_hash, check_password_hash

# Now create flask application object

app = Flask(__name__)

# Database Configuration and Creating object of SQLAlchemy

app.config['SECRET_KEY'] = '!9m@S-dThyIlW[pHQbN^'

app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:root@localhost/auth'

app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)

# Create User Model which contains id [Auto Generated], name, username, email and password

class User(db.Model):

    __tablename__ = 'usertable'

    id = db.Column(db.Integer, primary_key=True)

    name= db.Column(db.String(15), unique=True)

    username = db.Column(db.String(15), unique=True)

    email = db.Column(db.String(50), unique=True)

    password = db.Column(db.String(256), unique=True)

```
* Now create our first route, which will render the home page of our flask web application. put below code inside the `app.py`.

```python
@app.route('/')
def home():

    return render_template('index.html')

```
* It's time to create actual functionality for our web application. In our case it is endpoint for login and register.

* put below code inside `app.py`. it will provide use User Registration Functionality.

```python
# User Registration Api End Point
@app.route('/register/', methods = ['GET', 'POST'])
def register():
    # Creating RegistrationForm class object
    form = RegisterForm(request.form)

    # Cheking that method is post and form is valid or not.
    if request.method == 'POST' and form.validate():

        # if all is fine, generate hashed password
        hashed_password = generate_password_hash(form.password.data, method='sha256')
        
        # create new user model object
        new_user = User(
            
            name = form.name.data, 
            
            username = form.username.data, 
            
            email = form.email.data, 
            
            password = hashed_password )
        
        # saving user object into data base with hashed password
        db.session.add(new_user)
    
        db.session.commit()
    
        flash('You have successfully registered', 'success')
        
        # if registration successful, then redirecting to login Api
        return redirect(url_for('login'))
    
    else:
        
        # if method is Get, than render registration form
        return render_template('register.html', form = form)
```

* Above API endpoint will check that is http request method is get, than render registration form but if the http request method is post, than it will check that form is valid or not, if form is valid, it will generate hash password from password and create new user object with that password and then it will save that object inside the Database.

* Here, one thing to notice that, we do not need to check that password and confirm password is same or not by making login, we have checked that in our registration form generation class.

* Now, Let's create our Login and Logout APIs. Put below code inside the `app.py` file.

```python
# Login API endpoint implementation
@app.route('/login/', methods = ['GET', 'POST'])
def login():
    # Creating Login form object
    form = LoginForm(request.form)
    # verifying that method is post and form is valid
    if request.method == 'POST' and form.validate:
        # checking that user is exist or not by email
        user = User.query.filter_by(email = form.email.data).first()

        if user:
            # if user exist in database than we will compare our database hased password and password come from login form 
            if check_password_hash(user.password, form.password.data):
                # if password is matched, allow user to access and save email and username inside the session
                flash('You have successfully logged in.', "success")
                
                session['logged_in'] = True

                session['email'] = user.email 

                session['username'] = user.username
                # After successful login, redirecting to home page
                return redirect(url_for('home'))

            else:
                
                # if password is in correct , redirect to login page
                flash('Username or Password Incorrect', "Danger")

                return redirect(url_for('login'))
    # rendering login page
    return render_template('login.html', form = form)

```
* Here, if method is post and form is valid, than it will search user in Database using user's email. if user exist than it will compare the hashed password which is stored inside the database and simple password which is entered by user.

* If both password matched then allow user to access and redirect user to `home` while saving username and email inside the session.

* Let's write our last backend logic for Logout API and Running Server. Write below code inside the `app.py`.

```python
@app.route('/logout/')
def logout():
    # Removing data from session by setting logged_flag to False.
    session['logged_in'] = False
    # redirecting to home page
    return redirect(url_for('home'))

if __name__ == '__main__':
    # Creating database tables
    db.create_all()
    # running server
    app.run(debug=True)
```

* Now, let's make our frontend templates and static.

* Create `templates` directory inside the `Flask-Login-Register` directory.

* Let's create our layout file named as `base.html` inside the templates directory. It will provide basic layout for our front end.

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
    <link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
    <link rel="stylesheet" href="../static/main.css">
    <title>Flask</title>
</head>
<body>

    <div class="container">

        {% block body %}

        {% endblock body %}
    </div>
    
    <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
    <hr>
</body>
</html>
```
* Create `includes` directory inside our `templates` directory and create `formhelpers.html` inside the `includes` directory.

* `formhelpers.html` uses jinja2 macros functionality, through this we can dynamically render our error & success messages and html fields. write below html code inside the `formhelpers.html`.

```html
{% macro render_field(field) %}
  <dt>{{ field.label }}
  <dd>{{ field(**kwargs)|safe }}
  {% if field.errors %}
    <ul class=errors>
    {% for error in field.errors %}
      <li>{{ error }}</li>
    {% endfor %}
    </ul>
  {% endif %}
  </dd>
{% endmacro %}
```

* Now, create our `index.html`, `login.html` and `register.html` inside `templates` directory.

* put below code inside the `index.html`.
```html
{% extends "base.html" %}

{% block body %}

<div class="topnav">
    {% if session.logged_in %}
		<a href="/logout/">Logout</a>
	{% else %}
		<a href="/login/">Login</a>
		<a href="/register/">Sign up</a>
	{% endif %}
</div>
<hr>

{% if session['logged_in'] %}
    <h5>Welcome, {{session['username'] }}</h5>
{% endif %}

{% endblock body %}

```
* In above code, it will check for `logged_in` flag. it it is true than it will show logout on navbar and if it is false than it will show login and signup on navbar.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/xxaol7le2vw62utp1994.PNG)

* Now, create`login.html` file.
```html
{% extends "base.html" %}

{% block body %}

{% from "includes/formhelpers.html" import render_field %}

<h4>
    <strong>
        Login
    </strong>
</h4>
<hr>

<form method="POST">

    {{ render_field(form.email, class="form-control") }}
    {{ render_field(form.password, class="form-control") }}

    <button type="submit" class="btn btn-primary">Login</button>
</form>

<hr>

<a href="/register">Create a New Account!</a>

{% endblock body %}
```

* Here, render field will render email and password by using jinja2 macro functionality.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/y0onnk50vuxepb6wpdur.PNG)

* Now, Create `register.html`.
```html
{% extends "base.html" %}

{% block body %}

{% from "includes/formhelpers.html" import render_field %}

<h4>
    <strong>
        Register
    </strong>
</h4>
<hr>

<form method="POST">

    {{ render_field(form.name, class="form-control") }}
    {{ render_field(form.username, class="form-control") }}
    {{ render_field(form.email, class="form-control") }}
    {{ render_field(form.password, class="form-control") }}
    {{ render_field(form.confirm, class="form-control") }}

    <button type="submit" class="btn btn-primary">Register</button>
</form>

<hr>

<a href="/login">Login into existing account!</a>

{% endblock body %}
```
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/komvah4iot5mogkq558q.PNG)

* Now, let's create simple navigation bar for out project by applying css to the class.
* Create `static` director inside `Flask-Login-Register` and create `main.css` file. Our `main.css` file will look like below.

```css
/* Add a black background color to the top navigation */
.topnav {
    background-color: #172944;
    overflow: hidden;
}

  /* Style the links inside the navigation bar */
.topnav a {
    float: left;
    color: #f2f2f2;
    text-align: center;
    padding: 14px 16px;
    text-decoration: none;
    font-size: 17px;
}

  /* Change the color of links on hover */
.topnav a:hover {
    background-color: #ddd;
    color: black;
}

  /* Add a color to the active/current link */
.topnav a.active {
    background-color: #4CAF50;
    color: white;
}
```

* Here. our login-registration flow is completed. 

* Let's run our project.

* To run project in windows use below command inside the `Flask-Login-Register` path of command prompt.
```bat
set FLASK_APP=app.py

set FLASK_DEBUG=1

flask run --port=8080 
```
* To run project in MacOs/Linux use below command inside the `Flask-Login-Register` path of terminal.

```sh
export FLASK_APP=app.py

export FLASK_DEBUG=1

flask run --port=8080 
```
* Here, `--port` is optional flag.

* In next post i will explain what is jwt and implement jwt with flask.

##### Thank you for Reading. Give like and follow me for more amazing tutorials.