This is the Part-2 of the Flask Series. I have explained What is Rest API and Implementation in Flask. 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.
-
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.
- Now we create Login and Registration Forms using
wtforms
. - create
forms.py
file inside the directory.
# 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 usedEqualTo
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.
# 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
.
@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.
# 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.
# 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
.
@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 theFlask-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.
<!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 ourtemplates
directory and createformhelpers.html
inside theincludes
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 theformhelpers.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
andregister.html
insidetemplates
directory.put below code inside the
index.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.
- Now, create
login.html
file.
{% 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.
- Now, Create
register.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 %}
- Now, let's create simple navigation bar for out project by applying css to the class.
- Create
static
director insideFlask-Login-Register
and createmain.css
file. Ourmain.css
file will look like below.
/* 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.
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.
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.
Top comments (6)
Hey, One Tip: you can give code language in markdown when you are adding code in your post, it will render much cleaner. Check the markdown guide, you'll understand :)
Like below:
Okay, Thank you sir👍🏻
Haha, please no "sir" :)
I have updated it, look at now. Thank you for your reference 👍
Hey a small comment since I just fixed a CSRF issue related to /logout in a Flask web app. Please do not advise to do it with GET method, as that means that if any service has a JS or e.g. an IMG with src that forces you to go to /logout, then any service can force your users to logout at will. The best way is to use a POST request with CSRF token to check that a logout request is made by the service in question.
hello, how can i fix this plz? TypeError: 'Markup' object is not callable