Tutorial: Building an Authentication System with Streamlit

Tutorial: Building an Authentication System with Streamlit

Introduction

Ready to dive into the world of secure and scalable authentication? ?? In this tutorial, we're going to show you how to create a super-cool Streamlit-based authentication system that uses JWT tokens and role-based access control (RBAC). This isn't just about secure logins—it's about managing users, assigning roles, and crafting dashboards that are as unique as each user.

Imagine a system where admins have full control, and regular users are only able to access what they’re meant to—no more, no less. It’s all about making sure the right people have access to the right things. This project is built for growth, so whether you're just getting started or scaling up, you'll have everything you need for long-term success. Join us and see how easy it is to build something amazing with Streamlit and CodeCodix! ??????

Project Structure

Here is the structure of the project:

user_authentication/        
├── .streamlit/? ? ? ? ? ? # Streamlit-specific configurations        
│ ? └── secrets.toml        
├── auth/? ? ? ? ? ? ? ? ? # Authentication and database utilities        
│ ? ├── auth_manager.py? ? # User authentication logic        
│ ? ├── database.db? ? ? ? # Database file (SQLite)        
│ ? ├── db_manager.py? ? ? # Database operations (CRUD)        
│ ? ├── hash_utils.py? ? ? # Password hashing utilities        
│ ? ├── jwt_utils.py ? ? ? # JWT encoding and decoding        
│ ? └── init.py        
├── tests/ ? ? ? ? ? ? ? ? # Unit tests for components        
│ ? ├── test_auth.py ? ? ? # Placeholder for authentication tests        
│ ? └── init.py        
├── ui/? ? ? ? ? ? ? ? ? ? # User interface components        
│ ? ├── dashboard.py ? ? ? # Admin dashboard for managing users and roles        
│ ? ├── login.py ? ? ? ? ? # Login page logic        
│ ? ├── helpers.py ? ? ? ? # Placeholder for helper functions        
│ ? └── init.py        
├── app.py ? ? ? ? ? ? ? ? # Main application entry point        
├── auth.db? ? ? ? ? ? ? ? # Database for user authentication        
├── fondo.jpg? ? ? ? ? ? ? # Background image for the UI        
├── README.md? ? ? ? ? ? ? # Documentation for the project        
├── requirements.txt ? ? ? # Python dependencies        
└── setup.py ? ? ? ? ? ? ? # Placeholder for packaging        

Step 1: Setting Up Your Environment

Before we start implementing the system, let's set up your environment.

1. Clone the repository:

First, clone the repository to your local machine.

git clone https://github.com/Imanolasolo/user_authentication_streamlit.git        
cd user_authentication        

2. Install dependencies:

Install the required dependencies from the requirements.txt file.

pip install -r requirements.txt        

3. Initialize the database:

Run this command to initialize your SQLite database.

python -c "import auth.db_manager as db; db.initialize_database()"        

4. Run the application:

Start the Streamlit app by running the following command:

streamlit run app.py        

Step 2: Implementing Authentication

2.1 JWT Token Management

  • File: auth/jwt_utils.py
  • Functionality: This module handles encoding and decoding of JWT tokens. It stores user information (username, role) securely in the token.

import jwt        
from datetime import datetime, timedelta        
SECRET_KEY = "tu_secreto"        
def generate_token(username: str, role: str, expiration_minutes=60) -> str:        
????payload = {        
????????"username": username,        
????????"role": role,        
????????"exp": datetime.utcnow() + timedelta(minutes=expiration_minutes)        
????}        
????return jwt.encode(payload, SECRET_KEY, algorithm="HS256")        
def decode_token(token: str):        
????try:        
????????return jwt.decode(token, SECRET_KEY, algorithms=["HS256"])        
????except jwt.ExpiredSignatureError:        
????????return None        


2.2 Password Hashing

  • File: auth/hash_utils.py
  • Functionality: This file contains functions for securely hashing passwords using the bcrypt library.

import bcrypt        
def hash_password(password: str) -> str:        
????salt = bcrypt.gensalt()        
????return bcrypt.hashpw(password.encode(), salt).decode()        
def verify_password(password: str, hashed: str) -> bool:        
????return bcrypt.checkpw(password.encode(), hashed.encode())        

2.3 Database Integration

  • File: auth/db_manager.py
  • Functionality: This file manages an SQLite database to manage user records.

import sqlite3        
import os        
class DataBaseManager:        
????def init(self, db_name="database.db"):        
????????self.db_name = db_name        
????????self.connection = None        
????????self.create_tables()        
????def connect(self):        
????????"""Connect to the database."""        
????????self.connection = sqlite3.connect(self.db_name)        
????????return self.connection        
????def execute(self, query, params=()):        
????????"""Executes a SQL query."""        
????????conn = self.connect()        
????????cursor = conn.cursor()        
????????cursor.execute(query, params)        
????????conn.commit()        
????????return cursor        
????def fetchall(self, query, params=()):        
????????"""Executes a SELECT query and returns all the results."""        
????????conn = self.connect()        
????????cursor = conn.cursor()        
????????cursor.execute(query, params)        
????????return cursor.fetchall()        
????def fetchone(self, query, params=()):        
????????conn = self.connect()        
????????cursor = conn.cursor()        
????????cursor.execute(query, params)        
????????return cursor.fetchone()        
????def create_tables(self):        
????????"""Create the necessary tables in the database."""        
????????users_table = """        
????????CREATE TABLE IF NOT EXISTS users (        
????????????id INTEGER PRIMARY KEY AUTOINCREMENT,        
????????????username TEXT UNIQUE NOT NULL,        
????????????password TEXT NOT NULL,        
????????????role TEXT NOT NULL        
????????);        
????????"""        
????????roles_table = """        
????????CREATE TABLE IF NOT EXISTS roles (        
????????????id INTEGER PRIMARY KEY AUTOINCREMENT,        
????????????role_name TEXT UNIQUE NOT NULL        
????????);        
????????"""        
????????self.execute(users_table)        
????????self.execute(roles_table)        
if name == "__main__":        
????db_manager = DataBaseManager("database.db")        
????print(f"Database '{db_manager.db_name}' correctly initialized.")        

2.4 Manage User Authentication

  • File: auth/auth_manager.py
  • Functionality: This module handles user registration, authentication, and role management. It ensures that users can log in, be authenticated, and access their designated pages.

import sqlite3        
import streamlit as st        
from .hash_utils import hash_password, verify_password        
from .db_manager import DataBaseManager        
class AuthManager:        
????def init(self, db_path="auth.db"):        
????????self.db = DataBaseManager(db_path)        
????????self.create_admin_user()? # Create the admin user upon initialization        
????def create_admin_user(self):        
????????"""Create the admin user if it does not exist."""        
????????admin_username = st.secrets["admin"]["username"]        
????????admin_password = hash_password(st.secrets["admin"]["password"])        
????????admin_role = "admin"        
????????# Check if the admin user already exists        
????????admin_exists = self.db.fetchone(        
????????????"SELECT * FROM users WHERE username = ?",        
????????????(admin_username,)        
????????)        
????????if not admin_exists:        
????????????self.db.execute(        
????????????????"INSERT INTO users (username, password, role) VALUES (?, ?, ?)",        
????????????????(admin_username, admin_password, admin_role)        
????????????)        
????????????print("Admin user created successfully.")        
????def register_user(self, username: str, password: str, role: str):        
????????"""Register a new user in the database."""        
????????hashed_password = hash_password(password)        
????????try:        
????????????self.db.execute(        
????????????????"INSERT INTO users (username, password, role) VALUES (?, ?, ?)",        
????????????????(username, hashed_password, role)        
????????????)        
????????????return True        
????????except sqlite3.IntegrityError:        
????????????return False        
????def authenticate_user(self, username: str, password: str):        
????????"""Authenticates the user by verifying the credentials."""        
????????user = self.db.fetchone(        
????????????"SELECT username, password, role FROM users WHERE username = ?",        
????????????(username,)        
????????)        
????????if user and verify_password(password, user[1]):        
????????????return {"username": user[0], "role": user[2]}        
????????return None        
????def create_role(self, role_name: str):        
????????"""Create a new role in the database."""        
????????try:        
????????????self.db.execute("INSERT INTO roles (role_name) VALUES (?)", (role_name,))        
????????????return True        
????????except sqlite3.IntegrityError:        
????????????return False        

Step 3: Building the User Interface

3.1 Login Page

  • File: ui/login.py
  • Functionality: The login page allows users to enter their credentials, verify their identity, and create an authentication token upon successful login.

import streamlit as st        
from auth.auth_manager import AuthManager        
from auth.jwt_utils import generate_token, decode_token        
from ui import dashboard? # Make sure the dashboard module is imported correctly.        
auth_manager = AuthManager()        
def login_page():        
????st.title("Login")        
????# Check if there is already an active session        
????if "auth_token" in st.session_state:        
????????token_data = decode_token(st.session_state["auth_token"])        
????????if token_data:        
????????????st.success(f"Active session: {token_data['username']} ({token_data['role']})")        
????????????# Allows you to log out        
????????????if st.button("Log Out"):        
????????????????del st.session_state["auth_token"]        
????????????????st.rerun()        
????????????# Redirects to the dashboard of the corresponding role        
????????????if token_data["role"] == "admin":        
????????????????dashboard.admin_dashboard()        
????????????else:        
????????????????st.info("Role not supported yet.")        
????????????return        
????# Formulario de inicio de sesión        
????username = st.text_input("User", key="login_username")        
????password = st.text_input("Password", type="password", key="login_password")        
????if st.button("Login", key="login_button"):        
????????user = auth_manager.authenticate_user(username, password)        
????????if user:        
????????????token = generate_token(user["username"], user["role"])        
????????????st.session_state["auth_token"] = token        
????????????st.success(f"Welcome, {user['username']} ({user['role']})")        
????????????st.rerun()? # Reload to enter the active session        
????????else:        
????????????st.error("Incorrect username or password")        


Step 4: Admin Dashboard

4.1 Admin Dashboard

- File: ui/dashboard.py

- Functionality: The admin dashboard allows for managing users, roles, and displaying role-specific content. This is the CRUD of application.

import streamlit as st        
from auth.auth_manager import AuthManager        
from auth.hash_utils import hash_password        
from auth.jwt_utils import decode_token        
# We initialize the authentication manager        
auth_manager = AuthManager()        
def admin_dashboard():        
????"""        
????Administrator dashboard to manage users and roles.        
????Provides basic CRUD for users and roles.        
????"""        
????st.title("Administration Panel")        
????# Check if the user has an active session        
????if "auth_token" not in st.session_state:        
????????st.warning("Please log in first.")        
????????st.stop()        
????# Decode the token to get the user information        
????token_data = decode_token(st.session_state["auth_token"])        
????if not token_data or token_data.get("role") != "admin":        
????????st.error("You do not have permission to access this page.")        
????????st.stop()        
????# Tabs to separate user and role management        
????tab1, tab2 = st.tabs(["User management", "Role management"])        
????# Gestión de Usuarios        
????with tab1:        
????????st.header("User management")        
????????# Formulario para agregar un nuevo usuario        
????????st.subheader("Add user")        
????????new_username = st.text_input("Username", key="new_user_username")        
????????new_password = st.text_input("Password", type="password", key="new_user_password")        
????????roles = auth_manager.db.fetchall("SELECT role_name FROM roles")        
????????new_role = st.selectbox("Role", [r[0] for r in roles], key="new_user_role")        
????????if st.button("Add user", key="add_user_button"):        
????????????if new_username and new_password and new_role:        
????????????????success = auth_manager.register_user(new_username, new_password, new_role)        
????????????????if success:        
????????????????????st.success(f"User '{new_username}' created successfully.")        
????????????????????st.session_state["auth_token"] = st.session_state["auth_token"]? # Keep the token        
????????????????????st.rerun()        
????????????????else:        
????????????????????st.error(f"User '{new_username}' already exists.")        
????????????else:        
????????????????st.error("All fields are required.")        
????????# Show list of users with option to delete        
????????st.subheader("Registered Users")        
????????users = auth_manager.db.fetchall("SELECT id, username, role FROM users")        
????????for user_id, username, role in users:        
????????????col1, col2 = st.columns([3, 1])        
????????????col1.write(f"**{username}** ({role})")        
????????????if col2.button("Eliminate", key=f"delete_user_button_{user_id}"):        
????????????????auth_manager.db.execute("DELETE FROM users WHERE id = ?", (user_id,))        
????????????????st.session_state["auth_token"] = st.session_state["auth_token"]? # Keep the token        
????????????????st.rerun()        
????# Role Management        
????with tab2:        
????????st.header("Role Management")        
????????# Formulario para agregar un nuevo rol        
????????st.subheader("Add Role")        
????????new_role_name = st.text_input("Role Name", key="new_role_name")        
????????if st.button("Add Role", key="add_role_button"):        
????????????if new_role_name:        
????????????????success = auth_manager.create_role(new_role_name)        
????????????????if success:        
????????????????????st.success(f"Role '{new_role_name}' Created successfully.")        
????????????????????st.session_state["auth_token"] = st.session_state["auth_token"]? # Mantener el token        
????????????????????st.rerun()        
????????????????else:        
????????????????????st.error(f"Role '{new_role_name}' already exists.")        
????????????else:        
????????????????st.error("The role name is required.")        
????????# Mostrar lista de roles con opción para eliminar        
????????st.subheader("Existing Roles")        
????????roles = auth_manager.db.fetchall("SELECT id, role_name FROM roles")        
????????for role_id, role_name in roles:        
????????????col1, col2 = st.columns([3, 1])        
????????????col1.write(f"**{role_name}**")        
????????????if col2.button("Eliminate", key=f"delete_role_button_{role_id}"):        
????????????????auth_manager.db.execute("DELETE FROM roles WHERE id = ?", (role_id,))        
????????????????st.session_state["auth_token"] = st.session_state["auth_token"]? # Mantener el token        
????????????????st.rerun()        
# Main call to execute the function        
if name == "__main__":        
????admin_dashboard()        

Step 5: Running the App – app.py

The app.py file serves as the entry point for the Streamlit application. It is the first file that gets executed when you run the app, and it ties everything together: from setting the page configuration to defining the user interface flow.

Here's an in-depth look at how to set up app.py with a clean structure for handling user authentication, background image settings, and defining the flow between the login page and other components.

Explanation of app.py Components

from ui import dashboard        
from ui import login        
import streamlit as st        
import base64        
# Initial Configuration        
st.set_page_config(page_title="User auth", layout="wide")        
# Function to encode image as base64 to set as background        
def get_base64_of_bin_file(bin_file):        
????with open(bin_file, 'rb') as f:        
????????data = f.read()        
????return base64.b64encode(data).decode()        
????# Encode the background image        
img_base64 = get_base64_of_bin_file('fondo.jpg')        
????# Set the background image using the encoded base64 string        
st.markdown(        
????f"""        
????<style>        
????.stApp {{        
????????background: url('data:image/jpeg;base64,{img_base64}') no-repeat center center fixed;        
????????background-size: cover;        
????}}        
????</style>        
????""",        
unsafe_allow_html=True        
)        
if name == "__main__":        
????login.login_page()        

1. Page Configuration

st.set_page_config(page_title="User Auth", layout="wide")

This sets up the Streamlit page configuration:

  • page_title: Sets the title of the page displayed in the browser tab.
  • layout="wide": Makes the layout wider than the default, providing more room for your app content.

2. Encoding the Background Image

def get_base64_of_bin_file(bin_file):

????with open(bin_file, 'rb') as f:

????????data = f.read()

????return base64.b64encode(data).decode()

This function takes an image file (fondo.jpg), reads it as binary data, and encodes it into a Base64 string to use in the CSS style for setting the background image.

3. Set Background Image Using CSS

This snippet applies the Base64-encoded image as a background for the entire Streamlit app. The background image will cover the whole screen (background-size: cover) and remain fixed even when scrolling (background-attachment: fixed).

img_base64 = get_base64_of_bin_file('fondo.jpg')        
st.markdown(        
????f"""        
????<style>        
????.stApp {{        
????????background: url('data:image/jpeg;base64,{img_base64}') no-repeat center center fixed;        
????????background-size: cover;        
????}}        
????</style>        
????""",        
????unsafe_allow_html=True        
)        

4. User Authentication Flow

The login page (login.login_page()) is displayed.

5. Running the App

At the end of the script, the application checks the user’s authentication status and renders the appropriate content based on whether the user is logged in or not.

Summary of Functionality:

if name == "__main__":        
????login.login_page()? # If no token, show the login page        

  1. Login Page: If the user isn't logged in (i.e., no JWT token is found), the login page is displayed, prompting the user to enter their credentials.
  2. Admin Dashboard: If the user is logged in and their role is admin, they are redirected to the admin dashboard. You can expand this for other roles (e.g., doctors).
  3. Background Image: The app displays a background image, fetched and encoded into Base64 to be embedded directly in the app using CSS.
  4. Role-based Access: The system provides role-based access control (RBAC) where only authorized users can view specific pages.

Running the App:

Once you've set up your environment (installed dependencies, configured the database), you can run the app by executing:

streamlit run app.py        

This will start the Streamlit app, which will automatically open in your web browser, showing the login page or dashboard depending on the user's authentication status.

Conclusion

With the components above, you now have a fully functional authentication system using JWT tokens, role-based access control, and a user interface that allows admins to manage users and roles. The code demonstrates the key concepts of user authentication, role management, and secure login in Streamlit using JWT.

要查看或添加评论,请登录

Imanol Asolo的更多文章

社区洞察

其他会员也浏览了