重要
2025 年 5 月 1 日より、Azure AD B2C は新規のお客様向けに購入できなくなります。 詳細については、FAQ を参照してください。
この記事では、独自の Python Web アプリケーションに Azure Active Directory B2C (Azure AD B2C) 認証を追加する方法について説明します。 ユーザーが Azure AD B2C ユーザー フローを使用して、サインイン、サインアウト、プロファイルの更新、パスワードのリセットを行えるようにします。 この記事では、 Python 用 Microsoft Authentication Library (MSAL) を使用して、Python Web アプリケーションへの認証の追加を簡略化します。
この記事の目的は、「 Azure AD B2C と独自の Python アプリケーションを使用して、サンプル Python Web アプリケーションで認証を構成する」で使用したサンプル アプリケーションに置き換えます。
この記事では、 Python 3.9 以降 と Flask 2.1 を使用して基本的な Web アプリを作成します。 アプリケーションのビューでは 、Jinja2 テンプレートが使用されます。
[前提条件]
- 「Azure AD B2C を使用してサンプル Python Web アプリケーションで認証を構成する」の手順を完了します。 Azure AD B2C ユーザー フローを作成し、Azure portal で Web アプリケーションを登録します。
- Python 3.9 以降をインストールする
- Visual Studio Code または別のコード エディター
- Visual Studio Code 用の Python 拡張機能 をインストールする
手順 1: Python プロジェクトを作成する
ファイル システムで、このチュートリアル用のプロジェクト フォルダー (
my-python-web-app
など) を作成します。ターミナルで、ディレクトリを Python アプリ フォルダー (
cd my-python-web-app
など) に変更します。次のコマンドを実行して、現在のインタープリターに基づいて
.venv
という名前の仮想環境を作成してアクティブ化します。ターミナルで次のコマンドを実行して、仮想環境の pip を更新します。
python -m pip install --upgrade pip
Flask デバッグ機能を有効にするには、Flask を開発環境に
development
モードに切り替えます。 Flask アプリのデバッグの詳細については、 Flask のドキュメントを参照してください。code .
コマンドを実行するか、VS Code を開いて ファイル メニューから > を選択してプロジェクト フォルダーを開きます。
手順 2: アプリの依存関係をインストールする
Web アプリのルート フォルダーの下に、 requirements.txt
ファイルを作成します。 要件ファイルには、pip インストールを使用してインストールする パッケージが一覧表示 されます。 requirements.txt ファイルに次の内容を追加します。
Flask>=2
werkzeug>=2
flask-session>=0.3.2,<0.5
requests>=2,<3
msal>=1.7,<2
ターミナルで、次のコマンドを実行して依存関係をインストールします。
手順 3: アプリ UI コンポーネントをビルドする
Flask は、URL ルーティングとページ レンダリングの基本を提供する、Web アプリケーション向けの軽量の Python フレームワークです。 Jinja2 をテンプレート エンジンとして使用して、アプリのコンテンツをレンダリングします。 詳細については、 テンプレート デザイナーのドキュメントを参照してください。 このセクションでは、Web アプリの基本的な機能を提供する必要なテンプレートを追加します。
手順 3.1 ベース テンプレートを作成する
Flask のベース ページ テンプレートには、CSS ファイル、スクリプト ファイルなどの参照を含む、一連のページのすべての共有部分が含まれています。 また、ベース テンプレートでは、ベースを拡張する他のテンプレートがオーバーライドする必要がある 1 つ以上のブロック タグも定義されます。 ブロック タグは、基本テンプレートと拡張テンプレートの両方で {% block <name> %}
および {% endblock %}
によって線付けされます。
Web アプリのルート フォルダーに、 templates
フォルダーを作成します。 templates フォルダーで、 base.html
という名前のファイルを作成し、次の内容を追加します。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
{% block metadata %}{% endblock %}
<title>{% block title %}{% endblock %}</title>
<!-- Bootstrap CSS file reference -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="{{ url_for('index')}}">Python Flask demo</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="{{ url_for('index')}}">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('graphcall')}}">Graph API</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container body-content">
<br />
{% block content %}
{% endblock %}
<hr />
<footer>
<p>Powered by MSAL Python {{ version }}</p>
</footer>
</div>
</body>
</html>
手順 3.2 Web アプリ テンプレートを作成する
templates フォルダーの下に次のテンプレートを追加します。 これらのテンプレートは、 base.html
テンプレートを拡張します。
index.html: Web アプリのホーム ページ。 テンプレートでは、次のロジックが使用されます。ユーザーがサインインしない場合は、サインイン ボタンがレンダリングされます。 ユーザーがサインインすると、アクセス トークンの要求がレンダリングされ、プロファイルを編集するためのリンクが表示され、Graph API が呼び出されます。
{% extends "base.html" %} {% block title %}Home{% endblock %} {% block content %} <h1>Microsoft Identity Python Web App</h1> {% if user %} <h2>Claims:</h2> <pre>{{ user |tojson(indent=4) }}</pre> {% if config.get("ENDPOINT") %} <li><a href='/graphcall'>Call Microsoft Graph API</a></li> {% endif %} {% if config.get("B2C_PROFILE_AUTHORITY") %} <li><a href='{{_build_auth_code_flow(authority=config["B2C_PROFILE_AUTHORITY"])["auth_uri"]}}'>Edit Profile</a></li> {% endif %} <li><a href="/logout">Logout</a></li> {% else %} <li><a href='{{ auth_url }}'>Sign In</a></li> {% endif %} {% endblock %}
graph.html: REST API を呼び出す方法を示します。
{% extends "base.html" %} {% block title %}Graph API{% endblock %} {% block content %} <a href="javascript:window.history.go(-1)">Back</a> <!-- Displayed on top of a potentially large JSON response, so it will remain visible --> <h1>Graph API Call Result</h1> <pre>{{ result |tojson(indent=4) }}</pre> <!-- Just a generic json viewer --> {% endblock %}
auth_error.html: 認証エラーを処理します。
{% extends "base.html" %} {% block title%}Error{% endblock%} {% block metadata %} {% if config.get("B2C_RESET_PASSWORD_AUTHORITY") and "AADB2C90118" in result.get("error_description") %} <!-- See also https://learn.microsoft.com/azure/active-directory-b2c/active-directory-b2c-reference-policies#linking-user-flows --> <meta http-equiv="refresh" content='0;{{_build_auth_code_flow(authority=config["B2C_RESET_PASSWORD_AUTHORITY"])["auth_uri"]}}'> {% endif %} {% endblock %} {% block content %} <h2>Login Failure</h2> <dl> <dt>{{ result.get("error") }}</dt> <dd>{{ result.get("error_description") }}</dd> </dl> <a href="{{ url_for('index') }}">Homepage</a> {% endblock %}
手順 4: Web アプリを構成する
Web アプリのルート フォルダーに、 app_config.py
という名前のファイルを作成します。 このファイルには、Azure AD B2C ID プロバイダーに関する情報が含まれています。 Web アプリはこの情報を使用して、Azure AD B2C との信頼関係の確立、ユーザーのサインインとサインアウト、トークンの取得、検証を行います。 ファイルに次の内容を追加します。
import os
b2c_tenant = "fabrikamb2c"
signupsignin_user_flow = "B2C_1_signupsignin1"
editprofile_user_flow = "B2C_1_profileediting1"
resetpassword_user_flow = "B2C_1_passwordreset1" # Note: Legacy setting.
authority_template = "https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/{user_flow}"
CLIENT_ID = "Enter_the_Application_Id_here" # Application (client) ID of app registration
CLIENT_SECRET = "Enter_the_Client_Secret_Here" # Application secret.
AUTHORITY = authority_template.format(
tenant=b2c_tenant, user_flow=signupsignin_user_flow)
B2C_PROFILE_AUTHORITY = authority_template.format(
tenant=b2c_tenant, user_flow=editprofile_user_flow)
B2C_RESET_PASSWORD_AUTHORITY = authority_template.format(
tenant=b2c_tenant, user_flow=resetpassword_user_flow)
REDIRECT_PATH = "/getAToken"
# This is the API resource endpoint
ENDPOINT = '' # Application ID URI of app registration in Azure portal
# These are the scopes you've exposed in the web API app registration in the Azure portal
SCOPE = [] # Example with two exposed scopes: ["demo.read", "demo.write"]
SESSION_TYPE = "filesystem" # Specifies the token cache should be stored in server-side session
上記のコードは、サンプル Python Web アプリの構成に関する記事の「認証の構成」の「サンプル Web アプリの構成」セクションで説明されているように、Azure AD B2C 環境設定で更新してください。
手順 5: Web アプリ コードを追加する
このセクションでは、Flask ビュー関数と MSAL ライブラリ認証方法を追加します。 プロジェクトのルート フォルダーの下に、 app.py
という名前のファイルを次のコードで追加します。
import uuid
import requests
from flask import Flask, render_template, session, request, redirect, url_for
from flask_session import Session # https://pythonhosted.org/Flask-Session
import msal
import app_config
app = Flask(__name__)
app.config.from_object(app_config)
Session(app)
# This section is needed for url_for("foo", _external=True) to automatically
# generate http scheme when this sample is running on localhost,
# and to generate https scheme when it is deployed behind reversed proxy.
# See also https://flask.palletsprojects.com/en/1.0.x/deploying/wsgi-standalone/#proxy-setups
from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1)
@app.route("/anonymous")
def anonymous():
return "anonymous page"
@app.route("/")
def index():
#if not session.get("user"):
# return redirect(url_for("login"))
if not session.get("user"):
session["flow"] = _build_auth_code_flow(scopes=app_config.SCOPE)
return render_template('index.html', auth_url=session["flow"]["auth_uri"], version=msal.__version__)
else:
return render_template('index.html', user=session["user"], version=msal.__version__)
@app.route("/login")
def login():
# Technically we could use empty list [] as scopes to do just sign in,
# here we choose to also collect end user consent upfront
session["flow"] = _build_auth_code_flow(scopes=app_config.SCOPE)
return render_template("login.html", auth_url=session["flow"]["auth_uri"], version=msal.__version__)
@app.route(app_config.REDIRECT_PATH) # Its absolute URL must match your app's redirect_uri set in AAD
def authorized():
try:
cache = _load_cache()
result = _build_msal_app(cache=cache).acquire_token_by_auth_code_flow(
session.get("flow", {}), request.args)
if "error" in result:
return render_template("auth_error.html", result=result)
session["user"] = result.get("id_token_claims")
_save_cache(cache)
except ValueError: # Usually caused by CSRF
pass # Simply ignore them
return redirect(url_for("index"))
@app.route("/logout")
def logout():
session.clear() # Wipe out user and its token cache from session
return redirect( # Also logout from your tenant's web session
app_config.AUTHORITY + "/oauth2/v2.0/logout" +
"?post_logout_redirect_uri=" + url_for("index", _external=True))
@app.route("/graphcall")
def graphcall():
token = _get_token_from_cache(app_config.SCOPE)
if not token:
return redirect(url_for("login"))
graph_data = requests.get( # Use token to call downstream service
app_config.ENDPOINT,
headers={'Authorization': 'Bearer ' + token['access_token']},
).json()
return render_template('graph.html', result=graph_data)
def _load_cache():
cache = msal.SerializableTokenCache()
if session.get("token_cache"):
cache.deserialize(session["token_cache"])
return cache
def _save_cache(cache):
if cache.has_state_changed:
session["token_cache"] = cache.serialize()
def _build_msal_app(cache=None, authority=None):
return msal.ConfidentialClientApplication(
app_config.CLIENT_ID, authority=authority or app_config.AUTHORITY,
client_credential=app_config.CLIENT_SECRET, token_cache=cache)
def _build_auth_code_flow(authority=None, scopes=None):
return _build_msal_app(authority=authority).initiate_auth_code_flow(
scopes or [],
redirect_uri=url_for("authorized", _external=True))
def _get_token_from_cache(scope=None):
cache = _load_cache() # This web app maintains one cache per session
cca = _build_msal_app(cache=cache)
accounts = cca.get_accounts()
if accounts: # So all account(s) belong to the current signed-in user
result = cca.acquire_token_silent(scope, account=accounts[0])
_save_cache(cache)
return result
app.jinja_env.globals.update(_build_auth_code_flow=_build_auth_code_flow) # Used in template
if __name__ == "__main__":
app.run()
手順 6: Web アプリを実行する
ターミナルで、次のコマンドを入力してアプリを実行し、Flask 開発サーバーを実行します。 開発サーバーは、既定で app.py
を検索します。 次に、ブラウザーを開き、Web アプリの URL ( http://localhost:5000
) に移動します。
[省略可能]アプリをデバッグする
デバッグ機能を使用すると、特定のコード行で実行中のプログラムを一時停止できます。 プログラムを一時停止すると、変数を調べたり、デバッグ コンソール パネルでコードを実行したり、 デバッグで説明されている機能を利用したりできます。 Visual Studio Code デバッガーを使用するには、 VS Code のドキュメントを確認してください。
ホスト名やポート番号を変更するには、args
ファイルのlaunch.json
配列を使用します。 次の例では、ホスト名を localhost
し、ポート番号を 5001
するように構成する方法を示します。 ホスト名またはポート番号を変更する場合は、リダイレクト URI またはアプリケーションを更新する必要があります。 詳細については、「 Web アプリケーションの登録 」の手順を参照してください。
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: Flask",
"type": "python",
"request": "launch",
"module": "flask",
"env": {
"FLASK_APP": "app.py",
"FLASK_ENV": "development"
},
"args": [
"run",
"--host=localhost",
"--port=5001"
],
"jinja": true,
"justMyCode": true
}
]
}