Open In App

Flask - Role Based Access Control

Last Updated : 25 Mar, 2025
Comments
Improve
Suggest changes
Like Article
Like
Report

Role-Based Access Control (RBAC) is a security mechanism that restricts user access based on their roles within an application. Instead of assigning permissions to individual users, RBAC groups users into roles and each role has specific permissions.

For example, in a Flask app, we might have roles like admin, editor and user, where:

  • Admin can manage users and settings.
  • Editor can create and edit content.
  • User can only view content.

This approach improves security, simplifies permission management and ensures users only access what they need.

Note: For storing users' details we are going to use flask-sqlalchemy and db-browser for performing database actions. we can find detailed tutorial here.

Installing Packages

To learn how to create and set-up flask app, refer to- Create Flask App

After creating a Flask app, we need to install modules required in this project, to install them execute this command in the terminal-

pip install flask flask-security flask-sqlalchemy flask-login email-validator

It will install these packages:

  • Flask: A lightweight web framework for building web applications.
  • Flask-Security: Adds authentication and role management features.
  • Flask-SQLAlchemy: Integrates SQLAlchemy for easy database operations.
  • Flask-Login: Manages user sessions and handles login/logout.
  • email-validator: Validates email addresses for correct formatting.

File Structure

The file structure of this project aftre completion will look like this-

RBAC-file-structurre
File Structure

Importing Modules and Setting Configurations

Configurations are key-value settings that control the app's behavior, such as database connections, security settings and session management. These settings are stored in app.config and help customize the application's functionality. The configurations used in our app are-

  • app.config['SECRET_KEY'] = 'your_secret_key'- A secret key used for securely signing session cookies and protecting against attacks like CSRF.
  • app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db' - Defines the database location, here using SQLite (users.db) for storing user data.
  • login_manager.login_view = 'login' - Specifies the route where users should be redirected if they try to access a protected page without logging in. 
Python
from flask import Flask, render_template, redirect, url_for, request
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, login_user, logout_user, current_user
from flask_security import Security, SQLAlchemySessionUserDatastore, roles_accepted, UserMixin, RoleMixin
import uuid
app = Flask(__name__)

# --- Configuration ---
app.config['SQLALCHEMY_DATABASE_URI'] = "sqlite:///g4g.sqlite3"  # Path to the SQLite database
app.config['SECRET_KEY'] = 'MY_SECRET'                            # Secret key for session management

# --- Initialize Database ---
db = SQLAlchemy(app)

Create Database Models

This part defines our database models. We create two models—User and Role—with a many-to-many relationship via an association table. The fs_uniquifier is a required field for Flask-Security.

Python
# ---------------- Association Table for User Roles ----------------
roles_users = db.Table('roles_users',
    db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
    db.Column('role_id', db.Integer(), db.ForeignKey('role.id'))
)

# ---------------- Database Models ----------------
class User(db.Model, UserMixin):
    __tablename__ = 'user'
    id = db.Column(db.Integer, autoincrement=True, primary_key=True)
    email = db.Column(db.String(255), unique=True, nullable=False)
    password = db.Column(db.String(255), nullable=False, server_default='')
    active = db.Column(db.Boolean(), default=True)
    fs_uniquifier = db.Column(db.String(255), unique=True, nullable=False, default=lambda: uuid.uuid4().hex)
    # Relationship with roles
    roles = db.relationship('Role', secondary=roles_users, backref='roled')

class Role(db.Model, RoleMixin):
    __tablename__ = 'role'
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(80), unique=True, nullable=False)

Explanation

  • Association Table: roles_users is a helper table to create a many-to-many relationship between User and Role.
  • User Model: Contains user details including email, password, activation status and a unique identifier (fs_uniquifier) for security. It also relates to roles.
  • Role Model: Defines roles with a unique ID and name.

Defining Routes

In this section, we define all the routes for our application, such as home, signup, signin, logout and role-protected pages. The routes use Flask-Security decorators to restrict access based on user roles.

Python
                # Flask-Security Setup 
user_datastore = SQLAlchemySessionUserDatastore(db.session, User, Role)
security = Security(app, user_datastore)

                    # Routes

# Home route renders index.html
@app.route('/')
def index():
    return render_template("index.html")

# Signup route for user registration
@app.route('/signup', methods=['GET', 'POST'])
def signup():
    msg = ""
    if request.method == 'POST':
        user = User.query.filter_by(email=request.form['email']).first()
        if user:
            msg = "User already exists"
            return render_template("signup.html", msg=msg)
        user = User(email=request.form['email'], password=request.form['password'])
        role = Role.query.filter_by(id=int(request.form['options'])).first()
        if role:
            user.roles.append(role)
        else:
            msg = "Invalid role selection"
            return render_template("signup.html", msg=msg)
        db.session.add(user)
        db.session.commit()
        login_user(user)
        return redirect(url_for('index'))
    return render_template("signup.html", msg=msg)

# Signin route for user login
@app.route('/signin', methods=['GET', 'POST'])
def signin():
    msg = ""
    if request.method == 'POST':
        user = User.query.filter_by(email=request.form['email']).first()
        if user:
            if user.password == request.form['password']:
                login_user(user)
                return redirect(url_for('index'))
            else:
                msg = "Wrong password"
        else:
            msg = "User doesn't exist"
        return render_template("signin.html", msg=msg)
    return render_template("signin.html", msg=msg)

# Logout route to end the session
@app.route('/logout')
def logout():
    logout_user()
    return redirect(url_for('index'))

# Teachers route (Accessible to Admin only)
@app.route('/teachers')
@roles_accepted('Admin')
def teachers():
    teachers_list = []
    role_teachers = db.session.query(roles_users).filter_by(role_id=2).all()
    for teacher in role_teachers:
        user = User.query.filter_by(id=teacher.user_id).first()
        if user:
            teachers_list.append(user)
    return render_template("teachers.html", teachers=teachers_list)

# Staff route (Accessible to Admin and Teacher)
@app.route('/staff')
@roles_accepted('Admin', 'Teacher')
def staff():
    staff_list = []
    role_staff = db.session.query(roles_users).filter_by(role_id=3).all()
    for s in role_staff:
        user = User.query.filter_by(id=s.user_id).first()
        if user:
            staff_list.append(user)
    return render_template("staff.html", staff=staff_list)

# Students route (Accessible to Admin, Teacher, and Staff)
@app.route('/students')
@roles_accepted('Admin', 'Teacher', 'Staff')
def students():
    students_list = []
    role_students = db.session.query(roles_users).filter_by(role_id=4).all()
    for s in role_students:
        user = User.query.filter_by(id=s.user_id).first()
        if user:
            students_list.append(user)
    return render_template("students.html", students=students_list)

# My Details route (Accessible to all roles)
@app.route('/mydetails')
@roles_accepted('Admin', 'Teacher', 'Staff', 'Student')
def mydetails():
    return render_template("mydetails.html")

Explanation

  • Flask-Security Setup: Initializes Flask-Security with the user datastore connecting the User and Role models.
  • Home (/): Displays the index page with navigation links.
  • Signup (/signup): Handles user registration and role assignment.
  • Signin (/signin): Authenticates users based on email and password.
  • Logout (/logout): Logs out the user.
  • Protected Routes: (/teachers, /staff, /students, /mydetails) Use the @roles_accepted decorator to restrict access based on roles.

Creating Roles

We have tables but we still don't have roles created. We have to create those roles (Admin, Teacher, Staff, Student). Keep in mind that the id for Admin role must be 1, for Teacher: 2, Staff: 3 and Student: 4.

Create a new file "create_roles" in the same folder as app.py and add the below code. Remember to execute this file after the db creation.

Python
from app import Role, db, app

def create_roles():
    with app.app_context():
        admin = Role(id=1, name='Admin')
        teacher = Role(id=2, name='Teacher')
        staff = Role(id=3, name='Staff')
        student = Role(id=4, name='Student')

        db.session.add(admin)
        db.session.add(teacher)
        db.session.add(staff)
        db.session.add(student)

        db.session.commit()
        print("Roles created successfully!")

if __name__ == '__main__':
    create_roles()

Explanation

  • app.app_context(): The code is wrapped in with app.app_context(): to ensure Flask’s application context is active for database operations.
  • Role Insertion: Predefined roles are created with specific IDs and added to the database.
  • Commit: Changes are committed and a success message is printed.

Appliaction Entry Point

The following lines of code will be responsible fr creating the databse and running the app in debug mode, if the database already exists then it simply connects the app to it

Python
# --- Application Entry Point ---
if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run(debug=True)

Creating HTML files

Create the following html files in the templates folder

index.html

The index.html file uses the Jinja2 templating engine. The current_user variable stores the logged-in user's details. {% if current_user.is_authenticated %} checks if a user is logged in and displays their email ({{ current_user.email }}). Since users can have multiple roles, a loop iterates through them. If no user is logged in, the {% else %} block runs.

HTML
<!-- index.html -->

<!-- links to the pages -->
<a href="/teachers">View all Teachers</a> (Access: Admin)<br><br>
<a href="/staff">View all Staff</a> (Access: Admin, Teacher)<br><br>
<a href="/students">View all Students</a> (Access: Admin, Teacher, Staff)<br><br>
<a href="/mydetails">View My Details</a> (Access: Admin, Teacher, Staff, Student)
<br><br>
<!-- Show only if user is logged in -->
{% if current_user.is_authenticated %}
    <!-- Show current users email -->
    <b>Current user</b>: {{current_user.email}}
    <!-- Current users roles -->
    | <b>Role</b>: {% for role in current_user.roles%}
                    {{role.name}}
           {% endfor %} <br><br>
    <!-- link for logging out -->
    <a href="/logout">Logout</a>
<!-- Show if user is not logged in -->
{% else %}
    <a href="/signup">Sign up</a> | <a href="/signin">Sign in</a>
{% endif %}
<br><br>

Output:

How to implement role based access control in Flask?
index.html

signup.html page

The form's action is #, reloading the current page on submission. It uses the POST method to create a new database entry with fields for email, password and role selection via radio buttons. Jinja2's {% if %} checks if a user is logged in—if not, the form is shown; otherwise, a logged-in message appears.

HTML
<!-- signup.html -->

<h2>Sign up</h2>

<!-- Show only if user is logged in -->
{% if current_user.is_authenticated %}
    You are already logged in.

<!-- Show if user is NOT logged in -->    
{% else %}
{{ msg }}<br>

<!-- Form for signup -->
<form action="#" method="POST" id="signup-form">
            <label>Email Address </label>
            <input type="text" name="email" required /><br><br>
            
            <label>Password </label>
            <input type="password" name="password" required/><br><br>
            
            <!-- Options to choose role -->
              <!-- Give the role ids in the value -->
            <input type="radio" name="options" id="option1" value=1 required> Admin </input> 
        <input type="radio" name="options" id="option2" value=2> Teacher </input>
        <input type="radio" name="options" id="option3" value=3> Staff </input>
            <input type="radio" name="options" id="option3" value=4> Student </input><br>
        <br>
        
            <button type="submit">Submit</button><br><br>
            
            <!-- Link for signin -->
            <span>Already have an account?</span>
            <a href="/signin">Sign in</a>
</form>
<!-- End the if block -->
{% endif %}

Output:

How to implement role based access control in Flask?
signup.html

signin.html

Similar to the signup page, check if a user is already logged in, if not then render the form asking for email and password. The form method should be POST.

HTML
<!-- signin.html -->

<h2>Sign in</h2>

<!-- Show only if user is logged in -->
{% if current_user.is_authenticated %}
    You are already logged in.
    
<!-- Show if user is NOT logged in -->    
{% else %}
<!-- msg that was passed while rendering template -->
{{ msg }}<br>

<form action="#" method="POST" id="signin-form">
            <label>Email Address </label>
            <input type="text" name="email" required /><br><br>
            
            <label>Password </label>
            <input type="password" name="password" required/><br><br>
            
            <input class="btn btn-primary" type="submit" value="Submit"><br><br>
    
            <span>Don't have an account?</span>
            <a href="/signup">Sign up</a>
</form>
{% endif %}

Output:

How to implement role based access control in Flask?
signin.html

teachers.html

The teachers passed in the render_template is a list of objects, containing all the columns of the user table, so we're using Python for loop in jinja2 to show the elements in the list in HTML ordered list tag.

HTML
<!-- teachers.html -->

<h3>Teachers</h3>

<!-- list that shows all teachers' email -->
<ol>
{% for teacher in teachers %}
<li>
{{teacher.email}}
</li>
{% endfor %}
</ol>

Output:

How to implement role based access control in Flask?
teachers.html

staff.html

In this file, we are iterating all the staff and extracting their email IDs.

HTML
<! staff.html -->

<h3>Staff</h3>
<ol>
{% for staf in staff %}
<li>
{{staf.email}}
</li>
{% endfor %}
</ol>

Output:

How to implement role based access control in Flask?
staff.html

student.html

Iterating all the students and extracting their email IDs.

HTML
<!-- students.html -->

<h3>Students</h3>
<ol>
{% for student in students %}
<li>
{{student.email}}
</li>
{% endfor %}
</ol>

Output:

How to implement role based access control in Flask?
studen.html

mydetails.html

Similar to the index page, to show the role use a for loop from Jinja2, because a user can more than one role i.e., current_user.roles is a list of roles that were queried from the database.

HTML
<!-- mydetails.html -->

<h3>My Details</h3><br>
<b>My email</b>: {{current_user.email}}
| <b>Role</b>: {% for role in current_user.roles%}
                    {{role.name}}
       {% endfor %} <br><br>

Output:

How to implement role based access control in Flask?
details.html

Running and Testing the Application

To correctly run the app we need to follow these step:

Step 1: First run the following command in the terminal.

python app.py

It will start the app and create the database "g4g" in instance folder.

Step 2: Then stop the app using CTRL + C and run the create_roles.py file using command-

python create_roles.py

This will create the roles in the database "g4g".

Step 3: Then again run the main flask app using command-

python app.py

Go to:

http://127.0.0.1:5000

Output

To demonstrate the working of app, sign up as a new user "geek" in student role.

RBAC-output
Snapshot after signin

Next Article

Similar Reads