Authentication script

I have here a brief authentication script (well, a hundred lines or so of python) that just allows very simple user authentication and management, without any additional components, or even using the forms control. It’s been developed in version 0.55 just to make sure that it’s widely compatible!

To use it, just import the auth function from the file and run it - it returns either None or the authenticated username. It runs in the sidebar, but can be made to run in the main window if you prefer.

State is stored in an on-disk sqlite database file. THERE IS NO PASSWORD HASHING and passwords are easily viewable when editing the user file. This is for simple use cases only. Additional functions (eg password hashing and salting) would be fairly easy to add, but not needed for my use case, which is a public facing, multiuser server, with no confidential data but a need to display information depending on the user viewing the site.

To set the user database up, just run the module directly as a streamlit script.

I’ve included an example main.py streamlit script showing how to do the import (ideally as close to the top of the file as possible) and then use the return value to decide if the user is authenticated or not (as well as who they are!)

main.py

import streamlit as st

st.write(
    "This is a landing page designed to showcase the simple authentication library."
)
st.write(
    "This is just a single import and a function to run, and the return value is either None, or the authenticated username."
)
st.write(
    "The user login is usually based in the sidebar (though ultimately configurable by passing True or False to the sidebar parameter of the auth function"
)
st.write(
    "All the user management and username and password entry should be taken care of by the library. To automatically have creation and edit access, just run the library directly as a streamlit script."
)

from authlib import auth

data = auth()  # auth(sidebar=False) or auth(False) if you don't want the sidebar
"""This both displays authentication input in the sidebar, and then returns the credentials for use locally"""

st.write(f"{'AUTHENTICATED!' if data else 'NOT authenticated'}")
st.write(
    f"Authentication data received = {data} (which is the username that has logged in)"
)

authlib.py

import streamlit as st
import sqlite3 as sql


def auth(sidebar=True):

    try:
        conn = sql.connect("file:auth.db?mode=ro", uri=True)
    except sql.OperationalError:
        st.error(
            "Authentication Database is Not Found.\n\nConsider running authlib script in standalone mode to generate."
        )
        return None

    input_widget = st.sidebar.text_input if sidebar else st.text_input
    checkbox_widget = st.sidebar.checkbox if sidebar else st.checkbox
    user = input_widget("Username:")

    data = conn.execute("select * from users where username = ?", (user,)).fetchone()
    if user:
        password = input_widget("Enter Password:", type="password")
        if data and password == data[2]:
            if data[3]:
                if checkbox_widget("Check to edit user database"):
                    _superuser_mode()
            return user
        else:
            return None
    return None


def _list_users(conn):
    table_data = conn.execute("select username,password,su from users").fetchall()
    if table_data:
        table_data2 = list(zip(*table_data))
        st.table(
            {
                "Username": (table_data2)[0],
                "Password": table_data2[1],
                "Superuser?": table_data2[2],
            }
        )
    else:
        st.write("No entries in authentication database")


def _create_users(conn, init_user="", init_pass="", init_super=False):
    user = st.text_input("Enter Username", value=init_user)
    pass_ = st.text_input("Enter Password (required)", value=init_pass)
    super_ = st.checkbox("Is this a superuser?", value=init_super)
    if st.button("Update Database") and user and pass_:
        with conn:
            conn.execute(
                "INSERT INTO USERS(username, password, su) VALUES(?,?,?)",
                (user, pass_, super_),
            )
            st.text("Database Updated")


def _edit_users(conn):
    userlist = [x[0] for x in conn.execute("select username from users").fetchall()]
    userlist.insert(0, "")
    edit_user = st.selectbox("Select user", options=userlist)
    if edit_user:
        user_data = conn.execute(
            "select username,password,su from users where username = ?", (edit_user,)
        ).fetchone()
        _create_users(
            conn=conn,
            init_user=user_data[0],
            init_pass=user_data[1],
            init_super=user_data[2],
        )


def _delete_users(conn):
    userlist = [x[0] for x in conn.execute("select username from users").fetchall()]
    userlist.insert(0, "")
    del_user = st.selectbox("Select user", options=userlist)
    if del_user:
        if st.button(f"Press to remove {del_user}"):
            with conn:
                conn.execute("delete from users where username = ?", (del_user,))
                st.write(f"User {del_user} deleted")


def _superuser_mode():
    with sql.connect("file:auth.db?mode=rwc", uri=True) as conn:
        conn.execute(
            "create table if not exists users (id INTEGER PRIMARY KEY, username UNIQUE ON CONFLICT REPLACE, password, su)"
        )
        mode = st.radio("Select mode", ("View", "Create", "Edit", "Delete"))
        {
            "View": _list_users,
            "Create": _create_users,
            "Edit": _edit_users,
            "Delete": _delete_users,
        }[mode](
            conn
        )  # I'm not sure whether to be proud or horrified about this...


if __name__ == "__main__":
    st.write(
        "Warning, superuser mode\n\nUse this mode to initialise authentication database"
    )
    if st.checkbox("Check to continue"):
        _superuser_mode()
1 Like