Difference between revisions of "Login and Logout in a Flask App"

From TRCCompSci - AQA Computer Science
Jump to: navigation, search
(Create a login view)
(Creating the database)
 
(21 intermediate revisions by the same user not shown)
Line 51: Line 51:
 
             {{ form.password(size=32) }}
 
             {{ form.password(size=32) }}
 
         </p>
 
         </p>
        <p>{{ form.remember_me() }} {{ form.remember_me.label }}</p>
 
 
         <p>{{ form.submit() }}</p>
 
         <p>{{ form.submit() }}</p>
 
     </form>
 
     </form>
Line 61: Line 60:
 
Now add the following to the top of your 'views.py' or the 'py' file containing your routes:
 
Now add the following to the top of your 'views.py' or the 'py' file containing your routes:
 
<syntaxhighlight lang=python>
 
<syntaxhighlight lang=python>
from app.forms import LoginForm
+
from .forms import LoginForm
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Line 71: Line 70:
 
     form = LoginForm()
 
     form = LoginForm()
 
     if form.validate_on_submit():
 
     if form.validate_on_submit():
         return redirect('/index')
+
        userpass = form.password.data
 +
        username = form.username.data
 +
         return redirect('/')
 
     return render_template('login.html', title='Sign In', form=form)
 
     return render_template('login.html', title='Sign In', form=form)
 
</syntaxhighlight>
 
</syntaxhighlight>
  
At the moment the form doesn't check the login details, for this we are going to need to create a database table for users. Several options exist for this connection and can be found here ([Connecting sqlite to Flask Web App| sqlite] , [Connecting MySQL to Flask Web App| MySql]
+
At the moment the form doesn't check the login details, for this we are going to need to create a database table for users. Several options exist for this connection and can be found here ([[Connecting sqlite to Flask Web App| sqlite]] , [[Connecting MySQL to Flask Web App| MySql]] ). Both of these methods create a database class so that we can keep the specific database implementation within the class. This will mean we can continue from this point regardless of which you have chosen.
 +
 
 +
=Creating the database=
 +
At this point you should have a database class which includes the code to connect, execute a command, and run a select query. This is for sqlite, and the data types will need to be changed for MySQL. I created the following route and then visited this route to run the code:
 +
 
 +
<syntaxhighlight lang=python>
 +
@app.route('/create')
 +
def createuserdb():
 +
    db = Database()
 +
    sql = """CREATE TABLE IF NOT EXISTS users(
 +
                UserID text PRIMARY KEY,
 +
                UserPass text NOT NULL,
 +
                UserEmail text,
 +
                UserDOB text,
 +
                UserMF text
 +
                )"""
 +
    check = db.execute(sql)
 +
    db=Database()
 +
    sql = """insert into users
 +
                values("bob","password")
 +
          """
 +
    check = db.execute(sql)
 +
    return "User table created:"+str(check)
 +
</syntaxhighlight>
 +
 
 +
=Check the password=
 +
Your current login route should be:
 +
 
 +
<syntaxhighlight lang=python>
 +
@app.route('/login', methods=['GET', 'POST'])
 +
def login():
 +
    form = LoginForm()
 +
    if form.validate_on_submit():
 +
        userpass = form.password.data
 +
        username = form.username.data
 +
        return redirect('/')
 +
    return render_template('login.html', title='Sign In', form=form)
 +
</syntaxhighlight>
 +
 
 +
So edit it to get the following:
 +
 
 +
<syntaxhighlight lang=python>
 +
@app.route('/login', methods=['GET', 'POST'])
 +
def login():
 +
    form = LoginForm()
 +
    if form.validate_on_submit():
 +
        userpass = form.password.data
 +
        username = form.username.data
 +
        db = Database()
 +
        sql ="select * from users where UserID='%s' and UserPass='%s'" % (username,userpass)
 +
        rows=db.select(sql)
 +
        if len(rows)==0:
 +
            return render_template('login.html', title='Sign In', form=form)
 +
        else:
 +
            return redirect('/')
 +
    return render_template('login.html', title='Sign In', form=form)
 +
</syntaxhighlight>
 +
 
 +
This creates a database connection and then runs the select query to check the username and password. if no rows are return (username and password combination not in database) you will be given the data and the form back. The else currently just redirects to the root page, we will make this store the logged in user. '%s' is the placeholder for a string, if it was a number you should use '%d'.
 +
 
 +
=Login the user=
 +
'flask_login' is essentially a module which is a session manager and contains methods to login, logout, and also for marking pages 'login_required'. If you created your Flask Web App by downloading the packages from this wiki, it will be already installed in your app. Add the following into your 'views.py':
 +
 
 +
<syntaxhighlight lang=python>
 +
from flask_login import LoginManager, UserMixin, login_required, login_user, logout_user
 +
 
 +
# flask-login
 +
login_manager = LoginManager()
 +
login_manager.init_app(app)
 +
login_manager.login_view = "login"
 +
 
 +
# user model
 +
class User(UserMixin):
 +
    def __init__(self, id):
 +
        self.id = id
 +
 
 +
# callback to reload the user object       
 +
@login_manager.user_loader
 +
def load_user(userid):
 +
    return User(userid)
 +
</syntaxhighlight>
 +
 
 +
This should be everything required to start using 'flask_login'. Now you can add '@login_required' after the '@app.route' to restrict access to this route to only those who have logged in.
 +
 
 +
In the 'login' route, add the following lines before it redirects to the root page:
 +
 
 +
<syntaxhighlight lang=python>
 +
user = User(username)
 +
login_user(user)
 +
</syntaxhighlight>
 +
 
 +
Now add the following route to allow the user to logout:
 +
 
 +
<syntaxhighlight lang=python>
 +
@app.route("/logout")
 +
@login_required
 +
def logout():
 +
    logout_user()
 +
    return redirect('/')
 +
</syntaxhighlight>
 +
 
 +
=Creating Links for Login & Logout=
 +
 
 +
You should now be able to create links to display 'login' if the user is not currently logged in, and 'logout' if they are currently logged in. Add this into your 'base' or 'layout' template:
 +
 
 +
<syntaxhighlight lang=html>
 +
        {% if current_user.is_anonymous %}
 +
        <a href="{{ url_for('login') }}">Login</a>
 +
        {% else %}
 +
        <a href="{{ url_for('logout') }}">Logout</a>
 +
        {% endif %}
 +
</syntaxhighlight>
 +
 
 +
=Setting a session timeout=
 +
You will need to add the following import line to use the 'timedelta' function:
 +
 
 +
<syntaxhighlight lang=python>
 +
from datetime import timedelta
 +
</syntaxhighlight>
 +
 
 +
Add the following settings to the existing 'login_manager' settings. We also need to declare a 'before_request' method:
 +
 
 +
<syntaxhighlight lang=python>
 +
login_manager.refresh_view = 'relogin'
 +
login_manager.needs_refresh_message = (u"Session timedout, please re-login")
 +
login_manager.needs_refresh_message_category = "info"
 +
 
 +
 
 +
@app.before_request
 +
def before_request():
 +
    session.permanent = True
 +
    app.permanent_session_lifetime = timedelta(minutes=15)
 +
</syntaxhighlight>
 +
 
 +
The time delta can be set to any value, you will be required to login again after this time period.

Latest revision as of 16:20, 2 November 2019

If you created your Flask Web App following the wiki tutorials, and you downloaded the zip file and extracted it into the 'site-packages' folder you will already have 'flask-wtf' installed and ready to go. If not then you need to install 'flask-wtf' using pip.

Setting Secret Key

You need to find the 'py' file which includes this code:

from flask import Flask
app = Flask(__name__)

And add the following to set the secret key:

app.config['SECRET_KEY'] = 'secret key'

Create forms.py

In the root folder of your project, create a new file called 'forms.py'.

Now add the following code:

from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired

class LoginForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired()])
    password = PasswordField('Password', validators=[DataRequired()])
    submit = SubmitField('Sign In')

This will import the required modules to create a login form. The LoginForm class contains the fields to display on the form. It its important to note the 'DataRequired' validators.

Create a template

My WebApp was created using Visual Studio, and it already created a 'layout' template and then separate 'html' files for each page. If you already have templates set up you should copy one of the 'html' files for a page and edit it to this:

{% extends "layout.html" %}

{% block content %}
    <h1>Sign In</h1>
    <form action="" method="post" novalidate>
        {{ form.hidden_tag() }}
        <p>
            {{ form.username.label }}<br>
            {{ form.username(size=32) }}
        </p>
        <p>
            {{ form.password.label }}<br>
            {{ form.password(size=32) }}
        </p>
        <p>{{ form.submit() }}</p>
    </form>
{% endblock %}

Create a login view

Now add the following to the top of your 'views.py' or the 'py' file containing your routes:

from .forms import LoginForm

Now create a route or view for the login:

@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        userpass = form.password.data
        username = form.username.data
        return redirect('/')
    return render_template('login.html', title='Sign In', form=form)

At the moment the form doesn't check the login details, for this we are going to need to create a database table for users. Several options exist for this connection and can be found here ( sqlite , MySql ). Both of these methods create a database class so that we can keep the specific database implementation within the class. This will mean we can continue from this point regardless of which you have chosen.

Creating the database

At this point you should have a database class which includes the code to connect, execute a command, and run a select query. This is for sqlite, and the data types will need to be changed for MySQL. I created the following route and then visited this route to run the code:

@app.route('/create')
def createuserdb():
    db = Database()
    sql = """CREATE TABLE IF NOT EXISTS users(
                UserID text PRIMARY KEY,
                UserPass text NOT NULL,
                UserEmail text,
                UserDOB text,
                UserMF text
                )"""
    check = db.execute(sql)
    db=Database()
    sql = """insert into users
                values("bob","password")
           """
    check = db.execute(sql)
    return "User table created:"+str(check)

Check the password

Your current login route should be:

@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        userpass = form.password.data
        username = form.username.data
        return redirect('/')
    return render_template('login.html', title='Sign In', form=form)

So edit it to get the following:

@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        userpass = form.password.data
        username = form.username.data
        db = Database()
        sql ="select * from users where UserID='%s' and UserPass='%s'" % (username,userpass)
        rows=db.select(sql)
        if len(rows)==0:
            return render_template('login.html', title='Sign In', form=form)
        else:
            return redirect('/')
    return render_template('login.html', title='Sign In', form=form)

This creates a database connection and then runs the select query to check the username and password. if no rows are return (username and password combination not in database) you will be given the data and the form back. The else currently just redirects to the root page, we will make this store the logged in user. '%s' is the placeholder for a string, if it was a number you should use '%d'.

Login the user

'flask_login' is essentially a module which is a session manager and contains methods to login, logout, and also for marking pages 'login_required'. If you created your Flask Web App by downloading the packages from this wiki, it will be already installed in your app. Add the following into your 'views.py':

from flask_login import LoginManager, UserMixin, login_required, login_user, logout_user 

# flask-login
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = "login"

# user model
class User(UserMixin):
    def __init__(self, id):
        self.id = id

# callback to reload the user object        
@login_manager.user_loader
def load_user(userid):
    return User(userid)

This should be everything required to start using 'flask_login'. Now you can add '@login_required' after the '@app.route' to restrict access to this route to only those who have logged in.

In the 'login' route, add the following lines before it redirects to the root page:

user = User(username)
login_user(user)

Now add the following route to allow the user to logout:

@app.route("/logout")
@login_required
def logout():
    logout_user()
    return redirect('/')

Creating Links for Login & Logout

You should now be able to create links to display 'login' if the user is not currently logged in, and 'logout' if they are currently logged in. Add this into your 'base' or 'layout' template:

        {% if current_user.is_anonymous %}
        <a href="{{ url_for('login') }}">Login</a>
        {% else %}
        <a href="{{ url_for('logout') }}">Logout</a>
        {% endif %}

Setting a session timeout

You will need to add the following import line to use the 'timedelta' function:

from datetime import timedelta

Add the following settings to the existing 'login_manager' settings. We also need to declare a 'before_request' method:

login_manager.refresh_view = 'relogin'
login_manager.needs_refresh_message = (u"Session timedout, please re-login")
login_manager.needs_refresh_message_category = "info"


@app.before_request
def before_request():
    session.permanent = True
    app.permanent_session_lifetime = timedelta(minutes=15)

The time delta can be set to any value, you will be required to login again after this time period.