We deployed our streamlit apps behind the oauth2-proxy that gives us a secure layer in front of streamlit and is easy to setup.
The oauth2 provider is Google Workspace in our case. But you can use anything that is compatible with oauth2-proxy azure, facebook, github etc. The docs are good enough to guide you through setup on google / azure end.
We are using certbot webroot plugin for SSL certs see the docs for the exact command, the certbot is installed via snap on the host system.
Here is our docker-compose and nginx config, search for YOUR… to find places where you need to get the config changed.
docker-compose.yml
version: "3"
services:
nginx:
image: nginx
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- /etc/letsencrypt:/etc/letsencrypt:ro
- /var/www/html:/var/www/html:ro # for certbot webroot plugin
- /tmp/empty:/docker-entrypoint.d/
ports:
- 443:443
- 80:80
command: nginx-debug -g 'daemon off;'
environment:
- TZ=Europe/Warsaw
oauth2:
image: quay.io/oauth2-proxy/oauth2-proxy:latest
expose:
- 4180
environment:
- TZ=Europe/Warsaw
- OAUTH2_PROXY_COOKIE_SECURE=false
- OAUTH2_PROXY_UPSTREAMS=http://streamlit:8501/
- OAUTH2_PROXY_HTTP_ADDRESS=0.0.0.0:4180
# change that to your google domain
- OAUTH2_PROXY_EMAIL_DOMAINS=YOURDOMAIN.com
# - OAUTH2_PROXY_AUTHENTICATED_EMAILS_FILE=/your-file-with-emails.txt
# set this up to values you get from google auth
- OAUTH2_PROXY_REDIRECT_URL=https://apps.YOURDOMAIN.com/oauth2/callback
- OAUTH2_PROXY_COOKIE_SECRET=YOUR-COOKIE-SECRET
- OAUTH2_PROXY_CLIENT_ID=YOUR-CLIENT-ID.apps.googleusercontent.com
- OAUTH2_PROXY_CLIENT_SECRET=YOUR-CLIENT-SECRET
- OAUTH2_PROXY_SET_XAUTHREQUEST=true
- OAUTH2_PROXY_PASS_ACCESS_TOKEN=true
- OAUTH2_PROXY_COOKIE_REFRESH=1h
streamlit:
image: "YOUR-streamlit-app"
command: streamlit run /src/streamlit_entrypoint.py
environment:
- TZ=Europe/Warsaw
nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768;
}
http {
##
# copied from default nginx setup
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
gzip on;
server {
# set up SSL
listen 80 default_server;
listen [::]:80 default_server;
listen 443 ssl; # managed by Certbot
server_name "YOUR-DOMAIN.com";
ssl_certificate /etc/letsencrypt/live/apps.YOUR-DOMAIN.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/apps.YOUR-DOMAIN.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
root /var/www/html;
index index.html index.htm;
location /.well-known {
try_files $uri $uri/ =404;
}
location /stream {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://backend;
}
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
upstream backend {
server oauth2:4180;
}
}
Once you have the proxy deployed you can access a logged in user email address as follows:
def get_loggedin_user():
"""return logged in user email adress or None if no headers were found"""
headers = get_request_headers() # your way to access request headers
return headers.get('X-Forwarded-Email', None)
I’m not sure if it is the best way nowadays to access the request headers but it is what worked for us:
def get_session_id():
ctx = st.report_thread.get_report_ctx()
if ctx:
return ctx.session_id
def get_session_info():
session_id = get_session_id()
if get_session_id():
return st.server.server.Server.get_current()._get_session_info(session_id)
raise RuntimeError('No streamlit session')
def get_request_headers():
try:
return get_session_info().ws.request.headers
except AttributeError:
return {}
```