I am having issue with my Streamlit app and not sure what am I missing. My app requires login at the startup and during the login process, it checks to see if the MFA has been activated for the account or not. In case the MFA is not activated, it will show the user a QR code to scan on Authenticator app to add the account then have the user enter the code from Authenticator app to activate the MFA for the account.
The app works up to the point of showing the QR code and accepting the code from the Authenticator app but skips executing the code from the if condition onwards on line 24.
Any ideas what am I missing here? I have validated the values of the EnableMFA variable and it is correct so If condition should be executing but its not.
Here is the screen recording for the app behavior. After entering the MFA code, it goes back to the login screen and wouldn’t let me click the login butoon anymore. Only refreshing the screen from the browser will allow to interact with the app again.
Without the rest of your code, I can only guess: Do you have nested buttons?
If you have something like this, that could be your problem:
if st.button("Login"):
activate_mfa()
Your activate_mfa() function uses is st.form_submit_button(). As soon as you click the form submit button, the app (or dialog/fragment containing it) reruns. That makes the original Login button become False, so all the things dependent on the form submit button don’t have a chance to render on that rerun.
Check the scopes of your forms and fragments to make you don’t have nested buttons. If not, can you share more of your code?
Thanks for the response. Below is the full code for the login flow.
import streamlit as st
import dblib as db
from sqlalchemy import text, case, update
import otp, qr
def activate_mfa(login):
if 'data' not in st.session_state:
st.session_state.sec_key = otp.generate_secret()
## Above line returns an alphanumeric 16 character string that can be used for the QR code or manually adding to google authenticator app.
st.session_state.data = f'otpauth://totp/{login}?secret={st.session_state.sec_key}&issuer=timetracker'
st.write(f'MFA activation is required. Please scan the QR code with Authenticator app or you can manually add account with {st.session_state.sec_key} key')
col1,col2,col3 = st.columns([.25,.5,.25])
with col2:
img = qr.create_qr_code(data=st.session_state.data)
st.image(img)
with st.form('mfa',enter_to_submit=False):
c1,c2 = st.columns([.6,.4],vertical_alignment="bottom")
with c1:
EnableMFA = st.text_input("Enter OTP code from your app",key='k_enablemfacode')
with c2:
submit = st.form_submit_button('Activate MFA')
st.write(f'Session State -> {st.session_state}')
st.write(f'MFA Submit -> {submit}')
if 'MFAStatus' not in st.session_state and len(EnableMFA) == 6:
st.write(f'Key-> {st.session_state.sec_key} Data -> {st.session_state.data} OTP -> {EnableMFA}')
st.write('Validating otp...')
otp_valid = otp.validate_otp(EnableMFA,st.session_state.sec_key)
st.write(otp_valid)
if otp_valid:
session = db.db_connect()
secure_mfakey = db.ed(st.session_state.sec_key,'encode')
mfa_activate = update(db.Users).where(db.Users.Login == login).values({db.Users.MFAStatus : secure_mfakey})
try:
session.execute(mfa_activate)
session.commit()
st.success('MFA activated successfully')
except Exception as e:
st.error(f'Error while activating MFA. {str(e)}')
session.rollback()
session.close()
else:
st.error('Invalid MFA code. Please try again')
st.session_state.MFAStatus = 'Inactive'
st.rerun()
def validate_creds(login,pwd):
session = db.db_connect()
creds = session.query(db.Users.id,
db.Users.EmployeeId,
db.Users.Login,
db.Users.Password,
case((db.Users.MFAStatus == None, 'Inactive'),
else_= 'Active').label('MFAStatus'),
db.Users.MFAStatus.label('MFAKey'),
db.Employees.FirstName,
db.Employees.MiddleName,
db.Employees.LastName) \
.join(db.Employees, db.Users.EmployeeId == db.Employees.id) \
.filter(db.Users.Login == login).all() ## Get the user creds from DB
session.close()
if len(creds) == 0:
st.error("User not found. Please try again")
else:
if creds[0].Password == db.make_hashes(pwd):
if creds[0].MFAStatus == 'Active':
st.session_state.UserId = creds[0].id
st.session_state.UserLogin = login
st.session_state.MFAStatus = creds[0].MFAStatus
st.session_state.EmployeeId = creds[0].EmployeeId
st.session_state.FullName = f'{creds[0].FirstName} {creds[0].MiddleName} {creds[0].LastName}'
st.session_state.MFAKey = db.ed(f'{creds[0].MFAKey}','decode')
mfacode = st.text_input("Enter OTP code from your app",key='mfacode')
if mfacode and otp.validate_otp(int(mfacode),db.ed(f'{creds[0].MFAKey}','decode')):
st.rerun()
else:
st.error('Invalid MFA code. Please try again')
else:
return False
else:
st.error('Invalid user / password combination. Please check and try again.')
@st.dialog("Login",width="small")
def show_login_modal():
state = True
with st.form("Login",enter_to_submit=False):
st.write("Please enter your credentials to get started")
userlogin = st.text_input("Login Id",help="Enter your login user id")
userpwd = st.text_input("Password: ",type='password')
submit = st.form_submit_button("Login",icon=":material/login:")
if submit:
st.write(f'Validating user {userlogin}')
state = validate_creds(userlogin,userpwd)
if not state:
activate_mfa(userlogin)
return userlogin
The below if condition is what triggers the call to the login flow from the main function of the app.
if "UserId" not in st.session_state:
st.session_state.UserId = ul.show_login_modal()
So not exactly nested buttons, but something similar. You set state=True at the beginning of your dialog. You condition the change of this value on the form submit button.
User types in user name and password and clicks “Login”
The app reruns, the form submit button is True and validate_creds() is executed, state is updated and activate_mfa() is executed.
The user fills out the form within activate_mfa() and clicks that button.
The app reruns and now the first form’s button is False, so state is True (neither validate_creds() nor activate_mfa() execute).
Thanks for stopping by! We use cookies to help us understand how you interact with our website.
By clicking “Accept all”, you consent to our use of cookies. For more information, please see our privacy policy.
Cookie settings
Strictly necessary cookies
These cookies are necessary for the website to function and cannot be switched off. They are usually only set in response to actions made by you which amount to a request for services, such as setting your privacy preferences, logging in or filling in forms.
Performance cookies
These cookies allow us to count visits and traffic sources so we can measure and improve the performance of our site. They help us understand how visitors move around the site and which pages are most frequently visited.
Functional cookies
These cookies are used to record your choices and settings, maintain your preferences over time and recognize you when you return to our website. These cookies help us to personalize our content for you and remember your preferences.
Targeting cookies
These cookies may be deployed to our site by our advertising partners to build a profile of your interest and provide you with content that is relevant to you, including showing you relevant ads on other websites.