Streamlit-Authenticator, Part 1: Adding an authentication component to your app

How to securely authenticate users into your Streamlit app

Posted in Community, December 6 2022

Hey, community! 👋

My name is Mohammad Khorasani, and I’m a co-author of the book Web Application Development with Streamlit (Streamlit helped me get into full-stack development, so it was only fair to spread the word about it).

As developers, we often require features that are yet to be made natively. For me, that was implementing user authentication and privileges in a client-related project that eventually grew into a full-fledged package aptly named Streamlit-Authenticator.

Specifically, my client asked for the ability to authenticate users with different privilege levels for their business needs, as well as a whole host of other features. That’s what prompted me into developing this package. While authentication is definitely needed for some apps—especially corporate ones—it's great to make your apps accessible to the community whenever possible to share and spread the learnings!

In this two-part tutorial, you’ll learn:

  • How to install Streamlit-Authenticator
  • How to hash user passwords
  • How to create a login widget
  • How to authenticate users
  • How to implement user privileges

How to install Streamlit-Authenticator

Let’s start by creating a login form to authenticate a list of predefined users. Install the component by using the following command:

pip install streamlit-authenticator

Next, import the component into your Python script:

import streamlit_authenticator as stauth

How to hash user passwords

It’s absolutely vital to hash any password that will be stored on a disk, database, or any other medium. Here, you’ll be defining your users’ credentials in a YAML file.

You’ll also define several other configuration settings pertaining to the key and expiry date of the re-authentication JWT cookie. If you don’t require passwordless re-authentication, just set the expiry_days to 0.

You can also define a preauthorized list of users who can register their usernames and passwords (I’ll cover this in the next post in this series).

Step 1. Create the YAML file:

credentials:
  usernames:
    jsmith:
      email: jsmith@gmail.com
      name: John Smith
      password: '123' # To be replaced with hashed password
    rbriggs:
      email: rbriggs@gmail.com
      name: Rebecca Briggs
      password: '456' # To be replaced with hashed password
cookie:
  expiry_days: 30
  key: random_signature_key
  name: random_cookie_name
preauthorized:
  emails:
  - melsby@gmail.com

Step 2. Use the Hasher module to convert your plain text passwords into hashed passwords:

hashed_passwords = stauth.Hasher(['123', '456']).generate()

Step 3. Replace the plain text passwords in the YAML file with the generated hashed passwords.

Now that you’ve defined your users’ credentials and configuration settings, you’re ready to create an authenticator object.

Step 1. Import the YAML file into your script:

with open('../config.yaml') as file:
    config = yaml.load(file, Loader=SafeLoader)

Step 2. Create the authenticator object:

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

Step 3. Render the login widget by providing a name for the form and its location (i.e., sidebar or main):

name, authentication_status, username = authenticator.login('Login', 'main')

How to authenticate users

Once you have your authenticator object up and running, use the return values to read the name, authentication_status, and username of the authenticated user.

You can ppt-in for a logout button and add it as follows:

if authentication_status:
    authenticator.logout('Logout', 'main')
    st.write(f'Welcome *{name}*')
    st.title('Some content')
elif authentication_status == False:
    st.error('Username/password is incorrect')
elif authentication_status == None:
    st.warning('Please enter your username and password')

Or you can access the same values through a session state:

if st.session_state["authentication_status"]:
    authenticator.logout('Logout', 'main')
    st.write(f'Welcome *{st.session_state["name"]}*')
    st.title('Some content')
elif st.session_state["authentication_status"] == False:
    st.error('Username/password is incorrect')
elif st.session_state["authentication_status"] == None:
    st.warning('Please enter your username and password')

You can also alter the user if their credentials are incorrect:

After logging out, the authentication_status will revert back to None and the authentication cookie will be removed.

How to implement user privileges

Given that the authenticator object returns the username of your logged-in user, you can utilize that to implement user privileges where each user receives a more personalized experience as shown below:

name, authentication_status, username = authenticator.login('Login', 'main')
if authentication_status:
    authenticator.logout('Logout', 'main')
    if username == 'jsmith':
        st.write(f'Welcome *{name}*')
        st.title('Application 1')
    elif username == 'rbriggs':
        st.write(f'Welcome *{name}*')
        st.title('Application 2')
elif authentication_status == False:
    st.error('Username/password is incorrect')
elif authentication_status == None:
    st.warning('Please enter your username and password')

As you can see, each user is redirected to a separate application to cater to that specific user’s needs.

Wrapping up

I hope you feel confident now about securely authenticating users into your Streamlit application. In subsequent posts, you’ll learn how to register new users, reset usernames/passwords, and update user data.

In the meantime, feel free to read more about this component in our book Web Application Development with Streamlit. And if you have any questions, please leave them in the comments below or contact me on LinkedIn.

Happy Streamlit-ing! 🎈


This is a companion discussion topic for the original entry at https://blog.streamlit.io/streamlit-authenticator-part-1-adding-an-authentication-component-to-your-app/
1 Like

Hey Mohammad,

I’m very new to streamlit/programming, so apologies in advance if what I’m asking is obvious.

I’m using the Streamlit Authenticator with a Deta database for a project. My question is regarding the hasher. I’ve found that when creating a new user in the database like this:

#start of code snipprt
email=['brackethashed@email.com']

username=['brackethashed']

firstname = ['hashed']

surname= ['hashed']

password=['password']

admin= ['True']

approved=['True']

hashed_password= stauth.Hasher(password).generate()

for (email, username, firstname, surname, admin, approved, hashed_password ) in zip(email, username, firstname, surname, admin, approved, hashed_password):

db.insert_user(email, username, firstname, surname, admin, approved, hashed_password)
#end of code snippet

is successful and the user can log in from the UI. The pasword is stored as a string like so: $2b$12$N9S/5MvUWul23adjozgQPeEg/D95QLxNfPuGusiJbVwXWtSAjhxuW.

However, when the brackets and zip are removed, the password is stored as an array

#start of code snippet
email='tostring@email.com'

username='tostring'

firstname = 'zipbracket'

surname= 'hashed'

password='password'

admin= 'True'

approved='True'

hashed_password= stauth.Hasher(password).generate()

db.insert_user(email, username, firstname, surname, admin, approved, hashed_password)
#end of code snippet.

the password gets stored like this:

"[
  "$2b$12$tt3dVgrDPUNAzKSuEOZ21OuJbZzQv0slQuB00lzicv8.hKig3LXA2",
  "$2b$12$Bt3My1MwTEU2hVHFkcZfn.rxW2TuoC6TKGiib4lc9nz44D4S23XBC", etc]

and the login module cannot decode the password.
The other fields are stored correctly as strings.

I have a sign up page that successfully inserts all the fields in the db except the hashed password. I guess I’m not exactly clear on how to ensure the hashed password is stored correctly without the use of an array and zip.

Apologies in advance for the long winded question. As I said, I’m very new to streamlit and deta. I’ve spent a few days trying to figure out the issue and thought I’d reach out and ask.

Thanks for taking the time to read this.

Update: I’ve found a “workaround” for this…

Process the user intput text (simulated here by the ‘password’ variable) as follows:

#code snippet start
email='strpassaddbrack@email.com'
username='strpassaddbrack'
firstname = 'replacedstr4brack'
surname= 'hashed'
password='1234'
bracketPass=[password]
admin= 'True'
approved='True'
hashed_password= stauth.Hasher(bracketPass).generate()
strPass=str(hashed_password)
removeopenbrack=strPass.replace("[", "")
removeclosebrack=removeopenbrack.replace("]", "")
final=removeclosebrack.replace("'","")

db.insert_user(email, username, firstname, surname, admin, approved, final)

#code snippet end

The above treatment of the password will store it in deta db and the login module is able to process it.

for reference the insert_user method is:

#insert user method start
def insert_user(email, username, firstname, surname, admin, approved, hashed_password):
    """adding user"""
    #defining the email as the key
    return db.put({"key":email, "username": username, "firstname": firstname, "surname":surname, "admin":admin, "approved": approved,"password": hashed_password })
#insert user method end
1 Like

hi.
Mohammad,
When is the follow up post coming? I’m looking forward to it.

In this guide, you are using:

authenticator.logout('Logout', 'main')

While demonstrating how to use the authentication on other pages, should you define an authenticator instance on each page? how do you carry this object to other pages? I have no issues using the session state to check for authentication status.

Apologies if the question is a bit dumb.

Hi @Gyunald, hopefully before the end of January. Stay tuned!

If your authenticator object is instantiated in your main script then there will be no need to re-instantiate it in other pages. The way I usually develop multi-page apps in Streamlit is by creating a main script/page through which other sub-pages are invoked. This main script is also where the authenticator object is created, which means that all sub-pages will have access to it as well.

If you are using a different multi-page architecture, then I guess you can commit your authenticator object to session state and read it from other pages.

Is it possible to customize the widgets ? for example change widget form labels

Hi @Kiran_Jilla, yes you may modify the main label for each widget. More customized labels however are not currently supported.

I have an error which is as I’ve pasted below:

ImportError: cannot import name ‘streamlit_authenticator’ from ‘streamlit’ (/home/john/.local/share/virtualenvs/myproject-eA_FYPnA/lib/python3.10/site-packages/streamlit/init.py)

I’ve tried to update streamlit to the update version but still it persist. Can any one help me out?

Thank you for your time.

Install the authenticator.

pip install streamlit-authenticator

Hi - I have installed streamlit-authenticator and have verified that it is in my conda environment using conda list. However, I get an error ModuleNotFoundError: No module named ‘streamlit_authenticator’.

I am sure I have activated the environment as well before running the python script. It fails instantly on the import statement.

The version of streamlit-authenticator I have pip installed is 0.2.1.

Any ideas?

Could you post the full exception message or traceback?