チュートリアル: Python Flask Web アプリにサインインを追加する

このチュートリアルは、Python Flask Web アプリをゼロから構築し、Microsoft ID プラットフォームを使用して認証を統合する方法を示すチュートリアル シリーズの第 3 部です。 このチュートリアルでは、構築したアプリでユーザーを認証するコードを追加します。

  • 必要なモジュールと構成をインポートする
  • Flask Web アプリのインスタンスを作成する
  • ローカル開発用に ProxyFix ミドルウェアを構成する
  • ユーザーをサインインおよびサインアウトするコードを追加する
  • Web アプリのエントリ ポイントを定義する

必要なパッケージと構成をインポートする

構築している Web アプリでは、MSAL Python 上に構築された identity.web パッケージを使用して、Web アプリでユーザーを認証します。 identity.web パッケージ、Flask フレームワーク、Flask モジュール、Flask セッション、および前のチュートリアルで定義したアプリ構成をインポートするには、次のコードを app.py に追加します。

import identity.web
import requests
from flask import Flask, redirect, render_template, request, session, url_for
from flask_session import Session

import app_config

このコード スニペットでは、Flask で Web 要求とセッションを処理するための関数とオブジェクトである redirectrender_templaterequestsessionurl_for をインポートします。 アプリの構成設定が含まれる app_config もインポートします。

Flask Web アプリのインスタンスを作成する

必要なモジュールをインポートした後、app-config の構成を使用して Web アプリを初期化します。 Web アプリのインスタンスを作成するには、次のスニペットを次のように app.py に追加します。

app = Flask(__name__)
app.config.from_object(app_config)
assert app.config["REDIRECT_PATH"] != "/", "REDIRECT_PATH must not be /"
Session(app)

上記のコード スニペットでは、新しい Flask アプリケーションを初期化し、app.config.from_object(app_config) を使用して構成設定を読み込みます。 from_object を使用すると、アプリは (app_config) に指定されている構成を継承します。

また、アサーション チェックを実行して、アプリのリダイレクト パスがルート パス ("/") に設定されていないことを確認します。 Session(app) はアプリのセッション管理を初期化します。これにより、セッションを処理し、複数の要求にわたってユーザー認証状態などのデータを格納できます。

ローカル開発用に ProxyFix ミドルウェアを構成する

サンプル Web アプリはローカル ホストで実行されるため、ProxyFix ミドルウェアを使用して要求ヘッダーの URL スキームとホスト情報を修正します。 ProxyFix を適用するには、次のコードを app.py に追加します。

from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1)

認証オブジェクトを初期化する

次に、[identity.web.Auth](https://identity-library.readthedocs.io/en/latest/#identity.web.Auth) クラスのインスタンスを作成して認証オブジェクトを初期化します。 次のように、Auth オブジェクトを初期化するときに、コンストラクターでパラメーター sessionauthorityclient_id、および client_credential も渡します。

app.jinja_env.globals.update(Auth=identity.web.Auth)  # Useful in template for B2C
auth = identity.web.Auth(
    session=session,
    authority=app.config["AUTHORITY"],
    client_id=app.config["CLIENT_ID"],
    client_credential=app.config["CLIENT_SECRET"],
)

このコード スニペットでは、app.jinja_env.globals.update(Auth=identity.web.Auth)Auth という名前の新しいグローバル変数を追加し、これに identity.web.Auth の値を割り当てます。 これにより、Flask アプリケーションによってレンダリングされるすべてのテンプレートで Auth にアクセスできるようになります。

ユーザーのサインイン

このアプリで構築する承認フローは、2 つの分岐で構成されます。 第 1 段階では、次に示すように、auth.log_in 関数を呼び出してユーザーをサインインします。

@app.route("/login")
def login():
    return render_template("login.html", version=__version__, **auth.log_in(
        scopes=app_config.SCOPE, # Have user consent to scopes during log-in
        redirect_uri=url_for("auth_response", _external=True), # Optional. If present, this absolute URL must match your app's redirect_uri registered in Microsoft Entra admin center
        prompt="select_account",  # Optional.
        ))

ユーザーがアプリの /login URL に移動すると、Flask は、login.html テンプレートをレンダリングする要求を処理するビュー関数を呼び出します。 login() 内では、サインイン プロセス中にユーザーが同意する必要があるスコープの一覧を使用して、auth.log_in 関数を呼び出します。 また、パラメーターに redirect_uri を指定します。これは、アプリのリダイレクト URI i Microsoft Azure 管理センターと一致する必要があります。

必要に応じて、アクティブなセッションを持つアカウント間で再認証、ユーザーの同意、またはアカウントの選択を要求することで、ログイン プロンプトのビヘイビアーを制御する prompt などのパラメーターを追加できます。

承認フローの 2 番目の分岐では、次に示すように、redirect_uri コントローラー内で auth.complete_log_in 関数を呼び出して認証応答を処理します。

@app.route(app_config.REDIRECT_PATH)
def auth_response():
    result = auth.complete_log_in(request.args)
    if "error" in result:
        return render_template("auth_error.html", result=result)
    return redirect(url_for("index"))

この complete_log_in() 関数は、受信 auth_response ディクショナリをクエリ パラメーターとして受け取ります。 成功した場合、関数は redirect(url_for("index")) を使用してユーザーを "インデックス" ルートにリダイレクトします。 これは、ユーザーが正常にログインしたことを意味し、ユーザーの情報は、既に検証済みの ID トークンからの要求を含むディクショナリとして使用できます。

条件 if "error" in result: によって決定されたエラーが結果に含まれている場合は、"auth_error.html" テンプレートをユーザーにレンダリングします。

ユーザーのサインアウト

Flask アプリケーションからユーザーをサインアウトするには、次のように auth.log_out() メソッドを呼び出します。

@app.route("/logout")
def logout():
    return redirect(auth.log_out(url_for("index", _external=True)))

ユーザーがアプリの /logout URL ルートに移動すると、Flask は現在のアプリからサインアウトするログアウト関数を呼び出します。 また、ログアウト時にユーザーをリダイレクトするページも指定します。コード スニペットでは、url_for("index", _external=True). を使用してユーザーをアプリのホーム ページにリダイレクトします

Web アプリのエントリ ポイントを定義する

サインインとサインアウトのロジックを実装した後、次のように index() 関数を作成して、アプリのホーム ページにエントリ ポイントを追加します。

@app.route("/")
def index():
    if not (app.config["CLIENT_ID"] and app.config["CLIENT_SECRET"]):
        # This check is not strictly necessary.
        # You can remove this check from your production code.
        return render_template('config_error.html')
    if not auth.get_user():
        return redirect(url_for("login"))
    return render_template('index.html', user=auth.get_user(), version=__version__)

index() 関数は、ユーザーがアプリのルート URL("/") に移動したときに呼び出されます。 アプリのホームページをレンダリングする前に、構成チェックを処理し、ユーザー認証を検証します。 クライアント ID とクライアント シークレットが構成に不足しているかどうかを確認し、どちらか一方または両方の値がない場合は、Flask によって "config_error.html" テンプレートがレンダリングされます。

また、この関数は auth.get_user() を呼び出して、ユーザーが認証されているかどうかを確認します。 ユーザーが認証されていない場合、ユーザーは "login" ルートにリダイレクトされます。 認証されている場合、Flask は "index.html" テンプレートをレンダリングし、レンダリングのためにユーザー オブジェクト (auth.get_user() から取得) を渡します。

次のステップ