Tutorial: Building an Authentication System with Streamlit
Imanol Asolo
Senior Full Stack Developer & AI Tools Builder | Expert in Chatbot Development and Inbound Marketing Solutions | LLMs and AI integrator | AI Evangelist
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
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
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
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
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
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:
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
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.