User authentication

Is it possible to add an authentication step to access streamlit?
I want to control so that only users who can provide a correct username/password can access the content.

Example: https://dash.plot.ly/authentication

4 Likes

Welcome to the community, @fogel!

Unfortunately I don’t have a good solution for you right now. We are working on this as part of the Streamlit for Teams offering which is in limited beta right now and will be rolling out in early 2020.

If you don’t need true authentication, you could just set up a passphrase using st.text_input and only show the app if the answer matches the passphrase you set. It’s not ideal, but if you are just trying to gate access that would work. A related feature request we’re tracking will also enable password text_inputs.

Thank you for using Streamlit and we apologize for the delay. :pray:t2:We’ve been a bit overwhelmed with the amount of questions coming in since launch. :grimacing:

10 Likes

I have a very hacky but 9000% secure solution for you =)

Deploy an app to a server
Close all ports except 22 (ssh)
Launch streamlit on some port locally
Do not expose this port to the world
Use ssh tunnel to access your streamlit app
Whenever you grant access to someone just add his public ssh key to authorized_keys

(we do it all the time with notebooks and similar things)

4 Likes

Is it possible if we make iframe to streamlit? so, the authenticated user will be redirected to this iframe page?

2 Likes

Unfortunately, you really can’t; modern browsers will not allow insecure http-based content via https.

Even if you could, it wouldn’t really be secure. The streamlit app contained within the iframe would still be open to the world and exploitable by anybody scanning for open ports.

One way to do this would be to set up the streamlit app behind a web server reverse proxy, using https (Apache2 or Nginx). Here’s a good stackoverflow conversation about how to make this work on Nginx (in the example, imagine that your streamlit app is running on port 3001). Then you would need to apply HTTPS settings to those server definitions as in the Nginx https documentation here.

Then you would load the iframe with the basic Auth params supplied in the URL through your user-authenticated web page. It’s not a perfect solution, but at least you can guarantee that the credentials will only be embedded in pages meant for logged-in users. You’ll have to trust your users not to reshare the link.

I know this seems convoluted; right now Streamlit has been optimized as an internal tool for sharing data science and ML results. We can see that people really want to use it as a web app deployment tool, and our engineering path is being influenced by that! :heavy_heart_exclamation:

8 Likes

Hello Amanda,

We tried your proposed solution with text_input, however now it is always displaying password, is there a way to hide this text input widget after ‘successfull match’ or text provided in it?

Hey @Galkinpro ,

You could solve that using an empty() element, for instance:

import streamlit as st


def is_authenticated(password):
    return password == "admin"


def generate_login_block():
    block1 = st.empty()
    block2 = st.empty()

    return block1, block2


def clean_blocks(blocks):
    for block in blocks:
        block.empty()


def login(blocks):
    blocks[0].markdown("""
            <style>
                input {
                    -webkit-text-security: disc;
                }
            </style>
        """, unsafe_allow_html=True)

    return blocks[1].text_input('Password')


def main():
    st.header('Hello')
    st.balloons()


login_blocks = generate_login_block()
password = login(login_blocks)

if is_authenticated(password):
    clean_blocks(login_blocks)
    main()
elif password:
    st.info("Please enter a valid password")

Take in account that the password appearance style will only work in Chromium based browsers.

13 Likes

Here is my very simple workaround:

password = st.sidebar.text_input("Password:", value="")

# select our text input field and make it into a password input
js = "el = document.querySelectorAll('.sidebar-content input')[0]; el.type = 'password';"

# passing js code to the onerror handler of an img tag with no src
# triggers an error and allows automatically running our code
html = f'<img src onerror="{js}">'

# in contrast to st.write, this seems to allow passing javascript
div = Div(text=html)
st.bokeh_chart(div)

if password != os.environ["PASSWORD"]:
    st.error("the password you entered is incorrect")
    return
9 Likes

this is awesome! thank you, @aseifert

what import enabled you to use this function

Hi @John_M

I guess this is a bokeh object, so probably from bokeh.models import Div.

exactly, thanks. unfortunately, it seems i can’t edit my previous post anymore, but thanks for clearing that up.

The workaround isn’t needed anymore, however, as newer versions of streamlit do support a password field: st.text_input("Password:", value="", type="password")

3 Likes

You can also put auth in front of the application rather than inside it. For example, using oauth for login with nginx https://github.com/cloudflare/nginx-google-oauth/blob/master/README.md#docker-image

1 Like

In case this helps anyone - this was super helpful: https://github.com/Taxuspt/heroku_streamlit_nginx

1 Like

I have this below code to do the minmal user authentication. This is based on the SessionState hack here. Is there a better approach to achieve the below functionality

from SessionState import get

session_state = get(password='')

if session_state.password != 'pwd123':
    pwd_placeholder = st.sidebar.empty()
    pwd = pwd_placeholder.text_input("Password:", value="", type="password")
    session_state.password = pwd
    if session_state.password == 'pwd123':
        pwd_placeholder.empty()
        main()
    else:
        st.error("the password you entered is incorrect")
else:
    main()
3 Likes

The approach above by @nth-attempt worked quite well for me, thanks!

Minor suggestion: instead of

else:
    st.error("the password you entered is incorrect")

I would do elif session_state.password != '', so that the error doesn’t show up when the password is blank!

2 Likes

thanks for sharing, this works well

I did a simple test and oauth2-proxy seems to be a interesting option!
It’s a simple oauth proxy, therefore I could protect my streamlit application behind a google-login page.

Please be aware that oauth is not a trivial flow so, consider wisely all your requirements (i.e. logout, security, etc) :slight_smile:

Simple demo:

8 Likes

Hi and thx for the suggestions! :slight_smile:

I have the following error:
ModuleNotFoundError: No module named 'SessionState'

I tried to install various libraries but the error is still there.

Any idea anyone?

Thanks,
Charly

Hi Charly,

You need the SessionState gist. I.e. put the file SessionState.py from here in your project.

There are are different versions of the SessionState gist out there, but it looks like this one is recent. See e.g. this thread for some additional inspiration.

Best regards,
Peter

2 Likes