Flask_Login Documentation

Flask_Login provides user session management for Flask Application. It handles the common tasks of logging in, logging out, and remembering your("Remember Me") users’ sessions over extended periods of time. Python/Flask Accounts and Login runtime example:https://csp.nighthawkcodingsociety.com/crud/> THIS PROCEDURE NEEDS TO CHANGE to support working with 2 app, frontend/backend solutions. Review Geeks for Geeks, Second Article, and with Illustrations

MVC initialization init.py

LoginManager is a part of Flask and need to be configured after you establish 'app'. The below code needs to be adapted to JWT to work effectively with View that is run in a remote application.

In a nutshell, how you can we authentication a Flask app that has a server side manged View: Initialize Flask Use the Flask-Login library for session management

  • Use the built-in Flask utility for hashing passwords
    • Create Model
    • Use Flask-SQLAlchemy to create a User model. Add password encryption with werkzeug.security
    • Create Views
    • Create sign-up and login forms for the users to create accounts and log in
    • Add protected pages to the app for logged in users only
    • Control(check against DB):
    • Flash error messages back to users when something goes wrong (email exists when on sign-up page or incorrect email/pwd when on login page)
    • Use information from the user’s account to display on the profile page
    • Logout user
# __init__.py  
from flask import Flask  

# Setup of key Flask object (app)
app = Flask(__name__)

from flask_login import LoginManager

# The most important part of an application that uses Flask-Login is the LoginManager class.
# You should create one for your application like this:  
# Setup LoginManager object (app)
login_manager = LoginManager()

# The login manager contains the code that lets your application and Flask-Login work together, 
# such as how to load a user from an ID,  where to send users when they need to log in, and the like.  
# Once the actual application object has been created, you can configure it for login with:

login_manager.init_app(app)

MVC MODEL: model.py

Password encryption is implemented in model to protect user information.

# The class that you use to represent users needs to implement these properties and methods:  
# is_authenticated, is_active, is_anonymous, get_id()
# To make implementing a user class easier, you can inherit from UserMixin, which provides default implementations  
# for all of these properties and methods.

from flask_login import UserMixin
# Users DB is a collection Data Structure
class Users(UserMixin, db.Model):
    # define the Users schema
    userID = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255), unique=False, nullable=False)
    email = db.Column(db.String(255), unique=True, nullable=False)
    password = db.Column(db.String(255), unique=False, nullable=False)
    phone = db.Column(db.String(255), unique=False, nullable=False)

    # constructor of a User object, initializes instance variables within object
    def __init__(self, name, email, password, phone):
        self.name = name
        self.email = email
        self.set_password(password) #encrypt password
        self.phone = phone

# required for login_user, overrides id (login_user default) to implemented userID
# The method get_id() must return a str that uniquely identifies this user, and can be used to load the user  
# from the user_loader callback.
def get_id(self):
    return self.userID

## Hashing Passwords With Werkzeug(German word meaning Tools)

'''
Any time you want to login to a website/app, you need a password. 
It's very important not to store passwords as-is in a database, instead you need to do something called hashing which converts the password into a hash which is a big long string of text and characters.
Hashing is the process of scrambling raw information to the extent that it cannot reproduce it back to its original form. It takes a piece of information and passes it through a function that performs mathematical operations on the plaintext. 
This function is called the hash function, and the output is called the hash value/digest. Hashing is nearly impossible to revert, so if hackers get a hold of a database with hashed passwords, hash decoding is a futile task.  
<img width="520" alt="image" src="https://user-images.githubusercontent.com/88572244/161413976-32b5db62-56f4-4453-99c6-658bfbb995d1.png">

The Secure Hash Algorithm(SHA) with a digest size of 256 bits, or the SHA 256 algorithm, is one of the most widely used hash algorithms. While there are other variants, SHA 256 has been at the forefront of real-world applications.
Werkzeug is a comprehensive WSGI web application library. It is a WSGI toolkit that implements requests, response objects, and utility functions. 
This enables a web frame to be built on it. The Flask framework uses Werkzeug as one of its bases.**

werkzeug.security methods generate_password_hash to create a hashed/encrypted password and, check_password_hash to check the hashed password
'''

from werkzeug.security import generate_password_hash, check_password_hash

 # set password method is used to create encrypted password
def set_password(self, password):
    """Create hashed password."""
    * Procedural Abstraction
    self.password = generate_password_hash(password, method='sha256')

# check password to check versus encrypted password
def is_password_match(self, password):
    """Check hashed password."""
    result = check_password_hash(self.password, password)
    return result

MVC - VIEW

Password management has two parts.

<!---
# authorize.html
# The conventional HTML Sign-up screen 
-->

<div class="container bg-secondary py-4">
    <div class="p-5 mb-4 bg-light text-dark rounded-3">
        <form method="POST" ID="authUser" action={{url_for('crud.crud_authorize')}} >   <!--- url_for is specific to servers side -->
            <table>
                <tr><th><label for="user_name">Username</label></th></tr>
                <tr><td><input type="text" id='user_name' name="user_name" size="20" required></td></tr>
                <tr><th><label for="email">Email</label></th></tr>
                <tr><td><input type="email" id="email" name="email" size="20" required></td></tr>
                Hack #1 Add Phone Number to Sign-Up screen  
                <tr><th><label for="password1">Password</label></th></tr>
                <tr><td><input type="password" id='password1' name="password1" size="20" required></td></tr>
                <tr><th><label for="password2">Password Confirmation</label></th></tr>
                <tr><td><input type="password" id='password2' name="password2" size="20" required></td></tr>
                <tr><th><input type="submit" value="Submit"></th></tr>
                <tr><td><a href={{url_for('crud.crud_login')}}>Log In</a></td></tr>    <!--- url_for is specific to servers side -->
            </table>
        </form>
    </div>
</div>

MVC - CONTROL, View Driver app_crud.py

  • Note. a function that needs a login will required @login_required annotation
flask_login.login_required
# If you decorate a view(route) with this, it will ensure that the current user is logged in and authenticated before calling the actual view. 
# (If they are not, it calls the LoginManager.unauthorized callback.). 
# Use this example for Hack #3.
@app_crud.route('/')
@login_required  # Flask-Login uses this decorator to restrict access to logged in users
def crud():
    """obtains all Users from table and loads Admin Form"""
    return render_template("crud.html", table=users_all())

# Unauthorized users do not get access to the SQL CRUD
# Flask-Login directs unauthorized users to this unauthorized_handler
@login_manager.unauthorized_handler
def unauthorized():
    """Redirect unauthorized users to Login page."""
    return redirect(url_for('crud.crud_login'))

MVC - CONTROL query.py

from __init__ import login_manager, db
from cruddy.model import Users
from flask_login import current_user, login_user, logout_user

# login user based off of email and password
def login(email, password):
    # sequence of checks
    if current_user.is_authenticated:  # return true if user is currently logged in
        return True
    elif is_user(email, password):  # return true if email and password match
        user_row = user_by_email(email)
        login_user(user_row)  # sets flask login_user
        return True
    else:  # default condition is any failure
        return False


# this function is needed for Flask-Login to work.
# User_loader callback. This callback is used to reload the user object from the user ID stored in the session.   
# It should take the str ID of a user, and return the corresponding user object.  
# It should return None (not raise an exception) if the ID is not valid. 

@login_manager.user_loader
def user_loader(user_id):
    """Check if user login status on each page protected by @login_required."""
    if user_id is not None:
        return Users.query.get(user_id)
    return None


# Authorize new user requires user_name, email, password
def authorize(name, email, password):
    if is_user(email, password):
        return False   #email already exist in DB
    else:
        # auth_user is an object of class Users
        auth_user = Users(
            name=name,
            email=email,
            password=password,
            phone="1234567890"  # this should be added to authorize.html Hack #1
        )
        # Password is encrypted in the init method of the class with self.set_password(password)
        # Add it to the auth_user object
        auth_user.create()
        return True


# logout user
Hack #2 Add logout to CRUD screen  
def logout():
    logout_user()  # removes login state of user from session

Hacks:

Hack #1 Add DOB to Sign Up screen
Hack #2 Add logout to Menu Bar. Display logged in User or Logout on the menu bar
Hack #3 Add login_required to portion(s) of project

Common login features.

  • Validate email/password on login page
  • Validate email, check for duplicate email on sign up page
  • Add lock symbol to menu items when running as anonymous