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

Dear @Gedanggoreng currently streamlit_authenticator has no provisions for sending an email to the user with a forgotten username/password. The widget will return the email associated with the forgotten username/password and you will have to arrange for that information to be sent to the user’s email yourself.

How did you resolve this issue ?
I am having the same problem

As mentioned previously - currently streamlit_authenticator has no provisions for sending an email to the user with a forgotten username/password. The widget will return the email associated with the forgotten username/password and you will have to arrange for that information to be sent to the user’s email yourself.

I’m getting: “Username/password is incorrect” every time. I’m printing some variables to have a better understanding, but I’m not able to fix it:

I can share everything because I’m just starting with this. I’m trying to log with:
Login: me@mail.com
Password: 123

config.yaml

credentials:
  usernames:
    myself:
      email: me@mail.com
      name: Me
      password: $2b$12$AlQ63O8OQMQ0Fu/8/6jyYudiDI2MQtXVgV/m1uffW0PPJ4kNXCwLS
cookie:
  expiry_days: 30
  key: some_signature_key
  name: some_cookie_name
preauthorized:
  emails:
  - me@mail.com

main.py

import streamlit as st
import css
import prompts
import model
import yaml
import os
import streamlit_authenticator as stauth
from streamlit_authenticator import Authenticate
from yaml import SafeLoader

#####

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

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

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

##### Some defs #####

if authentication_status:
  authenticator.logout('Logout', 'main')
  with st.sidebar:
    ### UI ELEMENTS
elif authentication_status is False:
    st.error('Username/password is incorrect')
elif authentication_status is None:
    st.warning('Please enter your username and password')

pip list | grep streamlit

extra-streamlit-components   0.1.56
streamlit                    1.17.0
streamlit-authenticator      0.2.1

Hash generator

print(stauth.Hasher(['123']).generate())

Please enter the username i.e. myself, not the email.

It worked, I’m such a newbie :sweat_smile:

Thank you.

I got the same error, and for me its because I didn’t put the hashed password in the config.yaml file (running the code doesn’t mean you already stored the hashed password, you need to copy paste it into the config.yaml file)
image

Let me know, if this helps you :slight_smile: .

Screenshot 2023-03-15 172506

Is it possible to use password only w/o names & username?

No, both of these fields are required at the moment.

Hello, thank you for your amazing streamlit component. I wonder if there is a simple way to automatically log out after some time of inactivity (ex: 30min of inactivity).

Hi @AnthonyBrossault, you are most welcome. That is an excellent idea for a subsequent release, please feel free to add it to the GitHub list of issues and I will try to implement it in the near future. In the meantime, you can try to hack it yourself by clearing the session state variable associated with the authentication object after a set period of time, and removing associated reauthentication cookies saved on the browser. Removing keys from session state should be as straightforward as writing del st.session_state[‘authentication_status’]. You can read more about clearing the cookies here. To measure the passage of time you can make use of a library such as Streamlit Autorefresh. Cheers.

You can use any Gmail account for this by creating and using and app password to your email account as in the following link (https://support.google.com/accounts/answer/185833?visit_id=638215606016109598-1972104324&p=InvalidSecondFactor&rd=1). Once you’ve done it, use the following code:

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

def send_password_email(sender_email, sender_password, recipient_email, password):
    # Gmail SMTP server configuration
    smtp_server = 'smtp.gmail.com'
    smtp_port = 587

    # Email content
    subject = 'Secure Password Delivery'
    body = f'Your password is: {password}'

    # Create a multipart email message
    message = MIMEMultipart()
    message['Subject'] = subject
    message['From'] = sender_email
    message['To'] = recipient_email

    # Attach the email body as plain text
    message.attach(MIMEText(body, 'plain'))

    try:
        # Establish a secure connection to the SMTP server
        server = smtplib.SMTP(smtp_server, smtp_port)
        server.starttls()
        # Login to the SMTP server with the application-specific password
        server.login(sender_email, sender_password)
        # Send the email
        server.sendmail(sender_email, recipient_email, message.as_string())
        # Close the SMTP connection
        server.quit()
        print('Email sent successfully.')
    except Exception as e:
        print(f'Error occurred while sending the email: {e}')

# Example usage
sender_email = 'your_sender_email@gmail.com'
sender_password = 'your_application_specific_password_generated by Gmail'
recipient_email = 'recipient_email@example.com'
password = 'password_to_send'

send_password_email(sender_email, sender_password, recipient_email, password)

Can you explain why do i have to press the “Registre-se” button twice fo the login form desapear?

import pandas as pd  # pip install pandas openpyxl
import plotly.express as px  # pip install plotly-express
import streamlit as st  # pip install streamlit
import streamlit_authenticator as stauth  # pip install streamlit-authenticator

import database as db


# emojis: https://www.webfx.com/tools/emoji-cheat-sheet/
st.set_page_config(page_title="Sales Dashboard", page_icon=":bar_chart:", layout="wide")


# --- USER AUTHENTICATION ---
users = db.fetch_all_users()

usernames = [user["key"] for user in users]
names = [user["name"] for user in users]
hashed_passwords = [user["password"] for user in users]

credentials = {"usernames": {}}

for un, name, pw in zip(usernames, names, hashed_passwords):
    user_dict = {"name": name, "password": pw}
    credentials["usernames"].update({un: user_dict})


authenticator = stauth.Authenticate(
        credentials=credentials, cookie_name="sales_dashboard", key="abcdef", cookie_expiry_days=30
    )

if 'togle_login_register' not in st.session_state:
    st.session_state['togle_login_register'] = "Login"

if st.session_state['togle_login_register'] == "Login": 
    name, authentication_status, username = authenticator.login("Login", "main")

    if st.session_state['authentication_status'] == False:
        st.error("Username/password is incorrect")

    #if st.session_state['authentication_status'] == None:
        #st.warning("Please enter your username and password")

    registro = st.button("Registre-se")

    if registro:
        st.session_state['togle_login_register'] = "Register"
        

if st.session_state['togle_login_register'] == "Register":
    try:
        if authenticator.register_user('Register user', preauthorization=False):
            st.success('User registered successfully')
    except Exception as e:
        st.error(e)
    ja_registrado = st.button("Já sou Registrado")
    if ja_registrado:
        st.session_state['togle_login_register'] = "Login"```

I correct the issue with st.experimental_rerun(). Poblaby the change in may state variable togle_login_register was not trigering a rerun.

 if registro:
        st.session_state['togle_login_register'] = "Register"
        st.experimental_rerun()

Actualy i’ve learned that a better solution is to use a callback funtion in th button:

def handle_click_register():
    """Callback funtion para o botão registre-se"""
    st.session_state['togle_login_register'] = "Register"

registro = st.button("Registre-se", on_click=handle_click_register)

I am having an issue where I try to log in but nothing happens, I get on console “Invalid salt”

Screenshot 2023-08-04 at 5.03.17 pm

might someone have an idea of what could be happening?
I am using these versions
altair==5.0.1

attrs==23.1.0

bcrypt==4.0.1

blinker==1.6.2

cachetools==5.3.1

certifi==2023.7.22

charset-normalizer==3.2.0

click==8.1.6

decorator==5.1.1

extra-streamlit-components==0.1.56

gitdb==4.0.10

GitPython==3.1.32

idna==3.4

importlib-metadata==6.8.0

Jinja2==3.1.2

jsonschema==4.18.6

jsonschema-specifications==2023.7.1

markdown-it-py==3.0.0

MarkupSafe==2.1.3

mdurl==0.1.2

numpy==1.25.2

packaging==23.1

pandas==2.0.3

Pillow==9.5.0

protobuf==4.23.4

pyarrow==12.0.1

pydeck==0.8.0

Pygments==2.15.1

PyJWT==2.8.0

Pympler==1.0.1

python-dateutil==2.8.2

pytz==2023.3

pytz-deprecation-shim==0.1.0.post0

PyYAML==6.0.1

referencing==0.30.0

requests==2.31.0

rich==13.5.2

rpds-py==0.9.2

six==1.16.0

smmap==5.0.0

streamlit==1.25.0

streamlit-authenticator==0.2.2

tenacity==8.2.2

toml==0.10.2

toolz==0.12.0

tornado==6.3.2

typing_extensions==4.7.1

tzdata==2023.3

tzlocal==4.3.1

urllib3==2.0.4

validators==0.20.0

zipp==3.16.2

Please replace the plain text passwords for jsmith and rbriggs with their hashed equivalents.

1 Like

Hi! Have you solve this issue? I have the same problem and I have no idea how to solve it. I hope you can tell me how you handle this problem

Please make sure that you have replaced the plain text passwords in your config file with their hashed passwords.

Guys, I need help. I’m trying to use streamlit authenticator in my code. The examples I found part of a yaml or toml file. I’m just trying to pull this from my mysql table.

The database is running locally.

I’ll leave my script below. I believe I need to make a few adjustments to be able to add the streamlit_authenticator to it. Just remembering the way it is, he is logging in, but there is no logout button, and the name of the logged in user’s account does not appear either.
If you can help me, I would be very grateful. Thank you very much.

Script below

import streamlit as st
import hashlib
import mysql.connector
import pandas as pd
import requests
import pages.Despesa as despesa
import pages.Dashboard as dashboard
import pages.Investimentos as investimento
import pages.Renda as renda
import pages.Registro as registro
from streamlit_option_menu import option_menu
import streamlit_authenticator as stauth

Define a configuração da página antes de qualquer outro comando Streamlit

st.set_page_config(page_title=“MyMoney App”, layout=“wide”, initial_sidebar_state=“expanded”)

api para verificação de email
HUNTER_API_KEY = “d376657ff8a9625db293fccb838bb2cbe45ffef8”

def create_usertable():
config = {
‘user’: ‘root’,
‘password’: ‘airflow’,
‘host’: ‘172.17.0.2’,
‘database’: ‘financeiro’,
‘raise_on_warnings’: True
}
connection = mysql.connector.connect(**config)
cursor = connection.cursor()

# Verifica se a tabela 'users' já existe
cursor.execute("SHOW TABLES LIKE 'users'")
table_exists = cursor.fetchone()

if not table_exists:
    cursor.execute("CREATE TABLE users(id SERIAL PRIMARY KEY,usuario TEXT, email VARCHAR(255), senha TEXT, data_criacao TIMESTAMP)")

cursor.close()
connection.close()

def add_userdata(usuario, email, senha):
config = {
‘user’: ‘root’,
‘password’: ‘airflow’,
‘host’: ‘172.17.0.2’,
‘database’: ‘financeiro’,
‘raise_on_warnings’: True
}
connection = mysql.connector.connect(**config)
cursor = connection.cursor()
cursor.execute(“INSERT INTO users (usuario, email, senha, data_criacao) VALUES (%s, %s, %s, NOW())”, (usuario, email, senha))
connection.commit()
cursor.close()
connection.close()

def login_user(email, senha):
config = {
‘user’: ‘root’,
‘password’: ‘airflow’,
‘host’: ‘172.17.0.2’,
‘database’: ‘financeiro’,
‘raise_on_warnings’: True
}
connection = mysql.connector.connect(**config)
cursor = connection.cursor()
cursor.execute(“SELECT * FROM users WHERE email = %s AND senha = %s”, (email, senha))
data = cursor.fetchall()
cursor.close()
connection.close()
return data

def make_hashes(senha):
return hashlib.sha256(str.encode(senha)).hexdigest()

def check_hashes(senha, hashed_text):
if make_hashes(senha) == hashed_text:
return hashed_text
return False

def verify_email(email):
url = f"https://api.hunter.io/v2/email-verifier?email={email}&api_key={HUNTER_API_KEY}"
response = requests.get(url)
if response.status_code == 200:
data = response.json()
if data[‘data’][‘result’] == ‘undeliverable’:
return True
return False

print title of web app

st.title(“MyMoney - Aplicativo de Finanças e Investimento”)
st.markdown(“Esse é um protótipo. Erros e bugs podem acontecer.”)

class MultiApp:

def __init__(self):
    self.apps = []

def add_app(self, title, func):

    self.apps.append({
        "title": title,
        "function": func
    })

def main():
“”“Simple Login App”“”

menu = ["Login", "SignUp"]
choice = st.sidebar.selectbox("Menu", menu)

if choice == "Login":
    st.subheader("Login Section")

    email = st.sidebar.text_input("E-mail do usuário")
    if email:
        if verify_email(email):
            st.write(f"{email} é um endereço de e-mail válido!")
        else:
            st.write(f"{email} não é um endereço de e-mail válido.")
    senha = st.sidebar.text_input("Password", type='password')
    if st.sidebar.checkbox("Login"):
        create_usertable()
        hashed_pswd = make_hashes(senha)

        result = login_user(email, check_hashes(senha, hashed_pswd))
        if result:
            st.success("Logged In as {}".format(email))

            selected = option_menu(menu_title=None,
                                   options=["Dashboard", "Receitas", "Despesas", "Investimentos", "Registro"],
                                   orientation="horizontal",)
                                   
            #task = st.selectbox("Task", ["Dashboard", "Receitas", "Despesas", "Investimentos", "Registro"])
            if selected == "Dashboard":
                dashboard.app()
            elif selected == "Receitas":
                renda.app()
            elif selected == "Despesas":
                despesa.app()
            elif selected == "Investimentos":
                investimento.app()
            elif selected == "Registro":
                registro.app()
        else:
            st.warning("E-mail ou senha incorreto")

elif choice == "SignUp":
    st.subheader("Criar Nova Conta")
    usuario = st.text_input("Usuário")
    email = st.text_input("E-mail")
    if email:
        if verify_email(email):
            st.write(f"{email} é um endereço de e-mail válido")
        else:
            st.write(f"{email} não é um endereço de e-mail válido.")
    senha = st.text_input("Senha", type='password')

    if st.button("Criar Conta"):
        create_usertable()
        add_userdata(usuario, email, make_hashes(senha))
        st.success("Você criou com sucesso uma conta válida")
        st.info("Vá para o menu Login para fazer o login")

if name == ‘main’:
main()