Compartir a través de


Habilitación de la autenticación en su propia aplicación web de Python mediante Azure Active Directory B2C

Importante

A partir del 1 de mayo de 2025, Azure AD B2C ya no estará disponible para ser adquirido por nuevos clientes. Obtenga más información en nuestras preguntas más frecuentes.

En este artículo, aprenderá a agregar la autenticación de Azure Active Directory B2C (Azure AD B2C) en su propia aplicación web de Python. Permite que los usuarios inicien sesión, cierren sesión, actualicen el perfil y restablezcan la contraseña mediante flujos de usuario de Azure AD B2C. En este artículo se usa la Biblioteca de autenticación de Microsoft (MSAL) para Python para simplificar la adición de autenticación a la aplicación web de Python.

El objetivo de este artículo es sustituir la aplicación de ejemplo que usó en Configuración de la autenticación en una aplicación web de Python de ejemplo mediante Azure AD B2C con su propia aplicación de Python.

En este artículo se usa Python 3.9+ y Flask 2.1 para crear una aplicación web básica. Las vistas de la aplicación usan plantillas de Jinja2.

Prerrequisitos

Paso 1: Creación del proyecto de Python

  1. En el sistema de archivos, cree una carpeta de proyecto para este tutorial, como my-python-web-app.

  2. En el terminal, cambie el directorio a la carpeta de la aplicación de Python, como cd my-python-web-app.

  3. Ejecute el comando siguiente para crear y activar un entorno virtual denominado .venv en función del intérprete actual.

    sudo apt-get install python3-venv  # If needed
    python3 -m venv .venv
    source .venv/bin/activate
    
  4. Actualice pip en el entorno virtual mediante la ejecución del comando siguiente en el terminal:

    python -m pip install --upgrade pip
    
  5. Para habilitar las características de depuración de Flask, cambie Flask al entorno de desarrollo al modo development. Para más información sobre la depuración de aplicaciones de Flask, consulte la documentación de Flask.

    export FLASK_ENV=development
    
  6. Abra la carpeta del proyecto en VS Code ejecutando el comando code ., o abriendo VS Code y seleccionando Archivo>Abrir carpeta.

Paso 2: Instalación de dependencias de aplicaciones

En la carpeta raíz de la aplicación web, cree el requirements.txt archivo. El archivo de requisitos enumera los paquetes que se van a instalar mediante pip install. Agregue el siguiente contenido al archivo requirements.txt:

Flask>=2
werkzeug>=2

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

En el terminal, instale las dependencias mediante la ejecución de los siguientes comandos:

python -m pip install -r requirements.txt

Paso 3: Compilación de componentes de la interfaz de usuario de la aplicación

Flask es un marco ligero de Python para aplicaciones web que proporciona los conceptos básicos para el enrutamiento de direcciones URL y la representación de páginas. Usa Jinja2 como motor de plantilla para representar el contenido de la aplicación. Para obtener más información, consulte la documentación del diseñador de plantillas. En esta sección, agregará las plantillas necesarias que proporcionan la funcionalidad básica de la aplicación web.

Paso 3.1 Crear una plantilla base

Una plantilla de página base de Flask contiene todas las partes compartidas de un conjunto de páginas, incluidas las referencias a archivos CSS, archivos de script, etc. Las plantillas base también definen una o más etiquetas de bloque que se espera que invaliden otras plantillas que extienden la base. Una etiqueta de bloque se delimita mediante {% block <name> %} y {% endblock %} en la plantilla base y en la plantilla extendida.

En la carpeta raíz de la aplicación web, cree la templates carpeta . En la carpeta templates, cree un archivo denominado base.htmly agregue el contenido siguiente:

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

Paso 3.2 Crear las plantillas de aplicación web

Agregue las siguientes plantillas en la carpeta templates. Estas plantillas amplían la base.html plantilla:

  • index.html: la página principal de la aplicación web. Las plantillas usan la siguiente lógica: si un usuario no inicia sesión, representa el botón de inicio de sesión. Si un usuario inicia sesión, representa las declaraciones del token de acceso, proporciona un enlace para editar el perfil y llama a la API de 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: muestra cómo llamar a una 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: controla los errores de autenticación.

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

Paso 4: Configurar la aplicación web

En la carpeta raíz de la aplicación web, cree un archivo denominado app_config.py. Este archivo contiene información sobre el proveedor de identidades de Azure AD B2C. La aplicación web usa esta información para establecer una relación de confianza con Azure AD B2C, iniciar y cerrar la sesión de los usuarios, adquirir tokens y validarlos. Agregue el siguiente contenido al archivo:

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

Actualice el código anterior con la configuración del entorno de Azure AD B2C, tal como se explica en la sección Configuración de la aplicación web de ejemplo del artículo Configuración de la autenticación en una aplicación web de Python de ejemplo .

Paso 5: Agregar el código de la aplicación web

En esta sección, agregará las funciones de vista de Flask y los métodos de autenticación de la biblioteca MSAL. En la carpeta raíz del proyecto, agregue un archivo denominado app.py con el código siguiente:

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

Paso 6: Ejecución de la aplicación web

En el Terminal, ejecute la aplicación escribiendo el siguiente comando, que ejecuta el servidor de desarrollo de Flask. El servidor de desarrollo busca app.py de forma predeterminada. A continuación, abra el explorador y vaya a la dirección URL de la aplicación web: http://localhost:5000.

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

[Opcional] Depura tu aplicación

La característica de depuración le ofrece la oportunidad de pausar un programa en ejecución en una línea de código determinada. Al pausar el programa, puede examinar variables, ejecutar código en el panel Consola de depuración y, de lo contrario, aprovechar las características descritas en Depuración. Para usar el depurador de Visual Studio Code, consulte la documentación de VS Code.

Para cambiar el nombre de host o el número de puerto, use la args matriz del launch.json archivo. En el ejemplo siguiente se muestra cómo configurar el nombre de host en localhost y el número de puerto en 5001. Tenga en cuenta que, si cambia el nombre de host o el número de puerto, debe actualizar el URI de redireccionamiento o la aplicación. Para obtener más información, consulte el paso Registrar una aplicación 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
        }
    ]
}