Enhancing Flask SSO with Role-Based Access Control (RBAC)

Enhancing Flask SSO with Role-Based Access Control (RBAC)

Enhancing Flask SSO with Role-Based Access Control (RBAC)

To enhance your Single Sign-On (SSO) application with Role-Based Access Control (RBAC), you need to implement a mechanism to assign roles to users and control access to different routes or resources based on those roles. Here's how you can do it step by step:

1. Extend the Database Schema

You'll need to add a role column to the users table so that each user can be assigned a role, such as "admin," "user," or any other custom role.


def init_db():
    try:
        with sqlite3.connect('sso_service.db') as conn:
            c = conn.cursor()
            c.execute('''CREATE TABLE IF NOT EXISTS tokens (token TEXT PRIMARY KEY, username TEXT, token_type TEXT, expiration DATETIME)''')
            c.execute('''CREATE TABLE IF NOT EXISTS totp_secrets (username TEXT PRIMARY KEY, totp_secret TEXT)''')
            c.execute('''CREATE TABLE IF NOT EXISTS users (email TEXT PRIMARY KEY, username TEXT, password TEXT, role TEXT)''')
            conn.commit()
        logging.info("Database initialized successfully.")
    except sqlite3.Error as e:
        logging.error(f"Error initializing the database: {e}")
        raise

2. Assign Roles During Signup

When users sign up, you can assign roles automatically or based on certain conditions. Modify the signup route to include a role:


@app.route("/signup", methods=["GET", "POST"])
def signup():
    if request.method == "GET":
        return '''
        <!-- HTML code for signup remains the same -->
        '''

    data = request.json
    email = data.get("email")
    password = data.get("password")
    role = "user"  # Assign the default role 'user' during signup

    if not email or not password:
        return jsonify({"error": "Email and password are required"}), 400

    if not is_allowed_domain(email):
        return jsonify({"error": "Signup is only allowed for specific email domains"}), 400

    hashed_password = generate_password_hash(password)

    # Store the user with their role
    with sqlite3.connect('sso_service.db') as conn:
        c = conn.cursor()
        c.execute("INSERT INTO users (email, username, password, role) VALUES (?, ?, ?, ?)", (email, email, hashed_password, role))
        conn.commit()

    # The rest of your signup logic...

3. Enhance Token Generation with Roles

When generating JWT tokens, include the user's role in the token payload so that it can be used to authorize access:


def generate_tokens(username, is_refresh=False, original_expiration=None):
    role = get_user_role(username)  # Fetch the role of the user
    access_token = jwt.encode({
        "username": username,
        "role": role,  # Include the role in the token
        "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=ACCESS_TOKEN_EXPIRATION)
    }, SECRET_KEY, algorithm="HS256")

    # The rest of the function remains the same...

4. Get User Role Function

Ensure the get_user_role function fetches the role of the user from the database:


def get_user_role(username):
    with sqlite3.connect('sso_service.db') as conn:
        c = conn.cursor()
        c.execute("SELECT role FROM users WHERE username = ?", (username,))
        result = c.fetchone()
        return result[0] if result else None

5. Create a Role-Based Decorator

To restrict access to certain routes based on roles, you can create a decorator that checks the role from the JWT token.


from functools import wraps

def role_required(required_roles):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            token = request.cookies.get("access_token")
            if not token:
                return jsonify({"error": "Token missing, please login"}), 401
            try:
                decoded_token = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
                user_role = decoded_token.get("role")
                if user_role not in required_roles:
                    return jsonify({"error": "You do not have access to this resource"}), 403
                return func(*args, **kwargs)
            except jwt.ExpiredSignatureError:
                return jsonify({"error": "Token expired"}), 401
            except jwt.InvalidTokenError:
                return jsonify({"error": "Invalid token"}), 401
        return wrapper
    return decorator

6. Apply Role-Based Access Control (RBAC) to Routes

You can now protect certain routes by specifying which roles are allowed to access them:


@app.route("/admin", methods=["GET"])
@role_required(["admin"])
def admin_dashboard():
    return jsonify({"message": "Welcome to the admin dashboard!"})

@app.route("/user", methods=["GET"])
@role_required(["user", "admin"])  # Allow both user and admin roles to access this route
def user_dashboard():
    return jsonify({"message": "Welcome to the user dashboard!"})

7. Modify HTML Templates to Display Role-Based Content

You can adjust the frontend templates to display content based on the user's role. For example, in landing.html:


<p id="sessionInfo">Validating session...</p>

<!-- Admin-specific content -->
<div id="adminSection" style="display:none;">
    <h2>Admin Dashboard</h2>
    <p>Welcome, Admin!</p>
</div>

<!-- User-specific content -->
<div id="userSection" style="display:none;">
    <h2>User Dashboard</h2>
    <p>Welcome, User!</p>
</div>

<script>
    function validateSession() {
        fetch("https://sso.jegantest.in/validate", {
            method: "POST",
            credentials: "include"
        })
        .then(response => response.json())
        .then(data => {
            if (data && data.valid) {
                document.getElementById("sessionInfo").innerText = "Logged in as: " + data.user;
                if (data.role === "admin") {
                    document.getElementById("adminSection").style.display = "block";
                } else if (data.role === "user") {
                    document.getElementById("userSection").style.display = "block";
                }
            } else {
                window.location.href = "/index.html";
            }
        })
        .catch(error => {
            console.error("Error during session validation", error);
            window.location.href = "/index.html";
        });
    }

    window.onload = function() {
        validateSession();
    };
</script>

8. Testing and Debugging

Test the RBAC implementation by creating users with different roles and ensuring that only users with the correct role can access certain routes or resources.

Summary

By extending your SSO system with a role field in the database, modifying JWT token generation, and implementing a decorator for role-based access, you can effectively manage access to different parts of your application based on user roles. This approach is flexible and scalable, allowing you to add more roles and customize access rules easily.

Comments

Popular posts from this blog

MongoDB Installation Ubuntu 22.04 LTS