Panel応用編 第6回: 認証とセキュリティ (前半)

Python
この記事は約18分で読めます。

こんにちは、JS2IIUです。今回はPanelの認証とセキュリティに関わる話題です。若干分量が多いので、前半後半の2部構成にします。よろしくお願いします。

はじめに

Webアプリケーションを公開する際に、認証とセキュリティは重要な要素です。特にユーザーが個人情報や機密データを扱う場合、適切なセキュリティ対策を講じることが不可欠です。本記事では、Panelを使ってシンプルなログイン機能を実装する方法や、OAuthやJWT(JSON Web Tokens)を使ってセキュアな認証を行う方法を紹介します。

認証の基本: ユーザーログイン機能の追加

まずは、シンプルなユーザーログイン機能を構築します。ここでは、ユーザー名とパスワードを用いた基本的な認証システムを実装し、特定のページにアクセスするためにはログインが必要な仕組みを作ります。サンプルコードの詳しい説明は記事の最後、補足(1) 基本の認証、サンプルプログラムの解説、を参照して下さい。

シンプルなログイン機能の実装

import panel as pn

pn.extension()

# ユーザー情報の定義
USERS = {
    'admin': 'password123',
    'user1': 'mypassword'
}

# ユーザーセッションの管理
pn.state.user_logged_in = False

# ログインチェック関数
def check_login(username, password):
    if username in USERS and USERS[username] == password:
        pn.state.user_logged_in = True
        return True
    return False

# ログインフォーム
username_input = pn.widgets.TextInput(name='Username')
password_input = pn.widgets.PasswordInput(name='Password')
login_button = pn.widgets.Button(name='Login')

# ログイン処理
def handle_login(event):
    if check_login(username_input.value, password_input.value):
        login_message.value = 'Login successful!'
        main_area.clear()
        main_area.append(protected_content())
    else:
        login_message.value = 'Invalid credentials.'

login_button.on_click(handle_login)

# ログインメッセージ
login_message = pn.pane.Markdown('')

# プロテクトされたコンテンツ
def protected_content():
    return pn.Column("### Welcome to the protected page!", "This content is only for logged-in users.")

# メインレイアウト
main_area = pn.Column()

# ログイン画面
login_area = pn.Column(
    "## Login Page",
    username_input,
    password_input,
    login_button,
    login_message
)

# ログインまたは保護ページを表示
if not pn.state.user_logged_in:
    main_area.append(login_area)
else:
    main_area.append(protected_content())

# Panelアプリケーションとして表示
main_area.servable()
pn.serve(main_area)
  • USERS辞書で、ユーザー名とパスワードの組み合わせを定義します。シンプルな認証機能として、ユーザー名が正しく、パスワードが一致する場合にのみ認証を通過します。
  • pn.state.user_logged_in は、ユーザーがログインしているかどうかを管理します。この変数を使って、ログイン状態を管理し、ログイン済みの場合にのみ保護されたコンテンツを表示します。
  • pn.widgets.TextInput()pn.widgets.PasswordInput() は、ユーザー名とパスワードの入力フィールドを作成するために使用します。
  • handle_login() 関数で、ログイン処理を行い、認証に成功した場合はログインメッセージを表示し、保護されたコンテンツにリダイレクトします。

これにより、シンプルなログイン機能が完成し、認証済みのユーザーのみが特定のコンテンツにアクセスできる仕組みが実装されます。

記事の末尾に、このプログラムの詳細な解説を載せていますので参考にして下さい。

OAuthによるセキュアな認証

OAuth 2.0は、外部の認証プロバイダー(Google、GitHubなど)を利用して安全な認証を行うためのプロトコルです。PanelアプリにOAuthを統合することで、セキュリティをさらに強化し、簡単に外部サービスを使ったログインが実現できます。

OAuthのセットアップ

OAuthを実装するためには、まずAuthlibというライブラリを使用します。Authlibは、OAuthプロトコルを簡単に実装するためのライブラリです。

手順1: Authlibのインストール

まず、Authlibをインストールします。

pip install authlib

手順2: Google OAuthクライアントの設定

Google OAuthを使って認証を行うためには、Google Cloud ConsoleでOAuthクライアントIDとクライアントシークレットを取得する必要があります。Google Cloudで新しいプロジェクトを作成し、OAuth認証情報を生成します。

  1. Google Cloud Consoleにアクセスし、新しいプロジェクトを作成。
  2. OAuth 2.0認証情報を作成し、リダイレクトURLにhttp://localhost:5000を指定。

手順3: OAuthによる認証コードの実装

以下のコードで、Google OAuth認証をPanelアプリに統合します。コードの詳細説明がこの記事の末尾にありますので、参考にして下さい。

from authlib.integrations.requests_client import OAuth2Session
import panel as pn

pn.extension()

# クライアント情報
client_id = 'YOUR_CLIENT_ID'
client_secret = 'YOUR_CLIENT_SECRET'

# 認証エンドポイント
authorize_url = 'https://accounts.google.com/o/oauth2/auth'
token_url = 'https://oauth2.googleapis.com/token'

# OAuthセッションの初期化
client = OAuth2Session(client_id, client_secret, redirect_uri='http://localhost:5000')

# 認証リンクの生成
auth_url, state = client.create_authorization_url(authorize_url)

# 認証UIの表示
auth_link = pn.pane.Markdown(f"[Login with Google]({auth_url})")
auth_button = pn.widgets.Button(name="Login with Google")

# 認証後の処理
def handle_auth(event):
    token = client.fetch_token(token_url, authorization_response=event.new)
    user_info = client.get('https://www.googleapis.com/oauth2/v1/userinfo').json()
    login_message.value = f"Logged in as: {user_info['name']}"

auth_button.on_click(handle_auth)

# レイアウトの定義
login_message = pn.pane.Markdown("")
layout = pn.Column(auth_link, auth_button, login_message)

layout.servable()
pn.serve(layout)
  • OAuth2Session を使用して、Google OAuthの認証プロセスを管理します。client_idclient_secret はGoogle Cloudで取得した情報を使用します。
  • client.create_authorization_url() でGoogleの認証ページへのリンクを生成し、ユーザーにログインを促します。
  • 認証が成功すると、fetch_token() を使ってアクセストークンを取得し、そのトークンを使ってユーザー情報(例: 名前)を取得します。

これで、Google OAuthを使ったログインがPanelアプリに実装され、セキュアな認証が可能になります。

このプログラムについても、記事の最後に詳細説明を載せていますので参考にして下さい。

まとめ

ここまでで、Panelアプリケーションにシンプルなログイン機能を実装する方法と、OAuthを使ってGoogleを介した認証を行う方法について学びました。次回の記事(後半)では、さらにJWTを使ったトークンベースの認証や、API保護の実装について解説します。セキュリティを強化するための次のステップをぜひお楽しみに!

補足(1) 基本の認証、サンプルプログラムの解説

このプログラムは、Panelフレームワークを使用して、シンプルなログインフォームとユーザー認証機能を実装しています。ログインに成功したユーザーのみが特定のコンテンツにアクセスできる仕組みです。プログラムの各部分を順を追って説明します。

1. Panelの拡張機能の読み込み

import panel as pn
pn.extension()

まず、Panelの拡張機能を有効にする必要があるため、pn.extension()を呼び出します。これにより、Panelのウィジェットやレイアウト機能が使えるようになります。

2. ユーザー情報の定義

USERS = {
    'admin': 'password123',
    'user1': 'mypassword'
}

次に、ユーザー名とパスワードのペアを辞書として定義します。この辞書はログインの際に入力されたユーザー名とパスワードを照合するために使われます。

3. セッション管理

pn.state.user_logged_in = False

Panelpn.stateオブジェクトを使って、セッション状態(ユーザーがログインしているかどうか)を管理します。最初はFalseに設定されており、ユーザーがログインしていないことを示しています。

4. ログインチェック関数

def check_login(username, password):
    if username in USERS and USERS[username] == password:
        pn.state.user_logged_in = True
        return True
    return False

check_login()は、入力されたユーザー名とパスワードが正しいかどうかを確認する関数です。もし辞書USERSに入力されたユーザー名が存在し、そのパスワードが一致する場合、pn.state.user_logged_inTrueに設定し、ログインが成功したことを示します。そうでない場合はFalseを返します。

5. ログインフォームの定義

username_input = pn.widgets.TextInput(name='Username')
password_input = pn.widgets.PasswordInput(name='Password')
login_button = pn.widgets.Button(name='Login')

ここでは、Panelのウィジェットを使って、ログインフォームを作成しています。

  • TextInputはユーザー名の入力欄。
  • PasswordInputはパスワードの入力欄(入力内容が隠される)。
  • Buttonはログインボタンです。

6. ログイン処理

def handle_login(event):
    if check_login(username_input.value, password_input.value):
        login_message.value = 'Login successful!'
        main_area.clear()
        main_area.append(protected_content())
    else:
        login_message.value = 'Invalid credentials.'

login_button.on_click(handle_login)

handle_login()は、ログインボタンがクリックされたときに呼ばれるコールバック関数です。入力されたユーザー名とパスワードが正しいかどうかをcheck_login()で確認し、ログインが成功すれば以下の処理を行います。

  • login_messageに「Login successful!」と表示し、ログイン成功を通知します。
  • main_areaをクリアし、保護されたコンテンツ(protected_content())を表示します。

ログインが失敗した場合は、「Invalid credentials.」と表示します。

7. ログインメッセージ

login_message = pn.pane.Markdown('')

Markdownを使用して、ログイン結果を表示するためのメッセージ領域を作成しています。このメッセージはログインの成否に応じて更新されます。

8. 保護されたコンテンツ

def protected_content():
    return pn.Column("### Welcome to the protected page!", "This content is only for logged-in users.")

ログイン成功後にのみ表示されるコンテンツです。pn.Columnを使って、テキストが縦に並ぶレイアウトを作成しています。この部分は実際のアプリケーションでは、ユーザーごとに異なる内容を表示するなど、自由に拡張できます。

9. メインレイアウト

main_area = pn.Column()

アプリ全体のメインレイアウトです。Columnレイアウトは、各ウィジェットやコンテンツを縦に並べて表示します。

10. ログイン画面の構成

login_area = pn.Column(
    "## Login Page",
    username_input,
    password_input,
    login_button,
    login_message
)

ログインページのUIを構成しています。ユーザー名入力欄、パスワード入力欄、ログインボタン、そしてログイン結果のメッセージをColumnレイアウトで縦に並べています。

11. ログイン状態に基づく表示内容の切り替え

if not pn.state.user_logged_in:
    main_area.append(login_area)
else:
    main_area.append(protected_content())

ここでは、ユーザーがログインしているかどうかに基づいて表示する内容を切り替えています。ログインしていない場合はlogin_areaを表示し、ログイン済みの場合は保護されたコンテンツを表示します。

12. Panelアプリケーションの表示

main_area.servable()
pn.serve(main_area)

最後に、main_area.servable()を使って、このレイアウトをPanelアプリケーションとして表示できるようにします。pn.serve(main_area)でアプリケーションを実際に起動し、ブラウザからアクセスできるようにします。


結論

このプログラムは、非常にシンプルなユーザー認証を実装したPanelアプリケーションです。pn.stateを用いてログイン状態を管理し、ログインの成否によって表示内容を動的に切り替えます。このようなシステムは、ウェブアプリケーションでよく使用されるユーザー認証機能の基本となります。

補足(2) OAuthサンプルプログラムの解説

このサンプルプログラムは、OAuth2を使用してGoogleアカウントでの認証を行うPanelアプリケーションを作成するものです。具体的には、ユーザーがGoogleアカウントでログインし、その後のユーザー情報(名前など)を取得する流れを実現しています。以下に、このコードの各部分を丁寧に解説します。

1. 必要なライブラリのインポート

from authlib.integrations.requests_client import OAuth2Session
import panel as pn
  • OAuth2Session: authlibライブラリのクラスで、OAuth 2.0フローを簡単に扱うためのセッション管理を提供します。このクラスを用いて、認証トークンの取得や認証リクエストの作成を行います。
  • panel: Webアプリケーションやインタラクティブなダッシュボードを作成するためのライブラリで、UIの生成やイベント処理を扱います。

2. Panelの拡張機能を有効化

pn.extension()

PanelアプリケーションでUIコンポーネントや拡張機能を使用するために、pn.extension()でPanelの拡張機能を有効化します。

3. OAuth2認証に必要なクライアント情報の設定

client_id = 'YOUR_CLIENT_ID'
client_secret = 'YOUR_CLIENT_SECRET'

Google APIを使ってOAuth2認証を行うには、クライアントIDとクライアントシークレットが必要です。これらはGoogleのAPIコンソールから取得します。

4. 認証エンドポイントの設定

authorize_url = 'https://accounts.google.com/o/oauth2/auth'
token_url = 'https://oauth2.googleapis.com/token'
  • authorize_url: Googleアカウントの認証を行うためのエンドポイントURLです。ここにリクエストを送信してユーザーにログインを促します。
  • token_url: 認証後にアクセストークンを取得するためのURLです。このURLを使用して、OAuth 2.0フローのトークンを取得します。

5. OAuth2セッションの初期化

client = OAuth2Session(client_id, client_secret, redirect_uri='http://localhost:5000')
  • OAuth2Session: OAuth2のセッションを開始します。client_idclient_secretを渡し、redirect_uriで認証後のリダイレクト先を指定します。ここではローカルホストのhttp://localhost:5000に設定されています。

6. 認証リンクの生成

auth_url, state = client.create_authorization_url(authorize_url)
  • create_authorization_url: 認証用のURLを生成し、そのURLにユーザーをリダイレクトすることで、Googleでのログインを促します。このURLは、Googleのログインページへのリンクです。
  • state: CSRF(クロスサイトリクエストフォージェリ)対策として使用されるランダムな文字列で、後でセッションが一致するか確認します。

7. 認証リンクとUIボタンの作成

auth_link = pn.pane.Markdown(f"[Login with Google]({auth_url})")
auth_button = pn.widgets.Button(name="Login with Google")
  • auth_link: 認証リンクをMarkdown形式で表示します。ユーザーがクリックすると、Googleの認証ページにリダイレクトされます。
  • auth_button: Googleでのログインを促すボタンを作成します。ボタンの名前は「Login with Google」と設定されています。

8. 認証後の処理

def handle_auth(event):
    token = client.fetch_token(token_url, authorization_response=event.new)
    user_info = client.get('https://www.googleapis.com/oauth2/v1/userinfo').json()
    login_message.value = f"Logged in as: {user_info['name']}"
  • handle_auth: 認証が成功した後の処理を定義しています。ユーザーが認証を完了した後、fetch_tokenメソッドでアクセストークンを取得します。
  • authorization_response: 認証後にリダイレクトされたURLから必要な情報を抽出し、トークンを取得するために使用します。
  • get: Googleのユーザー情報API(https://www.googleapis.com/oauth2/v1/userinfo)にリクエストを送信し、ログインしたユーザーの名前などの情報を取得します。
  • login_message: 取得したユーザーの名前を表示するために、login_messageの値を更新します。

9. ボタンのクリックイベント処理

auth_button.on_click(handle_auth)
  • on_click: ボタンがクリックされた際にhandle_auth関数を実行するよう設定しています。

10. レイアウトの定義

login_message = pn.pane.Markdown("")
layout = pn.Column(auth_link, auth_button, login_message)
  • login_message: 認証結果を表示するための空のMarkdownパネルを初期化しています。ログイン後にここにユーザー名が表示されます。
  • layout: PanelのColumnレイアウトを使用して、認証リンク、ログインボタン、メッセージ表示領域を縦に並べたレイアウトを定義します。

11. アプリケーションの起動

layout.servable()
pn.serve(layout)
  • servable(): PanelのレイアウトをWebアプリケーションとして公開できるようにします。これにより、pn.serve()でこのレイアウトがブラウザに表示されるようになります。
  • pn.serve(layout): この関数は、Panelアプリケーションをサーバー上で動作させ、ブラウザからアクセス可能にします。

全体の流れ

  1. アプリが起動すると、Googleでログインするためのリンクとボタンが表示されます。
  2. ユーザーがリンクまたはボタンをクリックすると、Googleの認証ページにリダイレクトされます。
  3. 認証が成功すると、アプリはGoogleからアクセストークンを取得し、ユーザー情報(名前など)を取得します。
  4. 最後に、取得したユーザー名がアプリ内に表示されます。

このプログラムを実行するためには、事前にGoogle APIでOAuth 2.0のクライアントIDとシークレットを取得し、適切に設定する必要があります。

コメント

タイトルとURLをコピーしました