A secure authentication module to manage user access in a Streamlit application
- Creating a configuration file
- Setup
- Creating a login widget
- Authenticating users
- Creating a reset password widget
- Creating a new user registration widget
- Creating a forgot password widget
- Creating a forgot username widget
- Creating an update user details widget
- Updating the configuration file
- License
Streamlit-Authenticator is distributed via PyPI:
pip install streamlit-authenticator
Please feel free to test the demo app.
Using Streamlit-Authenticator is as simple as importing the module and calling it to verify your user's credentials.
import streamlit as st
import streamlit_authenticator as stauth
- Initially create a YAML configuration file and define your user's credentials: including names, usernames, and passwords (plain text passwords will be hashed automatically).
- In addition, enter a name, random key, and number of days to expiry, for a re-authentication cookie that will be stored on the client's browser to enable password-less re-authentication. If you do not require re-authentication, you may set the number of days to expiry to 0.
- Finally, define a list of pre-authorized emails of users who can register and add their credentials to the configuration file with the use of the register_user widget.
- Please remember to update the config file (as shown in step 10) after you use the reset_password, register_user, forgot_password, or update_user_details widgets.
credentials:
usernames:
jsmith:
email: jsmith@gmail.com
failed_login_attempts: 0 # Will be managed automatically
logged_in: False # Will be managed automatically
name: John Smith
password: abc # Will be hashed automatically
rbriggs:
email: rbriggs@gmail.com
failed_login_attempts: 0 # Will be managed automatically
logged_in: False # Will be managed automatically
name: Rebecca Briggs
password: def # Will be hashed automatically
cookie:
expiry_days: 30
key: some_signature_key # Must be string
name: some_cookie_name
pre-authorized:
emails:
- melsby@gmail.com
- Please note that the 'failed_login_attempts' and 'logged_in' fields corresponding to each user's number of failed login attempts and log-in status in the credentials will be added and managed automatically.
- Subsequently import the configuration file into your script and create an authentication object.
import yaml
from yaml.loader import SafeLoader
with open('../config.yaml') as file:
config = yaml.load(file, Loader=SafeLoader)
# Pre-hashing all plain text passwords once
# Hasher.hash_passwords(config['credentials'])
authenticator = stauth.Authenticate(
config['credentials'],
config['cookie']['name'],
config['cookie']['key'],
config['cookie']['expiry_days'],
config['pre-authorized']
)
- Plain text passwords will be hashed automatically by default, however, for a large number of users it is recommended to pre-hash the passwords in the credentials using the Hasher.hash_passwords function.
- If you choose to pre-hash the passwords, please set the auto_hash parameter in the Authenticate class to False.
- credentials: dict
- The credentials dict with plain text passwords.
- dict
- The credentials dict with hashed passwords.
- credentials: dict
- Provides the usernames, names, passwords, and emails, and other user data.
- cookie_name: str
- Specifies the name of the re-authentication cookie stored on the client's browser for password-less re-authentication.
- cookie_key: str
- Specifies the key that will be used to hash the signature of the re-authentication cookie.
- cookie_expiry_days: float, default 30.0
- Specifies the number of days before the re-authentication cookie automatically expires on the client's browser.
- pre_authorized: list, optional, default None
- Provides the list of emails of unregistered users who are authorized to register.
- validator: Validator, optional, default None
- Provides a validator object that will check the validity of the username, name, and email fields.
- auto_hash: bool, default True
- Automatic hashing requirement for passwords, True: plain text passwords will be hashed automatically, False: plain text passwords will not be hashed automatically.
- Please remember to pass the authenticator object to each and every page in a multi-page application as a session state variable.
- You can render the login widget as follows.
authenticator.login()
- location: str, {'main', 'sidebar', 'unrendered'}, default 'main'
- Specifies the location of the login widget.
- max_concurrent_users: int, optional, default None
- Limits the number of concurrent users. If not specified there will be no limit to the number of concurrently logged in users.
- max_login_attempts: int, optional, default None
- Limits the number of failed login attempts. If not specified there will be no limit to the number of failed login attempts.
- fields: dict, optional, default {'Form name':'Login', 'Username':'Username', 'Password':'Password', 'Login':'Login'}
- Customizes the text of headers, buttons and other fields.
- captcha: bool, default False
- Specifies the captcha requirement for the login widget, True: captcha required, False: captcha removed.
- clear_on_submit: bool, default False
- Specifies the clear on submit setting, True: clears inputs on submit, False: keeps inputs on submit.
- key: str, default 'Login'
- Unique key provided to widget to avoid duplicate WidgetID errors.
- callback: callable, optional, default None
- Optional callback function that will be invoked on form submission with a dict as a parameter.
- sleep_time: float, optional, default None
- Optional sleep time for the login widget.
- str
- Name of the authenticated user.
- bool
- Status of authentication, None: no credentials entered, False: incorrect credentials, True: correct credentials.
- str
- Username of the authenticated user.
- Please remember to re-invoke an 'unrendered' login widget on each and every page in a multi-page application.
- You can then retrieve the name, authentication status, and username from Streamlit's session state using st.session_state['name'], st.session_state['authentication_status'], and st.session_state['username'] to allow a verified user to access restricted content.
- You may also render a logout button, or may choose not to render the button if you only need to implement the logout logic programmatically.
- The optional key parameter for the logout button should be used with multi-page applications to prevent Streamlit from throwing duplicate key errors.
if st.session_state['authentication_status']:
authenticator.logout()
st.write(f'Welcome *{st.session_state["name"]}*')
st.title('Some content')
elif st.session_state['authentication_status'] is False:
st.error('Username/password is incorrect')
elif st.session_state['authentication_status'] is None:
st.warning('Please enter your username and password')
- button_name: str, default 'Logout'
- Customizes the button name.
- location: str, {'main', 'sidebar', 'unrendered'}, default 'main'
- Specifies the location of the logout button. If 'unrendered' is passed, the logout logic will be executed without rendering the button.
- key: str, default None
- Unique key that should be used in multi-page applications.
- callback: callable, optional, default None
- Optional callback function that will be invoked on form submission with a dict as a parameter.
- Or prompt an unverified user to enter a correct username and password.
- You may also retrieve the number of failed login attempts a user has made by accessing st.session_state['failed_login_attempts'] which returns a dictionary with the username as key and the number of failed attempts as the value.
- You may use the reset_password widget to allow a logged in user to modify their password as shown below.
if st.session_state['authentication_status']:
try:
if authenticator.reset_password(st.session_state['username']):
st.success('Password modified successfully')
except Exception as e:
st.error(e)
- username: str
- Specifies the username of the user to reset the password for.
- location: str, {'main', 'sidebar'}, default 'main'
- Specifies the location of the reset password widget.
- fields: dict, optional, default {'Form name':'Reset password', 'Current password':'Current password', 'New password':'New password', 'Repeat password': 'Repeat password', 'Reset':'Reset'}
- Customizes the text of headers, buttons and other fields.
- clear_on_submit: bool, default False
- Specifies the clear on submit setting, True: clears inputs on submit, False: keeps inputs on submit.
- key: str, default 'Reset password'
- Unique key provided to widget to avoid duplicate WidgetID errors.
- callback: callable, optional, default None
- Optional callback function that will be invoked on form submission with a dict as a parameter.
- bool
- Status of resetting the password.
- Please remember to update the config file (as shown in step 10) after you use this widget.
- You may use the register_user widget to allow a user to sign up to your application as shown below.
- If you require the user to be pre-authorized, set the pre_authorization parameter to True and add their email to the pre_authorized list in the configuration file.
- Once they have registered, their email will be automatically removed from the pre_authorized list in the configuration file.
- Alternatively, to allow anyone to sign up, set the pre_authorization parameter to False.
try:
email_of_registered_user, username_of_registered_user, name_of_registered_user = authenticator.register_user(pre_authorization=False)
if email_of_registered_user:
st.success('User registered successfully')
except Exception as e:
st.error(e)
- location: str, {'main', 'sidebar'}, default 'main'
- Specifies the location of the register user widget.
- pre_authorization: bool, default True
- Specifies the pre-authorization requirement, True: user must be pre-authorized to register, False: any user can register.
- domains: list, optional, default None
- Specifies the required list of domains a new email must belong to i.e. ['gmail.com', 'yahoo.com'], list: the required list of domains, None: any domain is allowed.
- fields: dict, optional, default {'Form name':'Register user', 'Email':'Email', 'Username':'Username', 'Password':'Password', 'Repeat password':'Repeat password', 'Register':'Register'}
- Customizes the text of headers, buttons and other fields.
- captcha: bool, default True
- Specifies the captcha requirement for the register user widget, True: captcha required, False: captcha removed.
- clear_on_submit: bool, default False
- Specifies the clear on submit setting, True: clears inputs on submit, False: keeps inputs on submit.
- key: str, default 'Register user'
- Unique key provided to widget to avoid duplicate WidgetID errors.
- callback: callable, optional, default None
- Optional callback function that will be invoked on form submission with a dict as a parameter.
- str
- Email associated with the new user.
- str
- Username associated with the new user.
- str
- Name associated with the new user.
- Please remember to update the config file (as shown in step 10) after you use this widget.
- You may use the forgot_password widget to allow a user to generate a new random password.
- The new password will be automatically hashed and saved in the configuration file.
- The widget will return the username, email, and new random password which the developer should then transfer to the user securely.
try:
username_of_forgotten_password, email_of_forgotten_password, new_random_password = authenticator.forgot_password()
if username_of_forgotten_password:
st.success('New password to be sent securely')
# The developer should securely transfer the new password to the user.
elif username_of_forgotten_password == False:
st.error('Username not found')
except Exception as e:
st.error(e)
- location: str, {'main', 'sidebar'}, default 'main'
- Specifies the location of the forgot password widget.
- fields: dict, optional, default {'Form name':'Forgot password', 'Username':'Username', 'Submit':'Submit'}
- Customizes the text of headers, buttons and other fields.
- captcha: bool, default False
- Specifies the captcha requirement for the forgot password widget, True: captcha required, False: captcha removed.
- clear_on_submit: bool, default False
- Specifies the clear on submit setting, True: clears inputs on submit, False: keeps inputs on submit.
- key: str, default 'Forgot password'
- Unique key provided to widget to avoid duplicate WidgetID errors.
- callback: callable, optional, default None
- Optional callback function that will be invoked on form submission with a dict as a parameter.
- str
- Username associated with the forgotten password.
- str
- Email associated with the forgotten password.
- str
- New plain text password that should be transferred to the user securely.
- Please remember to update the config file (as shown in step 10) after you use this widget.
- You may use the forgot_username widget to allow a user to retrieve their forgotten username.
- The widget will return the username and email which the developer should then transfer to the user securely.
try:
username_of_forgotten_username, email_of_forgotten_username = authenticator.forgot_username()
if username_of_forgotten_username:
st.success('Username to be sent securely')
# The developer should securely transfer the username to the user.
elif username_of_forgotten_username == False:
st.error('Email not found')
except Exception as e:
st.error(e)
- location: str, {'main', 'sidebar'}, default 'main'
- Specifies the location of the forgot username widget.
- fields: dict, optional, default {'Form name':'Forgot username', 'Email':'Email', 'Submit':'Submit'}
- Customizes the text of headers, buttons and other fields.
- captcha: bool, default False
- Specifies the captcha requirement for the forgot username widget, True: captcha required, False: captcha removed.
- clear_on_submit: bool, default False
- Specifies the clear on submit setting, True: clears inputs on submit, False: keeps inputs on submit.
- key: str, default 'Forgot username'
- Unique key provided to widget to avoid duplicate WidgetID errors.
- callback: callable, optional, default None
- Optional callback function that will be invoked on form submission with a dict as a parameter.
- str
- Forgotten username that should be transferred to the user securely.
- str
- Email associated with the forgotten username.
- You may use the update_user_details widget to allow a logged in user to update their name and/or email.
- The widget will automatically save the updated details in both the configuration file and re-authentication cookie.
if st.session_state['authentication_status']:
try:
if authenticator.update_user_details(st.session_state['username']):
st.success('Entries updated successfully')
except Exception as e:
st.error(e)
- username: str
- Specifies the username of the user to update user details for.
- location: str, {'main', 'sidebar'}, default 'main'
- Specifies the location of the update user details widget.
- fields: dict, optional, default {'Form name':'Update user details', 'Field':'Field', 'Name':'Name', 'Email':'Email', 'New value':'New value', 'Update':'Update'}
- Customizes the text of headers, buttons and other fields.
- clear_on_submit: bool, default False
- Specifies the clear on submit setting, True: clears inputs on submit, False: keeps inputs on submit.
- key: str, default 'Update user details'
- Unique key provided to widget to avoid duplicate WidgetID errors.
- callback: callable, optional, default None
- Optional callback function that will be invoked on form submission with a dict as a parameter.
- bool
- Status of updating the user details.
- Please remember to update the config file (as shown in step 10) after you use this widget.
- Please ensure that the configuration file is re-saved anytime the credentials are updated or whenever the reset_password, register_user, forgot_password, or update_user_details widgets are used.
with open('../config.yaml', 'w') as file:
yaml.dump(config, file, default_flow_style=False)
This project is proprietary software. The use of this software is governed by the terms specified in the LICENSE file. Unauthorized copying, modification, or distribution of this software is prohibited.