Streamlitで認証機能付きマルチページWebアプリを作成する方法


---

### Streamlitで認証機能付きマルチページWebアプリを作成する方法

Streamlitでマルチページに対応させるため試行錯誤したので共有します。

強み
・Pythonコード形式で単調な記法
・マークダウン記述
・データサイエンス系に強い

弱み：
・組み込みの認証機能
・マルチページ機能　
・より詳細なウェブページ

そのため、外部ライブラリや独自のロジックを組み合わせて実現する必要があります。

基本的に以下の2つのアプローチが考えられます。

1.  **外部ライブラリ `streamlit-authenticator` とファイル分割によるマルチページ**
2.  **独自の認証ロジックとセッションステート、ページ関数によるマルチページ**

今回は、より手軽で推奨される1つ目のアプローチを詳しく解説します。

---

### 1. 外部ライブラリ `streamlit-authenticator` とファイル分割によるマルチページ

この方法が最も一般的で、実装も比較的容易です。

#### 必要なライブラリのインストール

まず、必要なライブラリをインストールします。

```bash
pip install streamlit streamlit-authenticator PyYAML
```

* `streamlit`: Webアプリ本体
* `streamlit-authenticator`: 認証機能を提供
* `PyYAML`: ユーザー情報をYAMLファイルで管理するために必要

#### プロジェクトのディレクトリ構成

以下のようなディレクトリ構成を推奨します。

```
.
├── app.py
├── config.yaml
├── pages/
│   ├── page1.py
│   └── page2.py
└── requirements.txt
```

* `app.py`: メインのアプリケーションファイル。認証ロジックとページ遷移を制御します。
* `config.yaml`: ユーザー情報や認証設定を格納します。
* `pages/`: 各ページを定義するPythonファイルを格納するディレクトリです。
* `requirements.txt`: アプリケーションの依存関係を記述します。

#### 各ファイルのコード例

##### `config.yaml` (ユーザー情報の設定)

```yaml
credentials:
  usernames:
    jsmith:
      email: jsmith@gmail.com
      name: John Smith
      password: abc # 実際にはハッシュ化されたパスワードを使用することを強く推奨
    rbriggs:
      email: rbriggs@gmail.com
      name: Rebecca Briggs
      password: def # 実際にはハッシュ化されたパスワードを使用することを強く推奨

cookie:
  expiry_days: 30
  key: random_signature_key # 秘密鍵。適当な文字列を設定してください
  name: some_cookie_name

preauthorized:
  emails:
    - melsby@gmail.com
```

**重要:** `password` は**プレーンテキスト**で記述していますが、これは**開発用**です。**本番環境では必ずハッシュ化されたパスワードを使用してください。** `streamlit-authenticator` は内部でパスワードのハッシュ化（`stauth.Hasher`）をサポートしています。

例：パスワードをハッシュ化する場合の`app.py`での利用方法（後述）

```python
import streamlit_authenticator as stauth
hashed_passwords = stauth.Hasher(['abc', 'def']).generate()
# config.yamlにはハッシュ化されたパスワードを設定
# credentials:
#   usernames:
#     jsmith:
#       password: <ハッシュ化されたパスワード>
```

##### `app.py` (メインアプリケーション)

```python
import streamlit as st
import streamlit_authenticator as stauth
import yaml
from yaml.loader import SafeLoader

# 認証設定の読み込み
with open('./config.yaml') as file:
    config = yaml.load(file, Loader=SafeLoader)

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

# ログインフォームの表示
name, authentication_status, username = authenticator.login('Login', 'main')

if authentication_status:
    authenticator.logout('Logout', 'main', key='unique_key')
    st.write(f'Welcome *{name}*')
    st.title('Multi-Page Web App')

    # 認証成功後のページ遷移ロジック
    # Streamlitのセッションステートを利用して現在のページを管理
    if 'current_page' not in st.session_state:
        st.session_state.current_page = 'home' # 初期ページ

    st.sidebar.title("Navigation")
    if st.sidebar.button("Home"):
        st.session_state.current_page = 'home'
    if st.sidebar.button("Page 1"):
        st.session_state.current_page = 'page1'
    if st.sidebar.button("Page 2"):
        st.session_state.current_page = 'page2'

    # 各ページの読み込みと表示
    if st.session_state.current_page == 'home':
        st.write("これはホーム画面です。")
    elif st.session_state.current_page == 'page1':
        from pages import page1
        page1.show_page()
    elif st.session_state.current_page == 'page2':
        from pages import page2
        page2.show_page()

elif authentication_status == False:
    st.error('Username/password is incorrect')
elif authentication_status == None:
    st.warning('Please enter your username and password')

# パスワードのハッシュ化例 (config.yamlに設定する前に実行)
# hashed_passwords = stauth.Hasher(['abc', 'def']).generate()
# print(hashed_passwords)
```

##### `pages/page1.py`

```python
import streamlit as st

def show_page():
    st.title("ページ1")
    st.write("これはページ1のコンテンツです。")
    st.button("ページ1のボタン")
```

##### `pages/page2.py`

```python
import streamlit as st

def show_page():
    st.title("ページ2")
    st.write("これはページ2のコンテンツです。")
    st.checkbox("ページ2のチェックボックス")
```

#### アプリケーションの実行

プロジェクトのルートディレクトリで以下のコマンドを実行します。

```bash
streamlit run app.py
```

ブラウザで開かれたStreamlitアプリにアクセスし、設定したユーザー名とパスワードでログインしてみてください。ログイン成功後、サイドバーに表示されたボタンでページを切り替えることができるはずです。

---

### 2. 独自の認証ロジックとセッションステート、ページ関数によるマルチページ

この方法は、`streamlit-authenticator`のような外部ライブラリを使わずに、Streamlitの基本的な機能だけで認証とマルチページを実現するものです。より細かいカスタマイズが可能ですが、認証ロジックの実装（パスワードのハッシュ化、セッション管理など）をすべて自身で行う必要があります。

**基本的な考え方:**

* **認証:** ユーザー名とパスワードの入力フォームを作成し、入力された情報が正しいかチェックします。パスワードは必ずハッシュ化して比較します。
* **セッション管理:** `st.session_state` を利用して、ユーザーのログイン状態（`st.session_state.logged_in = True`など）や現在の表示ページを管理します。
* **マルチページ:** 各ページを関数として定義し、`st.session_state.current_page` の値に応じて表示する関数を切り替えます。

**実装のポイント:**

* **パスワードのハッシュ化:** `bcrypt` や `passlib` などのライブラリを使用します。
* **セッション管理:** ログイン状態やユーザー情報を `st.session_state` に格納します。
* **ページ遷移:** サイドバーのボタンなどで `st.session_state.current_page` の値を変更し、メインコンテンツを再描画します。

**このアプローチは、`streamlit-authenticator` を使用するよりも多くのコード記述とセキュリティ対策の知識が必要になります。** 特に本番環境での運用を考える場合、認証のセキュリティを確保するためには十分な検討が必要です。

---

### その他の考慮事項とヒント

* **パスワードのハッシュ化:** 繰り返しになりますが、本番環境では絶対にプレーンテキストのパスワードを使わないでください。`streamlit-authenticator` が提供する `stauth.Hasher` を活用するか、`bcrypt` などの強力なハッシュ関数を使用してください。
* **ユーザー情報の管理:** `config.yaml` の代わりに、データベース（SQLite, PostgreSQLなど）やクラウドの認証サービス（Firebase Authentication, AWS Cognitoなど）と連携することも可能です。
* **Streamlitの再実行:** Streamlitは、コードに変更があったり、ウィジェットが操作されたりすると、スクリプト全体を上から下に再実行する特性があります。この特性を理解し、セッションステートを適切に利用することが重要です。
* **CSSでのスタイリング:** Streamlitのデフォルトの見た目だけでなく、カスタムCSSを適用してよりリッチなUIを作成することも可能です。
* **デプロイ:** 作成したアプリは、Streamlit Cloud、Heroku、Google Cloud Run、AWS Fargateなど、様々なプラットフォームにデプロイできます。

---

### まとめ

Streamlitで認証機能付きのマルチページWebアプリを作成するには、`streamlit-authenticator` のような外部ライブラリを利用するのが最も簡単で推奨される方法です。これにより、認証ロジックの実装の手間を大幅に削減できます。

もし、より細かい制御や独自の認証フローが必要な場合は、Streamlitのセッションステートと組み合わせて独自のロジックを実装することも可能ですが、セキュリティ面での考慮がより重要になります。

### 発展アイデア
Webアプリとして限定した場合の活用
・OSライブラリなど組み合わせ、バックエンドのコードを実行させ、Streamlitのみで完結させる。
複数フレームワーク組み合わせ
・複雑な仕組みを作ろうとした場合はFastAPIと組み合わせより工夫ができるかもしれません。

ご不明な点がありましたら、お気軽にご質問ください！