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/
8 Likes

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.

1 Like

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
2 Likes

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

1 Like

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.

1 Like

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

1 Like

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.

1 Like

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

1 Like

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

1 Like

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.

1 Like

Install the authenticator.

pip install streamlit-authenticator
1 Like

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?

1 Like

Could you post the full exception message or traceback?

1 Like


Hi, @ferdy this is the error

1 Like

Just to be sure: is it running on Streamlit Cloud or not? If not, Iā€™d just double check locally again activating the environment, lunching python and then trying to import streamlit-authenticator.

1 Like

Hello! I am using a Multipage app, with a main.py where login occurs and in the pages folder there are all the pages: 1_dashboard.py, 2_model.py, etcā€¦ I would like to invoke all the pages a user can access depending on the type of profile. A user may access all pages but others should only access maybe a restricted amount of pages. Is there any way of doing it? Thank you!

1 Like

Hello @mkhorasani, Iā€™m also use your package streamlit-authenticator.

  1. I want to add a link of Forgot Password into Login widget, It is possible to do? If yes then How?
  2. I Also want to create button of create new user widget but it is not working yet. currently st.markdown is working for that but it is not looking good for ideal UI.
    Please reply me. Thanks
1 Like

You can restrict selective pages using following steps:

  1. In selective page import from main import obj1
  2. Then you can use obj1 status inside if else condition to allow users or not.

Hi @Jitendra_Bhavsar, adding the forgot password link to the login form is an excellent idea, this is something that I can implement in a future release, please feel free to raise the idea on the GitHub repository for streamlit-authenticator. Iā€™m not exactly what the second issue is, if possible please share your source code.

2 Likes

Sure, my second point code is

    if st.button("Register New User"):
        try:
            if authenticator.register_user("Register user", preauthorization=False):
                st.success("User registered successfully")
        except Exception as e:
            st.error(e)

        with open("config.yaml", "w") as file:
            yaml.dump(config, file, default_flow_style=False)
1 Like