Skip to content

03. Flask-Security

Next we will implement the authentication and authorization on our application using flask-security-too.

The reason why I am using flask security too is that, it will reduce half of our works in this application development. It will make our development easier, secure and realistic.

Installation

Flask-Security does provide all the modern web security mechanism but we will go with the basic feature of authentication and authorization.

Basic Flask-SQLAlchemy Application

To install flask-security we will use the following command: Read more... flask-security-too.readthedocs.io/en/stable/quickstart.html#basic-flask-sqlalchemy-application

pip install flask-security**fsqla,common**

This command will install flask security and its compatible sqlalchemy extension.

Database Setup

Now create a file with name database.py where we will create all the database object and tables.

database.py

from flask_sqlalchemy import SQLAlchemy
from flask_security.models import fsqla_v3 as fsqla
db = SQLAlchemy ``

# Define models
fsqla.FsModels.set_db_info `db`


class Product `db.Model`:
    id = db.Column `db.Integer, primary_key=True, autoincrement=True`
    name = db.Column `db.String `100`, nullable=False`
    img_name = db.Column `db.String `100`, nullable=False`
    mgf_date = db.Column `db.String `100`, nullable=False`
    exp_date = db.Column `db.String `100`, nullable=False`
    description = db.Column `db.Text, nullable=True`
    price = db.Column `db.Float, nullable=False`
    quantity = db.Column `db.Integer, nullable=False`
    view_type = db.Column `db.String `100`, nullable=False`
    category_id = db.Column `db.Integer, db.ForeignKey `
        'category.id'`, nullable=False`

    def __init__ `self, name, description, price, category_id, view_type, img_name, mgf_date, exp_date, quantity`:
        self.name = name
        self.description = description
        self.price = price
        self.category_id = category_id
        self.view_type = view_type
        self.img_name = img_name
        self.mgf_date = mgf_date
        self.exp_date = exp_date
        self.quantity = quantity


class UserProduct `db.Model`:
    __tablename__ = 'userproduct'
    id = db.Column `db.Integer, primary_key=True`
    quantity = db.Column `db.Integer, nullable=False`
    product_id = db.Column `db.Integer, db.ForeignKey `
        'product.id'`, nullable=False`
    user_id = db.Column `db.Integer, db.ForeignKey `'user.id'`, nullable=False`


class Category `db.Model`:
    id = db.Column `db.Integer, primary_key=True, autoincrement=True`
    name = db.Column `db.String `50`, nullable=False, unique=True`
    view_type = db.Column `db.String `100`, nullable=False`
    products = db.relationship `'Product', backref='category', lazy=True`

    def __init__ `self, name, view_type`:
        self.name = name
        self.view_type = view_type


class Role `db.Model, fsqla.FsRoleMixin`:
    pass


class User `db.Model, fsqla.FsUserMixin`:
    total_value = db.Column `db.Float`
    orders = db.relationship `'Order', backref='user', lazy=True`
    cart = db.relationship `'Product', secondary='userproduct'`


class Order `db.Model`:
    id = db.Column `db.Integer, primary_key=True, autoincrement=True`
    user_id = db.Column `db.Integer, db.ForeignKey `'user.id'`, nullable=False`
    name = db.Column `db.String `100`, nullable=False`
    img_name = db.Column `db.String `100`, nullable=False`
    amount = db.Column `db.Float, nullable=False`
    order_date = db.Column `db.DateTime, nullable=False`

    def __init__ `self, user_id, name, img_name, amount, order_date`:
        self.user_id = user_id
        self.name = name
        self.img_name = img_name
        self.amount = amount
        self.order_date = order_date

Note: > By default flask-security-too create all the necessary columns and tables required for all its implementations. It add some default columns in the User and Role table and it also creates roles_user table.

In the talbes Product, Category, and Order we have defined a serialize methods to serialize the data into json format.

We have also created one-to-many relationship between Product and Category. One Category can have multiple products, that's why we have added a Foreign key with name category_id in the Product table and added products to the Category table.

Note > Calling Category ``.products will return all the products under that category a python list of category objects.

Same One-to-Many relationship between User and Order tables. Obviously one user can have multiple orders, that's why we have added a Foreign key with name user_id in the Order table and added orders to the User table.

Many-to-Many Relationship

We have created a many-to-many relationship between User and Product tables, that's why we have added a userproduct association table in the database.

The reason that we need this M-to-M relationship is because a product can be added to cart by multiple users until and unless the quantity of the product available. Same way a user can add multiple products.

Configuration

Let's configure the the database and the flask-security-too.

config.py

class LocalDevelopmentConfig ``:
    SQLALCHEMY_DATABASE_URI = "sqlite:///project.db"
    SECRET_KEY = 'aojfnojvaor38905nauvwej0taf'
    SECURITY_PASSWORD_SALT = 'aojfnojvaor38905nauvwej0taf'
    SECURITY_REGISTERABLE = True
    SECURITY_SEND_REGISTER_EMAIL = False
    SECURITY_CHANGEABLE = True
    SECURITY_POST_REGISTER_VIEW = '/login'
    SECURITY_POST_LOGIN_VIEW = '/'

You can read more about the different configurations here flask-security-too.readthedocs.io/en/stable/config.html.

Note The moment you implement flask-security-too you no need to create the even templates. Flask-security-too has all the templates for you. You just need to select yes/no for the features you want to use.

Now we will integrate the Flask-security-too with the flask application.

python filename=main.py

from flask import Flask
from flask_security import  ` # **!tl add:start**
    Security,
    SQLAlchemyUserDatastore,
    hash_password
`
from application.database import  `
    db,
    User,
    Role
`
from application.config import LocalDevelopmentConfig # **!tl add:end**

app = None # **!tl add**
app = Flask `__name__` #**!tl remove**

def create_app ``: # **!tl add:start**
    app = Flask `__name__`
    if os.getenv `'ENV', "development"` == "production":
        raise Exception `"Currently no production config is setup."`
    else:
        app.config.from_object `LocalDevelopmentConfig`
    db.init_app `app`
    with app.app_context ``:
        db.create_all ``
    return app # **!tl add:end**


app = create_app ``
user_datastore = SQLAlchemyUserDatastore `db, User, Role`
security = Security `app, user_datastore`

@app.route `"/"`
def index ``:
  return "<h1>Hello, World!</h1>"

if __name__ == "__main__":
  app.run `debug=True`

Note I would like to request to read about app_context ``` **here**flask.palletsprojects.com/en/3.0.x/appcontext/`.

The snippet of code db.create_all ``` will create all the tables in the database. And we are reading all the configurations from theconfig.pyfile with the help of the objectLocalDevelopmentConfig`.

Admin Credentials

I want my code to insert the admin credentials while creating the database fro the first time.

Note One thing you should know that `db.create_all ``` won't create the database again. It checks if the database is there or not, if not then only it creates the database. So whenever you make changes to the schema of the database you will to delete the existing database and start the app to create the new database.

But there issue with this. Let's suppose you don't want to delete the existing data in your database. Which is obvious if your application is running on production then you can delete the database else you will loose all your existing data.

There is way to overcome from this bottleneck that is database migration. For Flask app there is a library called flask-migrate which will help you to migrate your database schema. You can read more about it here flask-migrate.readthedocs.io/en/latest/.

But for this development we will keep the things less and less complicated so better to delete the database and create a new one.

python filename=main.py
...


app = create_app ``
user_datastore = SQLAlchemyUserDatastore `db, User, Role`
security = Security `app, user_datastore`

with app.app_context ``: #**tl! add:start**
    security.datastore.find_or_create_role `
        name="admin", permissions={"user-read", "user-write"}
    `
    if not security.datastore.find_user `email="test@me.com"`:
        security.datastore.create_user `email="test@me.com",
                                       password=hash_password `"password"`,
                                       roles=**"admin"**`
        db.session.commit `` # **tl! add:end**
...

The moment you start using Flask-security-too you no longer directly deal with the User and Role table instead of that you ask for all the operations to security. This security is like an assistance for you which will manage the security and these two tables for you in your application.

Testing it out

We will create a protected route to verify if the authentication is working fine or not?

python filename=main.py

...
from flask_security import  ` # **!tl add:start**
    Security,
    SQLAlchemyUserDatastore,
    hash_password,
    auth_required #**tl! add**
`
...


@app.route `"/protected"`
@auth_required ``
def index ``:
  return "<h1>Protected Route</h1>"

if __name__ == "__main__":
  app.run `debug=True`

Let's start the application by running the following command:

python main.py

And try to access the protected route http://localhost:5000/protected in your browser. You will notice that the route is protected and you will be redirected to the login page. Now you can login with admin credentials and try again to access the protected route.

You will be able to access the protected route only if you are logged in.

Let's commit the change using the following command.

git add .
git commit -m "Added protected route"

Continue to create routes...