DEV Community

Cover image for Elastic D&D - Update 2 - Streamlit & the Login Page
Joe
Joe

Posted on • Edited on

Elastic D&D - Update 2 - Streamlit & the Login Page

Last week I mentioned that I would begin breaking down the code of the Streamlit application. However, the post would be massive, so I will be digging into smaller parts of it over the next few weeks! Enjoy!

Streamlit

Streamlit is a Python module that turns code into shareable web applications. I was quite surprised at how easy it was to pick this up and create an entire front-end to my data. Some Python experience is required for sure, but the learning curve on this specific module is not bad. They have fantastic documentation, and the cheat sheet will be your best friend. I promise.

Streamlit-Authenticator

Streamlit-Authenticator is a secure authentication module to validate user credentials in a Streamlit application. There is a fantastic 2-part blog series written by Mohammad Khorasani, developer of this package, that illustrates how to use this module. You can find them here and here. I believe I ended up using pieces from both parts -- in fact, the entirety of the Login page would not have been possible without his code. Thank you Mohammad!

Building a Login Page

Creating the Authentication YAML File

Place streamlit-auth.yaml in the same directory that you are running your Streamlit application from:

cookie:
  expiry_days: 0
  key: random_signature_key
  name: random_cookie_name
credentials:
  usernames:
    player1:
      email: player1@dnd.com
      name: Player 1
      password: # paste hash from generate_password.py
preauthorized:
  emails:
  - player2@dnd.com
  - player3@dnd.com
  - player4@dnd.com
  - player5@dnd.com
Enter fullscreen mode Exit fullscreen mode

Use this to generate a hashed password to paste into your YAML. The list of email addressed under "preauthorized" allows you to control who is allowed to register via the registration page.

NOTE:

Streamlit-Authenticator throws errors if there is not at least 1 username in the YAML configuration. I hard-coded myself into the YAML and let everyone else use the registration page.

Coding the Login Page

NOTE:

Because I used Streamlit's built-in session state, there is a need to "initialize" variables, which allows session information to persist across pages. I built the "initialize_session_state" function to implement this in a cleaner fashion.

You can set the value of a variable in the session state to that of an existing function in your code. I used this throughout to display pages and note input forms.

st.experimental_rerun() is used to control the flow of your application; running your script from the beginning to reflect changes in session state, variables, etc.

Before we reach the login page, we need to initialize the username variable and load the authentication YAML file into the environment. From there, I display a banner and go right into the page display logic.

### PROGRAM ###

# initializes session state and loads login authentication configuration
initialize_session_state(["username"])
config, authenticator = load_yml()

# displays application title and sets page accordingly
display_image("banner.png")
if not st.session_state.username:
    st.session_state.runpage = app_page1
    st.session_state.runpage()
else:
    st.session_state.runpage = app_page2
    st.session_state.runpage()
Enter fullscreen mode Exit fullscreen mode

Because a username isn't set via the login page yet, the following function will run, displaying the login page:

def app_page1():
    # displays login and registration widgets
    tab1, tab2 = st.tabs(["Login", "Register"])
    # login tab
    with tab1:
        try:
            name,authentication_status,username = authenticator.login("Login","main")
            if authentication_status:
                st.session_state.runpage = "app_page2"
                st.experimental_rerun()
            elif authentication_status == False:
                st.error('Username/password is incorrect')
            elif authentication_status == None:
                st.warning('Please enter your username and password')
        except:
            pass
    # registration tab
    with tab2:
        try:
            if authenticator.register_user('Register', preauthorization=True):
                success('User registered successfully')
                update_yml()
        except Exception as e:
            error_message(e)
Enter fullscreen mode Exit fullscreen mode

This page consist of 2 tabs, Login and Register, used for authenticating into the rest of the application and registering a new user for authentication respectively.

Login Tab

This tab displays a login widget, consisting of a username input, password input, and a submit button, which set values for variables in the session state. One of three things happens in this tab:

  1. If "authentication_status" is empty, you will be prompted to enter your information.
  2. After hitting submit, if your information matches something in the YAML, the "app_page2" function is run, taking you to the rest of the application.
  3. After hitting submit, if your information doesn't match something in the YAML, an error message will appear.
# login tab
    with tab1:
        try:
            name,authentication_status,username = authenticator.login("Login","main")
            if authentication_status:
                st.session_state.runpage = "app_page2"
                st.experimental_rerun()
            elif authentication_status == False:
                error_message('Username/password is incorrect')
            elif authentication_status == None:
                st.warning('Please enter your username and password')
        except:
            pass
Enter fullscreen mode Exit fullscreen mode

Register Tab

This tab displays a registration widget, consisting of the fields required to be set in the YAML and a submit button. Once submitted, if there is something wrong with your input, an error message will appear. If successful, the YAML file will update.

# registration tab
    with tab2:
        try:
            if authenticator.register_user('Register', preauthorization=True):
                success('User registered successfully')
                update_yml()
        except Exception as e:
            error_message(e)
Enter fullscreen mode Exit fullscreen mode

Related Functions

def error_message(text):
    # displays error message
    import time

    error = st.error(text)
    time.sleep(1)
    error.empty()
Enter fullscreen mode Exit fullscreen mode
def initialize_session_state(variable_list):
    # creates empty variables in streamlit session state
    for variable in variable_list:
        if variable not in st.session_state:
            st.session_state[variable] = None
Enter fullscreen mode Exit fullscreen mode
def load_yml():
    # loads login authentication configuration
    import yaml
    from yaml.loader import SafeLoader

    with open(streamlit_path + "streamlit-auth.yaml") as file:
        config = yaml.load(file, Loader=SafeLoader)

    authenticator = stauth.Authenticate(
        config['credentials'],
        config['cookie']['name'],
        config['cookie']['key'],
        config['cookie']['expiry_days'],
        config['preauthorized']
    )

    return config, authenticator
Enter fullscreen mode Exit fullscreen mode
def update_yml():
    # updates login authentication configuration file
    import yaml

    with open(streamlit_path + "streamlit-auth.yaml", 'w') as file:
        yaml.dump(config, file, default_flow_style=False)
Enter fullscreen mode Exit fullscreen mode

Closing Remarks

Next week, I will start digging into app_page2 and what it can accomplish!

Check out the GitHub repo below. You can also find my Twitch account in the socials link, where I will be actively working on this during the week while interacting with whoever is hanging out!

GitHub Repo
Socials

Happy Coding,
Joe

Top comments (0)