Condividi tramite


Abilitare l'autenticazione nell'applicazione Web Python usando Azure Active Directory B2C

Importante

A partire dal 1° maggio 2025, Azure AD B2C non sarà più disponibile per l'acquisto per i nuovi clienti. Altre informazioni sono disponibili nelle domande frequenti.

Questo articolo descrive come aggiungere l'autenticazione di Azure Active Directory B2C (Azure AD B2C) nella propria applicazione Web Python. È possibile consentire agli utenti di accedere, disconnettersi, aggiornare il profilo e reimpostare la password usando i flussi utente di Azure AD B2C. Questo articolo usa Microsoft Authentication Library (MSAL) per Python per semplificare l'aggiunta dell'autenticazione all'applicazione Web Python.

L'obiettivo di questo articolo è sostituire l'applicazione di esempio usata in Configurare l'autenticazione in un'applicazione Web Python di esempio usando Azure AD B2C con la propria applicazione Python.

Questo articolo usa Python 3.9+ e Flask 2.1 per creare un'app Web di base. Le visualizzazioni dell'applicazione usano modelli Jinja2.

Prerequisiti

Passaggio 1: Creare il progetto Python

  1. Nel file system creare una cartella di progetto per questa esercitazione, ad esempio my-python-web-app.

  2. Nel terminale, cambia la directory verso la cartella della tua app Python, come cd my-python-web-app.

  3. Eseguire il comando seguente per creare e attivare un ambiente virtuale denominato .venv in base all'interprete corrente.

    sudo apt-get install python3-venv  # If needed
    python3 -m venv .venv
    source .venv/bin/activate
    
  4. Aggiornare pip nell'ambiente virtuale eseguendo il comando seguente nel terminale:

    python -m pip install --upgrade pip
    
  5. Per abilitare le funzionalità di debug di Flask, passa Flask all'ambiente di sviluppo nella modalità development. Per altre informazioni sul debug delle app Flask, vedere la documentazione di Flask.

    export FLASK_ENV=development
    
  6. Aprire la cartella del progetto in VS Code eseguendo il code . comando oppure aprendo VS Code e selezionando File>Apri cartella.

Passaggio 2: Installare le dipendenze delle app

Nella cartella radice dell'app Web creare il requirements.txt file. Il file dei requisiti elenca i pacchetti da installare tramite pip install. Aggiungere il contenuto seguente al file requirements.txt:

Flask>=2
werkzeug>=2

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

Nel terminale installare le dipendenze eseguendo i comandi seguenti:

python -m pip install -r requirements.txt

Passaggio 3: Creare componenti dell'interfaccia utente dell'app

Flask è un framework Python leggero per le applicazioni Web che fornisce le nozioni di base per il routing url e il rendering delle pagine. Usa Jinja2 come motore di modelli per eseguire il rendering del contenuto dell'app. Per ulteriori informazioni, verificare la documentazione del progettista di modelli. In questa sezione vengono aggiunti i modelli necessari che forniscono la funzionalità di base dell'app Web.

Passaggio 3.1 Creare un modello di base

Un modello di pagina di base in Flask contiene tutte le parti condivise di un set di pagine, inclusi riferimenti a file CSS, file di script e così via. I modelli di base definiscono anche uno o più tag di blocco che ci si aspetta siano sovrascritti da altri modelli che estendono la base. Un tag di blocco è delineato da {% block <name> %} e {% endblock %} sia nel modello di base che nel modello esteso.

Nella cartella radice dell'app Web creare la templates cartella . Nella cartella templates creare un file denominato base.htmle quindi aggiungere il contenuto seguente:

<!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>

Passaggio 3.2 Creare i modelli di app Web

Aggiungere i modelli seguenti nella cartella templates. Questi modelli estendono il base.html modello:

  • index.html: home page dell'app Web. I modelli usano la logica seguente: se un utente non effettua l'accesso, mostra il pulsante di login. Se un utente esegue l'accesso, visualizza le attestazioni del token di accesso, il collegamento per modificare il profilo e chiama un'API 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: illustra come chiamare un'API 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: gestisce gli errori di autenticazione.

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

Passaggio 4: Configurare l'app Web

Nella cartella radice dell'app Web creare un file denominato app_config.py. Questo file contiene informazioni sul provider di identità di Azure AD B2C. L'app Web usa queste informazioni per stabilire una relazione di trust con Azure AD B2C, consentire l'accesso e l'uscita degli utenti, acquisire i token e convalidarli. Aggiungere il contenuto seguente nel file:

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

Aggiornare il codice precedente con le impostazioni dell'ambiente Azure AD B2C come illustrato nella sezione Configurare l'app Web di esempio dell'articolo Configurare l'autenticazione in un'app Web Python di esempio .

Passaggio 5: Aggiungere il codice dell'app Web

In questa sezione vengono aggiunte le funzioni di visualizzazione Flask e i metodi di autenticazione della libreria MSAL. Nella cartella radice del progetto aggiungere un file denominato app.py con il codice seguente:

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()

Passaggio 6: Eseguire l'app Web

Nel terminale eseguire l'app immettendo il comando seguente, che esegue il server di sviluppo Flask. Il server di sviluppo cerca app.py per l'impostazione predefinita. Aprire quindi il browser e passare all'URL dell'app Web: http://localhost:5000.

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

[Facoltativo] Eseguire il debug dell'app

La funzionalità di debug consente di sospendere un programma in esecuzione in una determinata riga di codice. Quando si sospende il programma, è possibile esaminare le variabili, eseguire il codice nel pannello Console di debug e in caso contrario sfruttare le funzionalità descritte in Debug. Per usare il debugger di Visual Studio Code, vedere la documentazione di VS Code.

Per modificare il nome host e/o il numero di porta, usare la args matrice del launch.json file. Nell'esempio seguente viene illustrato come configurare il nome host in localhost e il numero di porta in 5001. Si noti che, se si modifica il nome host o il numero di porta, è necessario aggiornare l'URI di reindirizzamento o l'applicazione. Per altre informazioni, vedere il passaggio Registrare un'applicazione 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
        }
    ]
}