تمكين المصادقة في تطبيق ويب Python الخاص بك باستخدام Azure Active Directory B2C

هام

اعتبارا من 1 مايو 2025، لن يكون Azure AD B2C متوفرا للشراء للعملاء الجدد. تعرف على المزيد في الأسئلة المتداولة.

في هذه المقالة، ستتعلم كيفية إضافة مصادقة Azure Active Directory B2C (Azure AD B2C) في تطبيق ويب Python الخاص بك. يمكنك تمكين المستخدمين من تسجيل الدخول وتسجيل الخروج وتحديث ملف التعريف وإعادة تعيين كلمة المرور باستخدام تدفقات مستخدم Azure AD B2C. تستخدم هذه المقالة مكتبة مصادقة Microsoft (MSAL) ل Python لتبسيط إضافة المصادقة إلى تطبيق ويب Python.

الهدف من هذه المقالة هو استبدال نموذج التطبيق الذي استخدمته في تكوين المصادقة في نموذج تطبيق ويب Python باستخدام Azure AD B2C مع تطبيق Python الخاص بك.

تستخدم هذه المقالة Python 3.9+وFlask 2.1 لإنشاء تطبيق ويب أساسي. تستخدم طرق عرض التطبيق قوالب Jinja2.

المتطلبات الأساسية

الخطوة 1: إنشاء مشروع Python

  1. على نظام الملفات الخاص بك، قم بإنشاء مجلد مشروع لهذا البرنامج التعليمي، مثل my-python-web-app.

  2. في المحطة الطرفية، قم بتغيير الدليل إلى مجلد تطبيق Python، مثل cd my-python-web-app.

  3. قم بتشغيل الأمر التالي لإنشاء بيئة ظاهرية باسم وتنشيطها .venv استنادا إلى المترجم الحالي.

    sudo apt-get install python3-venv  # If needed
    python3 -m venv .venv
    source .venv/bin/activate
    
  4. تحديث النقطة في البيئة الظاهرية عن طريق تشغيل الأمر التالي في المحطة الطرفية:

    python -m pip install --upgrade pip
    
  5. لتمكين ميزات تتبع أخطاء Flask، قم بتبديل Flask إلى بيئة التطوير إلى development الوضع. لمزيد من المعلومات حول تصحيح أخطاء تطبيقات Flask، راجع وثائق Flask.

    export FLASK_ENV=development
    
  6. افتح مجلد المشروع في VS Code عن طريق تشغيل code . الأمر، أو عن طريق فتح VS Code وتحديد File>Open Folder.

الخطوة 2: تثبيت تبعيات التطبيق

ضمن المجلد الجذر لتطبيق الويب الخاص بك، قم بإنشاء requirements.txt الملف. يسرد ملف المتطلبات الحزم التي سيتم تثبيتها باستخدام تثبيت pip. أضف المحتوى التالي إلى ملف requirements.txt:

Flask>=2
werkzeug>=2

flask-session>=0.3.2,<0.5
requests>=2,<3
msal>=1.7,<2

في المحطة الطرفية، قم بتثبيت التبعيات عن طريق تشغيل الأوامر التالية:

python -m pip install -r requirements.txt

الخطوة 3: إنشاء مكونات واجهة مستخدم التطبيق

Flask هو إطار عمل Python خفيف الوزن لتطبيقات الويب التي توفر أساسيات توجيه عنوان URL وعرض الصفحة. ويستخدم Jinja2 كمحرك قالب لعرض محتوى تطبيقك. لمزيد من المعلومات، راجع وثائق مصمم القالب. في هذا القسم، يمكنك إضافة القوالب المطلوبة التي توفر الوظائف الأساسية لتطبيق الويب الخاص بك.

الخطوة 3.1 إنشاء قالب أساسي

يحتوي قالب الصفحة الأساسية في Flask على جميع الأجزاء المشتركة لمجموعة من الصفحات، بما في ذلك مراجع ملفات CSS وملفات البرنامج النصي وما إلى ذلك. تحدد القوالب الأساسية أيضا علامة كتلة واحدة أو أكثر من المتوقع أن تتجاوزها القوالب الأخرى التي توسع القاعدة. يتم تحديد علامة الكتلة بواسطة {% block <name> %} و {% endblock %} في كل من القالب الأساسي والقالب الموسع.

في المجلد الجذر لتطبيق الويب الخاص بك، قم بإنشاء 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 إنشاء قوالب تطبيق الويب

أضف القوالب التالية ضمن مجلد القوالب. توسع هذه القوالب القالب base.html :

  • index.html: الصفحة الرئيسية لتطبيق الويب. تستخدم القوالب المنطق التالي: إذا لم يقم المستخدم بتسجيل الدخول، فإنه يعرض زر تسجيل الدخول. إذا سجل المستخدم الدخول، فإنه يعرض مطالبات الرمز المميز للوصول، ويربط بتحرير ملف التعريف، ويستدعي واجهة برمجة تطبيقات Graph.

    {% 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.

    {% 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: تكوين تطبيق الويب الخاص بك

في المجلد الجذر لتطبيق الويب الخاص بك، أنشئ ملفا باسم app_config.py. يحتوي هذا الملف على معلومات حول موفر هوية Azure AD B2C. يستخدم تطبيق الويب هذه المعلومات لإنشاء علاقة ثقة مع 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

قم بتحديث التعليمات البرمجية أعلاه بإعدادات بيئة Azure AD B2C كما هو موضح في قسم تكوين نموذج تطبيق الويب في مقالة تكوين المصادقة في نموذج تطبيق ويب Python .

الخطوة 5: إضافة التعليمات البرمجية لتطبيق الويب

في هذا القسم، يمكنك إضافة وظائف عرض 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: تشغيل تطبيق الويب الخاص بك

في المحطة الطرفية، قم بتشغيل التطبيق عن طريق إدخال الأمر التالي، الذي يقوم بتشغيل خادم تطوير Flask. يبحث app.py خادم التطوير بشكل افتراضي. ثم افتح المتصفح وانتقل إلى عنوان URL لتطبيق الويب: http://localhost:5000.

python -m flask run --host localhost --port 5000

[اختياري] تصحيح أخطاء تطبيقك

تمنحك ميزة تصحيح الأخطاء الفرصة لإيقاف برنامج قيد التشغيل مؤقتا على سطر معين من التعليمات البرمجية. عند إيقاف البرنامج مؤقتا، يمكنك فحص المتغيرات، وتشغيل التعليمات البرمجية في لوحة Debug Console، وبخلاف ذلك الاستفادة من الميزات الموضحة في تصحيح الأخطاء. لاستخدام مصحح أخطاء Visual Studio Code، تحقق من وثائق VS Code.

لتغيير اسم المضيف و/أو رقم المنفذ، استخدم args صفيف launch.json الملف. يوضح المثال التالي كيفية تكوين اسم المضيف إلى localhost ورقم المنفذ إلى 5001. ملاحظة، إذا قمت بتغيير اسم المضيف أو رقم المنفذ، يجب تحديث عنوان URI لإعادة التوجيه أو التطبيق الخاص بك. لمزيد من المعلومات، راجع خطوة تسجيل تطبيق ويب .

{
    // 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
        }
    ]
}