New Component: Streamlit-Authenticator, a secure authenticaton module to validate user credentials in a Streamlit application

Hi @mkhorasani,

Thanks for this great component. I have one question:
In current implementation i would need to provide all users and passwords, and etc. in order to be able to login with the credentials. Is possible to ask for them after typing username and password?
This would be great if we have a lot of users and we would not need to fetch all of them, but just checking the one if it is present in DB or other types of doc where someone stores them.

Hey @mkhorasani! Been working on this for a week now and I keep getting False for the authentication status. The name also is None. I’ve done all kinds of debugging, no luck so far. It’s giving me a headache! Would appreciate it if you could help!

I’m reading credentials from a local database with this code:

# Get the records from database
subs = db.fetch_all_records()
usernames = [user['key'] for user in subs]
names = [user['name'] for user in subs]
hashed_passwords = [user['password'] for user in subs]
emails = [user['email'] for user in subs]

And then converting the format and passing them to the functionlike below:

# Define Cookie
cookie = {'key':'cookie_key', 'name':'cookie_name', 'expiry_days':3}

# Turn `credentials` into a dictionary acceptable by `streamlit_authenticator.Authenticate`
credentials = {
    'usernames': {
        username: {
            'email': email,
            'name': name,
            'password': password
        } 
        for username, email, name, password in zip(usernames, emails, names, hashed_passwords)
    }
}
# Define preauthorized users
preauthorized = {'emails': ['sample@gmail.com']}

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


# Debugging output
st.write("Step 1 - Input credentials:", credentials)
st.write("Step 2 - Hashed passwords:", hashed_passwords)

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

# More debugging output after authentication
st.write("Step 3 - Authentication result:")
st.write("   Name:", name)
st.write("   Authentication Status:", authentication_status)
st.write("   Username:", username)

The output is as below after I enter username = testuser and password=abc123 :point_down:

username is correct, it must be the password=abc123, that is wrong. Try to verify that value.

I tried to simulate using sqlite3 and it works just fine.

1 Like

Thank you for the reply @ferdy.
It made me suspicious; that this might just be one of those situations, so…
I just reinstalled streamlit-authenticator and it’s working now! :smiley:

I worried a lot when you say, you have been working for a week on that issue.

1 Like

Right!? I mean the project itself took about 30 minutes and this step was just not working and everything seemed to be fine! That’s why I love errors! They just tell you what they want from you LOL

Hi, thanks for creating this component. Just curious how well does it scale with users ? Can we store additional information in the yaml file ?

edit: is this component compatible with a db ?

@mkhorasani great component! I love it…

I do have a question however, is it possible to add additional values to the yaml file, i’d like to add 2 more fields and save them to a st.session_state key to access them later, even though everything ‘works’ as intended I can’t seem to retrieve the yaml past the ‘usernames’ field. How to access the values once a user has logged in to get these specific fields.

It might be an easy answer but I can’t seem to figure it out and Im really struggling here… seems like I need to update my python skills once more! :wink:

Hi @Quadster thank you for reaching out. I’m not sure if I understood your question correctly, can you please elaborate or share your code? If you are trying to access values past the usernames field, I guess it would be as such: config[‘credentials’][‘usernames’][][] - I don’t see why this would throw any problems.

credentials:
  usernames:
    user1:
      email: emailadress1
      name: name1
      test: something
      password: password hash
    user2:
      email: emailadress2
      name: name2
      test: something2
      password: password hash 

If I try to acces ‘test’ after the user logged in it won’t let me.

print(config['credentials']['usernames'][]['test'])

throws me a syntax error

SyntaxError: invalid syntax. Perhaps you forgot a comma?

When done directly as

config['credentials']['usernames']['test']

it throws a KeyError;

KeyError: 'test'

I hope this makes it more clear and not more confusing…

You would have to also specify the username:

print(config['credentials']['usernames']['user1']['test'])

Yes, but that would mean I have to specify it for every user in the yaml file to get the ‘test’ value. Isn’t there a way to take a blank [ ] for it somehow?

You can access the username of any logged in user with the following:

st.session_state["username"]

Then use it as follows:

print(config['credentials']['usernames'][st.session_state["username"]]['test'])

I have the following bug, every time I login in I am able to click trough the different pages using this on the main page:

with open(‘config.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’]
)
name, authentication_status, username = authenticator.login(location=“main”)
st.write(name, authentication_status, username)
st.session_state[“authentication_status”] = authentication_status

if st.session_state[“authentication_status”]:
print(“Hello world”)

and then on every page only the part:

if st.session_state[“authentication_status”]:
print(“Hello world”)

The problem is when I reload the page completely by clicking on the browsers reload arrows, I get the following error:

KeyError: ‘st.session_state has no key “authentication_status”. Did you forget to initialize it? More info: Add statefulness to apps - Streamlit Docs

I go to reduce the amount of times this message was delivered by adding the full login module again on every page:

with open(‘config.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’]
)
name, authentication_status, username = authenticator.login(location=“main”)
st.write(name, authentication_status, username)
st.session_state[“authentication_status”] = authentication_status

if st.session_state[“authentication_status”]:
print(“Hello world”)

Never the less, every now and then I still get the error:

KeyError: ‘st.session_state has no key “authentication_status”. Did you forget to initialize it? More info: Add statefulness to apps - Streamlit Docs

Any work arounds? to my feeling streamlit, when sometimes reloading a page completely, struggles to get the correct authentication status? any opinions?

Not that one. What I did is define an account.py page in pages. On this page I write the authentication.

For other pages, if users reload the page, I just switch the page to account.py page. Something like this.

I just managed to get it right apparently by wrapping it like this:


if "authentication_Status" not in st.session_state:
    with open('/config.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']
    )
    name, authentication_status, username = authenticator.login(location="main")
    st.write(name, authentication_status, username)
    st.session_state["authentication_status"] = authentication_status

I wrapped the whole thing in a function and put it in a separate module and I just call the module in every page centralising the whole thing now. Works like a charm so far:

main.py or any page:

import sys
sys.path.insert(1, "/Project folder")
from modules import module_login

if "authentication_Status" not in st.session_state:
    module_login.login()

module_login.py:

import yaml
from yaml.loader import SafeLoader
import streamlit_authenticator as stauth
import streamlit as st


def login():
    with open('/config.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']
    )
    name, authentication_status, username = authenticator.login(location="main")
    st.write(name, authentication_status, username)
    st.session_state["authentication_status"] = authentication_status

def main():
    print("Modules called!")

if __name__ == "__main__":
    main()
1 Like

I am having issues to add a user or update passwords.
This works fine for checking if I already have created a user manually but not for the rest of the functions:

import streamlit_authenticator as stauth
import yaml
from yaml.loader import SafeLoader
import streamlit as st

def authenticate_before_showing_main_content():
    user_config_path = "config/credentials.yml"
    with open(user_config_path) as file:
        config = yaml.load(file, Loader=SafeLoader)

    authenticator = stauth.Authenticate(
        config["credentials"],
        config["cookie"]["name"],
        config["cookie"]["key"],
        config["cookie"]["expiry_days"],
        config["pre-authorized"],
    )
    authenticator.login()
    if st.session_state["authentication_status"]:
        st.toast(f'Welcome *{st.session_state["name"]}*')
        st.write('#Some content')
        authenticator.logout(location="sidebar", key="authentication_logout")
        # Reset password
        reset_pwd_button = st.sidebar.button("Reset Password")
        if reset_pwd_button:
            try:
                if authenticator.reset_password(
                    st.session_state["username"],
                    location="sidebar",
                ):
                    st.sidebar.success("Password modified successfully")

            except Exception as e:
                st.error(e)
with open(user_config_path, "w", encoding='utf-8') as file:
        yaml.dump(config, file, default_flow_style=False)

but it’s not doing the trick (something very similar with adding a new user with a pre-authorized email). I hit the reset_pwd_button and it removes the information as if things had worked (although I do not see the success mesage) and nothing is written to the .yml file
Could you explain me a little bit better how to make use of the widgets to reset passwrods and add users??
I have installed pyyaml and have tried a couple of things but to no avail.

It is all there in the readme.