【Streamlit v1.46.1】認証モジュール + SQLite3を使ってログイン機能を実装

ログイン/サインアップ機能を、streamlit-authenticator モジュールを使って行います。ユーザー情報はSQLite3に保存して永続化し、パスワードはハッシュ化しています。

streamlit-authenticator は、Streamlitアプリケーションにユーザー認証機能を追加するためのライブラリです。ログイン機能に必要な入力フォームの自動作成とセッションステータスの管理が簡単に行えます。

また、Streamlitはデフォルトで同期的に動作するフレームワークですので、SQLite3との連携も同期処理で行います。

開発環境

  • OS: Ubuntu22.04
  • version:Streamlit v1.46.1 / streamlit-authenticate v0.4.2
  • 仮想環境でのインストール
    pip install streamlit==1.46.1
    pip install streamlit-authenticate==0.4.2

SQLite3の準備

  1. DBスキーマの設計
  2. サンプル・ユーザー作成

(1)DBスキーマの設計

createdb.py(DBスキーマの設計)→これをpython3 createdb.pyで実行する。

import sqlite3
import logging

conn = sqlite3.connect("st-menber.db")
c = conn.cursor()

# usersテーブルの作成
try:
    c.execute("drop table if exists users")
    c.execute("""
    CREATE TABLE users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        username TEXT NOT NULL,
        password TEXT NOT NULL
    )
    """)
except Exception as e:
    logging.exception("エラーが発生しました: %s", e)

else :
    print("データベースの作成に成功しました")

createdb.pyの実行結果(データベースの作成)

# 下記コマンドで実行させます。
python3 createdb.py

データベースの作成に成功しました

(2)サンプル・ユーザー作成

insertdb.py(ユーザー登録処理検証)→ python3 insertdb.pyで実行

import sqlite3
import logging

conn = sqlite3.connect("st-menber.db")
cursor = conn.cursor()

# ここではpassword平文のまま動作確認。
sample_user = [
    ("sanji", "pass123"),
    ("usop", "pass345")
]

try: 
    cursor.executemany("insert into users (username, password) values (?,?)",sample_user)
    conn.commit()  # ← コミットを忘れないでください。
    conn.close()       # ← 必ずクローズさせる。
except Exception as e:
    logging.exception("エラーが発生しました: %s", e)
else:
    print("サンプルユーザーの作成に成功しました")

insertdb.pyの実行結果(テーブルの作成と確認)

# (1) 下記コマンドで実行させます。
python3 insertdb.py 

サンプルユーザーの作成に成功しました

# (2) sqlite3をコマンドラインで起動。
sqlite3 st-menber.db 

SQLite version 3.37.2 
Enter ".help" for usage hints.

# (3) テーブ内容表示
sqlite> select * from users;
1|sanji|pass123
2|usop|pass345

# (4) 抜ける
sqlite> .quit

以上で、StreamlitからSQLite3と接続できることが分かりました。次はStreamlitの認証機能とデータベースとを統合します。

streamlit-authenticatorとの統合

パスワードのハッシュ化してユーザー情報を登録

先程のinsertdb.pyを追加・修正します。パスワードのハッシュ化には、Streamlit_authenticatorのHasherクラスのhash()メソッドを使います。

insertdb.pyの修正

import sqlite3
import logging
import streamlit_authenticator as stauth # ← 追加

conn = sqlite3.connect("st-menber.db")
cursor = conn.cursor()

sample_user = [
    ("sanji", stauth.Hasher.hash("pass123")),  # ← 修正
    ("usop", stauth.Hasher.hash("pass345"))   # ← 修正
]

try: 
    cursor.executemany("insert into users (username, password) values (?,?)",sample_user)
    conn.commit()
    conn.close()
    print("サンプルユーザーの作成に成功しました")
except Exception as e:
    logging.exception("エラーが発生しました: %s", e)
streamlit-authenticatorの使い方
  1. データベースから既存のユーザー情報を取得し、Authenticate()メソッドで初期化する。
  2. 最新版ではユーザー情報は全て辞書形式でまとめる
  3. ログインフォームの生成

①ユーザー情報は辞書形式でまとめる。

fetch_users()関数は、streamlit-authenticateのAuthenticate()メソッドに渡すcredentials変数を自動生成します。

def  fetch_users(conn):
    try:
        cur = conn.cursor()
        cur.execute("select username ,password from users")
        rows = cur.fetchall()

        credentials = {"usernames":{}}
        # credentials = {}
        for row in rows:
            username, hashed_password = row
            tmp_dict = {
                'name': username,   # ここでusernameをnameとして使用
                'password': hashed_password,
            }
            credentials["usernames"][username] =tmp_dict
        conn.close()
        return credentials
    
    except Exception as e:
        st.error(f"ユーザー情報取得エラー: {e}")
        return {"usernames": {}}

先程のinsertdb.pyで追加したユーザー情報を基に作成したcredentials辞書を、読み易いように展開すると以下のようになります。fetch_users()関数はこれを自動生成して返しています。

# sample
credentials = {
    "usernames": {
        "sanji": {
            "name": "sanji",
            "password": stauth.Hasher.hash('pass123') 
        },
        "usop": {
            "name": "usop",
            "password": stauth.Hasher.hash('pass456')
        }
    }
}

②認証オブジェクトの作成(初期化)

認証オブジェクト名をauthenticatorとしています。

authenticator = stauth.Authenticate(
    credentials=credentials,                     # ← ①でまとめたcredentialsを渡す
    cookie_name="my_app_cookie",  # 任意のクッキー名
    cookie_key="super_secret_key",    # 任意の署名キー(テストのため直書きで済ませています)
    cookie_expiry_days=30                       # クッキーの有効期限(日数)
)

③ログインフォームの生成

認証オブジェクトauthenticatorlogin()メソッドを呼び出します。これだけでログインフォームが自動生成されます。

 authenticator.login(
     location="sidebar",
     fields={
         "Form name": "ログイン",
         "Username": "ユーザー名",
         "Password": "パスワード",
         "Login": "ログイン"
     }
streamlit-authenticate v0.4.2からの変更箇所まとめ

streamlit-authenticateは、2025年7月時点の最新版はv0.4.2です。旧バージョンから処理内容が大きく変わっていますので、ご注意ください。

主な変更一覧

アプリ実行

app.pyの全コード

"""
Streamlit + SQlite3 + streamlit_authenticator
によるログイン/サインアップ機能
"""
#  v1.46.1
import streamlit as st
# ↓画像を扱いたい方は以下もimportすると便利です。
from PIL import Image
import sqlite3
import streamlit_authenticator as stauth # 追加

# 状態管理
if "page" not in st.session_state:
    st.session_state["page"] = "login"

if "mypage" not in st.session_state:
    st.session_state["mypage"] = "Home"    

# 画面遷移先
def Home():
    st.header('ホーム画面')
    img1="./images/公園の河.jpg"
    img2="./images/palazzina寺院.jpg"
    col1,col2 = st.columns(2)
    with col1:
        image1= Image.open(img1)
        st.image(image1)
    with col2:
        image2 = Image.open(img2)
        st.image(image2)

def info():
    st.header('お知らせ')
    st.write("〇〇公園のひまわりが見頃です。")
    st.write("〇〇町の祇園祭が無事終了しました。")

def inquiry():
    st.header('お問い合わせ')


# credentials作成
def fetch_users(conn):
    try:
        cur = conn.cursor()
        cur.execute("select username ,password from users")
        rows = cur.fetchall()

        credentials = {"usernames":{}}
        for row in rows:
            username, hashed_password = row
            tmp_dict = {
                'name': username,   # ここでusernameをnameとして使用
                'password': hashed_password,
            }
            credentials["usernames"][username] =tmp_dict
        conn.close()
        return credentials
    
    except Exception as e:
        st.error(f"ユーザー情報取得エラー: {e}")
        return {"usernames": {}}

# -新規登録
def regist_user(conn, username, hashed_password):
    conn = sqlite3.connect("st-menber.db") # 一旦conn.close()しているので再度接続する!
    try:
        cur = conn.cursor()
        cur.execute("insert into users (username, password) values (?, ?)",(username, hashed_password))
        conn.commit()
        conn.close()
        return True
    except sqlite3.IntegrityError :
        st.warning("このユーザー名は既に登録されています。別のユーザー名をお試しください。")
        return False
    except Exception as e:
        st.error(f"ユーザー登録エラー: {e}")
        return False

 
# --メイン関数--
def main():
    conn = sqlite3.connect("st-menber.db")
    if conn is None:
        st.error("データベースへの接続に失敗しました。アプリケーションを終了します。")
        st.stop() # データベース接続ができない場合はここで停止

    mycredentials = fetch_users(conn)

    # ↓ローカル環境なので直書き
    mysignature_key = 'super_secret123456!#$_verylonglong-cool'
  
    authenticator = stauth.Authenticate(
        mycredentials,
        'streamlit_v1.46_login_cookie',  # 任意のクッキー名
        mysignature_key,                 # 任意の署名キー
        cookie_expiry_days=30,          # クッキーの有効期限(日数)
    )

    # サイドバーの設計
    with st.sidebar:
        if st.session_state["page"]== "login":
            if st.button("新規登録はこちらから"):
                st.session_state["page"] = "signup"
                st.rerun()
            st.divider()
            
            authenticator.login(location="sidebar",fields={
                "Form name": "ログイン",
                "Username": "ユーザー名",
                "Password": "パスワード",
                "Login": "ログイン"
            })
      
        
            if st.session_state["authentication_status"]:
                st.session_state["page"] = "dashboard"
                # print(st.session_state["authentication_status"])
            elif st.session_state["authentication_status"] is False:
                st.error("ユーザー名またはパスワードが間違っています")
            elif st.session_state["authentication_status"] is None:
                st.warning("ユーザー名とパスワードを入力してください")

        elif st.session_state["page"]== "dashboard":
            # st.markdown("ログインに成功しました。  \nようこそ!!")  # OK ※半角スペース2個+\nで改行になる
            if st.session_state["authentication_status"]:
                st.write("ログインに成功しました。")
                st.success(f"ようこそ {st.session_state['name']} さん!")
                st.session_state["page"] = "dashboard"
            dash_page = st.selectbox('選択してください',['Home','お知らせ', 'お問い合わせ'])
            st.session_state["mypage"] = dash_page

            authenticator.logout("ログアウト", location="sidebar")
            if st.session_state["authentication_status"] is None:
                st.session_state["page"] = "login"
                st.warning("ログアウトしました。再度ログインしてください。")
                st.rerun()
        elif st.session_state["page"] == "signup":
            st.header('登録フォーム')
            st.write("アカウントをお持ちでない方はこちらから登録してください。")

            with st.form("register_user_form"):
                username = st.text_input("ユーザー名")
                new_password = st.text_input("パスワード", type="password")
                confirm_password = st.text_input("パスワードの確認", type="password")
                
                submit_registration = st.form_submit_button("登録する")
                if submit_registration:
                    if username and new_password and confirm_password:
                        if new_password == confirm_password :
                            try:
                                hashed_password = stauth.Hasher.hash(new_password)
                                if regist_user(conn, username, hashed_password):
                                    st.session_state["page"] = "login"
                                    st.markdown("<span style='color:blue;'>ご登録ありがとうございました。</span>", unsafe_allow_html=True) 
                                    st.rerun() 

                            except Exception as e:
                                st.error(f"パスワードのハッシュ化または登録中にエラーが発生しました: {e}")
                        else:
                            st.error("パスワードが一致しません。再度確認してください。")
                    else :
                        st.error("全ての登録項目を入力してください。")

            st.divider()
            if st.button("ログイン画面に戻る"):
                st.session_state["page"] = "login"
                st.rerun()


    # メイン画面の設計
    if st.session_state["page"] == "dashboard":
        # 画面遷移
        if st.session_state["mypage"] == "Home":
            Home()
        elif st.session_state["mypage"] == "お知らせ":
            info()
        elif st.session_state["mypage"] == "お問い合わせ":
            inquiry()

    
if __name__ == '__main__':
    main()

実行するには次のコマンドを入力してください。

streamlit run app.py

ローカル環境でのブラウザ動作

最終的なusersテーブル:パスワードはちゃんとハッシュ化されています。

以上です。😀

Follow me!