🐻‍❄️

【st.login】GoogleアカウントでログインできるStreamlitアプリの開発方法と仕組みをわかりやすく解説

に公開

前置き

こんにちは!データエンジニアの山口歩夢です。

Streamlitにはたくさんの便利な関数が用意されています。
簡単にアプリケーションの開発ができ、OpenID Connect(OIDC)を活用したユーザー認証でさえも、とても簡単に実装することができます。

これにより、皆さんがよく見るGoogleアカウントなどを使用したユーザー認証を簡単に実装できます。

本記事では、その仕組みから設定方法、実装例などをわかりやすく紹介します。

OpenID Connectとは?

まず、Streamlitst.login()がどのような仕組みで動いているのかを理解するために、OpenID Connect(以下OIDC)について簡単に触れておきます。

OIDC(OpenID Connect)は、OAuth 2.0を拡張してユーザー認証を行うためのプロトコルです。OIDCを使うことで、GoogleやMicrosoftなどのIDプロバイダーが提供するアカウントを利用して、他のアプリケーションに安全にログインできるようになります。

ユーザーがIDプロバイダーにログインすると、その認証結果をIDトークンとともに安全にアプリケーションへ送信されます。アプリケーション側では、このIDトークンをもとにユーザーを識別し、ログイン処理を行うことが可能になります。

本記事では、GoogleのIDプロバイダーを使ってログイン認証機能を実装するので、
上記をざっくり言うと、次のようなことが実現できるイメージです。

Googleアカウントを使って安全にログインできるStreamlitアプリが作れるようになります。
また、Streamlitアプリ側でユーザー登録やパスワード管理が不要になります。

この仕組みを図にすると、以下のようなイメージです。

image.png

さらに、OIDCはSSO(Single Sign-On)を実現するためにもよく利用されます。一度ログインすれば、他の対応アプリにもシームレスにアクセスできるようになります。

OIDCの仕組みは非常に奥が深く、学んでみると難しく感じますが、以下の書籍を読むとイメージを掴むことができました。

https://techbookfest.org/product/4885634867003392?productVariantID=6565720896831488

Streamlitでの認証関連関数の紹介

OIDCについてイメージがついたので、
Streamlitが提供する認証関連の関数について見ていきましょう。
Streamlitは以下の3つの認証関連の関数を提供しています。

  • st.login()
  • st.logout()
  • st.experimental_user

これらの機能を一つひとつ解説していきます。

st.login()

st.login()は、ユーザーをOIDCプロバイダーにリダイレクトして認証を行い、認証完了後にアプリへ戻す機能を提供します。
つまり、ユーザー認証を行ってくれるということで、冒頭の図の①~⑥は、主にst.loginが担っています。

st.login()を機能させるためには、st.secrets.tomlに色々と設定を行う必要があります。そちらは、後ほど解説させていただきます。

st.experimental_user

ログイン中のユーザー情報にアクセスできる読み取り専用オブジェクトです。
st.login()関数を使ってユーザー認証が完了したら、ユーザー情報がCookieに保存され、st.experimental_userからアクセスできるようになります。
Streamlitが冒頭の図の③で返されたIDトークンを解析し、その情報をst.experimental_userに格納するイメージです。

st.experimental_userには、ユーザー情報が辞書型で格納されます。
例えば、GoogleのIDプロバイダーを利用した場合、以下のような情報が取得できます。

{
    "is_logged_in": true,
    "iss": "https://accounts.google.com",
    "azp": "{client_id}.apps.googleusercontent.com",
    "aud": "{client_id}.apps.googleusercontent.com",
    "sub": "{unique_user_id}",
    "email": "{user}@gmail.com",
    "email_verified": true,
    "at_hash": "{access_token_hash}",
    "nonce": "{nonce_string}",
    "name": "{full_name}",
    "picture": "https://lh3.googleusercontent.com/a/{content_path}",
    "given_name": "{given_name}",
    "family_name": "{family_name}",
    "iat": {issued_time},
    "exp": {expiration_time}
}

st.logout()

st.logout()はユーザーをログアウトさせるための関数です。
呼び出すと、Cookie、st.experimental_userからユーザー情報を削除し、アプリにログイン前の画面を表示します。

secrets.tomlの設定

st.login()を使ってユーザー認証機能を実装するには、secrets.tomlの設定が必須です。
以下のような設定をする必要があります。

共通設定

すべてのプロバイダーに共通して必要な設定は以下の通りです。
secrets.toml[auth]セクションに、次の設定を記述します。

  • redirect_uri: アプリケーションのURL + /oauth2callback
  • cookie_secret: ランダムかつ強力な文字列(セッション保護用)

プロバイダーごとの設定

上記の共通設定に加えて、プロバイダーごとに以下の情報を設定します。

  • client_id: プロバイダーから取得
  • client_secret: プロバイダーから取得
  • server_metadata_url:
    プロバイダー毎に設定する値が異なるため、公式ドキュメントを確認しましょう。
    例えば、Googleであれば、"https://accounts.google.com/.well-known/openid-configuration"を設定します。

その他(OIDCの細かい設定)

基本的な設定はStreamlitが自動で行ってくれますが、
上記に加えて、OIDCの細かい設定を行うことも可能です。

OIDCはログイン体験を制御するための多くのパラメータ(例:scopepromptresponse_typeなど)を設定できます。
これらを、secrets.tomlclient_kwargsパラメータに記入することで設定できます。

設定できるパラメータなどは、以下を参照してください。

https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest

ちなみに先ほど申した通り、基本的な設定はStreamlitが自動で行ってくれています。
公式ドキュメントによると、デフォルトでscopeopenid profile emailpromptにはselect_accountが設定されています。
secrets.tomlに書き起こすと、以下のようなイメージです。

secrets.toml
[auth]
redirect_uri = "http://localhost:8501/oauth2callback"
cookie_secret = "xxx"
client_id = "xxx"
client_secret = "xxx"
server_metadata_url = "xxx"
client_kwargs = { 
    scope = "openid profile email", # ユーザーの基本情報を取得
    prompt` = "select_account"      # 複数アカウントからの選択を促す
}

scopeは取得したい情報の種類を指定しており、prompt="select_account"では、ユーザーにアカウント選択を促す画面を表示するという設定がされています。

また、statenonceといったセキュリティ対策用のパラメータは自動的に処理されるため、指定する必要がありません。secrets.tomlで認証を設定すると、StreamlitはCORSとXSRF保護を自動的に有効化します。

こんなところでもStreamlitは開発者が開発しやすいように、セキュリティ面での設定まで自動で行ってくれており、非常に便利だなと思いました。

実装手順

Streamlitが提供している機能や必要な設定が分かってきたので、
Google Identity(IDプロバイダー)とStreamlitを使用した、ログイン機能の実装を行っていきます。

必要なディレクトリやライブラリのセットアップ

必要なディレクトリやライブラリを準備していきます。
まずは、以下のような構成でディレクトリを用意します。

project/ 
├── requirements.txt
├── .streamlit
|   └── secrets.toml
└── streamlit_app.py

そして、以下のようにrequirements.txtには必要なライブラリを書き、

requirements.txt
Streamlit>=1.42.0
Authlib>=1.3.2

以下のように$ pip installコマンドなどでインストールしておきましょう。

$ pip install -r requirements.txt

secrets.tomlのセットアップ

次に.streamlit/secrets.tomlを設定していきます。
本記事では、ローカル開発環境でGoogle Identityを使ったユーザー認証を実装するための設定を行います。

client_idclient_secretの取得

まずは、client_idclient_secretをGoogle Cloud Consoleから取得します。
以下のドキュメントに従って取得しています。

https://developers.google.com/identity/openid-connect/openid-connect?hl=ja#getcredentials

最初に「API とサービス」の「認証情報」ページに遷移し、「同意画面を構成」というボタンをクリックします。

image.png

表示される画面で「開始」をクリックします。

image.png

設定画面に遷移し、アプリケーション名やメールアドレスの登録を行います。

image.png

次にどのユーザーがログインできるかを設定をします。
「内部」は組織内のユーザー、「外部」は「テストユーザー」に設定されたユーザーのみログイン可能となります。

image.png

最後に、プロジェクトの変更通知先メールアドレスの入力が求められるので入力します。

image.png

以上で、プロジェクト構成の用意ができたので、今回ログイン可能にする「テストユーザー」の登録をしていきます。「APIとサービス」ページに戻り、「OAuth同意画面」をクリックします。

image.png

「対象」をクリックし、「+ Add users」ボタンを押して、Streamlitアプリでログイン可能にするユーザーのメールアドレスを入力し、保存します。(今回は自分のGmailアドレスを登録しました。)

image.png

その後、再度「API とサービス」ページに戻り、画面上部の「+ 認証情報を作成」をクリックします。

image.png

「OAuthクライアントID」を選択し、client_idを作成します。

image.png

最後に以下の画像のように設定を行います。アプリケーションの種類は「ウェブ アプリケーション」を選択し、「名前」は管理しやすい名称を入力します。「承認済みのリダイレクト URL」には OIDC が認証後にリダイレクトする URL を設定します。

今回はローカル環境で検証を行うので、http://localhost:8501/oauth2callbackを設定します。
設定するURLは、アプリをホストしている場所によって異なります。アプリのURLを適切に設定する必要があります。image.png

「作成」をクリックすると、client_idclient_secretが表示されます。
これらのクライアントIDの情報をsecrets.tomlに設定するので、メモなどに控えておきます。

image.png

次にcookie_secretを取得します。
こちらは、Cookieを暗号化して署名するために使用される文字列です。
以下のコマンドを実行することで、ランダムな文字列を取得します。

python -c 'import os,base64; print(base64.urlsafe_b64encode(os.urandom(32)).decode())'

https://oauth2-proxy.github.io/oauth2-proxy/configuration/overview/

secrets.tomlを設定

これで、取得する必要がある情報を取得し終わったので、secrets.tomlに設定をします。
以下のように記述します。

secrets.toml
[auth]
redirect_uri = "http://localhost:8501/oauth2callback"
cookie_secret = "[ランダムに生成した文字列]"
client_id = "[Googleで取得したクライアントID]"
client_secret = "[Googleで取得したクライアントシークレット]"
server_metadata_url = "https://accounts.google.com/.well-known/openid-configuration"

redirect_uriserver_metadata_urlに設定する値は以下のように決まっています。

redirect_uriは、ユーザーがGoogleアカウントで認証が終わった後にリダイレクトされるURLです。今回はローカル環境でユーザー認証を行うため、http://localhost:8501/oauth2callbackを設定しています。

そして、server_metadata_urlはGoogleのOpenID Connect (OIDC) 設定のURLです。
GoogleのIDプロバイダーを仕様する場合は、https://accounts.google.com/.well-known/openid-configurationを設定します。
こちらは、OIDCプロバイダーの詳細(認可、トークン、取り消し、ユーザー情報、公開鍵エンドポイントのURIなど)を提供するキーと値のペアが含まれています。ブラウザで実行してみるとどんな値が入っているかが確認できます。

streamlit_app.pyにコードを書く

secrets.tomlの設定が完了したので、
streamlit_app.pyファイルにコードを書きます。
以下のコードを書くだけでログイン機能が完成です。

streamlit_app.py
import streamlit as st

# ログイン状態の確認
if not st.experimental_user.is_logged_in:
    st.title("ログイン画面")
    if st.button("Googleアカウントでログイン", icon=":material/login:"):
        st.login()
    st.stop()

# ログアウトボタン
if st.button("ログアウトする", icon=":material/logout:"):
    st.logout()  # ログアウト処理

# ユーザー情報の表示
st.markdown(f"ようこそ! {st.experimental_user.name}さん!")

以下のコマンドを実行して、実際にアプリがどのような動きをするのか見てみます。

$ streamlit run streamlit_app.py

まずは、ブラウザで以下のような画面が表示されます。
「Googleアカウントでログイン」ボタンをクリックしてみると、
image.png

Googleにログインするための画面に遷移します。
先ほど「テストユーザー」に登録たアカウントを選択すると、
image.png

st_loginにログイン」という画面に遷移します。画像のst_loginにログインには、先ほどOAuthクライアントIDの作成時に設定したアプリケーションの名称が表示されています。
「次へ」ボタンをクリックしてみましょう。
image.png

すると、無事ログイン後の画面が表示されるようになりました!
image.png

まとめ

Streamlitが提供する関数を活用することでGoogleアカウントを使ったユーザー認証機能を簡単に実装することができることが分かりました。
Google以外にも、Microsoft、Oktaなどの認証基盤とも連携できるため、非常に便利です。
自分的にはstatenonceといったセキュリティ対策用のパラメータもStreamlitが自動で設定してくれているというところが特に印象的でした。

Streamlitには、まだまだ便利な機能がたくさんあります。
詳しくは、私が執筆した『Streamlit入門 Pythonで学ぶデータ可視化&アプリ開発ガイド』にて解説していますので、ぜひご覧ください!

https://nextpublishing.jp/book/18323.html

Discussion