Compartilhar via


Habilitar a autenticação em seu próprio aplicativo Web Python usando o Azure Active Directory B2C

Importante

A partir de 1º de maio de 2025, o Azure AD B2C não estará mais disponível para compra para novos clientes. Saiba mais em nossas perguntas frequentes.

Neste artigo, você aprenderá a adicionar a autenticação do Azure AD B2C (Azure Active Directory B2C) em seu próprio aplicativo Web Python. Você permite que os usuários entrem, saiam, atualizem o perfil e redefinam a senha usando fluxos de usuário do Azure AD B2C. Este artigo usa a MSAL (Biblioteca de Autenticação da Microsoft) para Python para simplificar a adição de autenticação ao seu aplicativo Web Python.

O objetivo deste artigo é substituir o aplicativo de exemplo usado em Configurar autenticação em um aplicativo Web Python de exemplo usando o Azure AD B2C por seu próprio aplicativo Python.

Este artigo usa Python 3.9+ e Flask 2.1 para criar um aplicativo Web básico. As exibições do aplicativo usam modelos do Jinja2.

Pré-requisitos

Etapa 1: Criar o projeto do Python

  1. No sistema de arquivos, crie uma pasta de projeto para este tutorial, como my-python-web-app.

  2. No terminal, altere o diretório para a pasta do aplicativo Python, como cd my-python-web-app.

  3. Execute o comando a seguir para criar e ativar um ambiente virtual nomeado .venv com base no interpretador atual.

    sudo apt-get install python3-venv  # If needed
    python3 -m venv .venv
    source .venv/bin/activate
    
  4. Atualize o pip no ambiente virtual executando o seguinte comando no terminal:

    python -m pip install --upgrade pip
    
  5. Para habilitar os recursos de depuração do Flask, alterne o Flask para o ambiente de desenvolvimento para o modo development. Para obter mais informações sobre a depuração de aplicativos do Flask, confira a Documentação do Flask.

    export FLASK_ENV=development
    
  6. Abra a pasta do projeto no VS Code executando o code . comando ou abrindo o VS Code e selecionando Arquivo>Abrir Pasta.

Etapa 2: Instalar dependências do aplicativo

Na pasta raiz do aplicativo Web, crie o requirements.txt arquivo. O arquivo de requisitos lista os pacotes a serem instalados usando a instalação do pip. Adicione o seguinte conteúdo ao arquivo requirements.txt:

Flask>=2
werkzeug>=2

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

No terminal, instale as dependências executando os seguintes comandos:

python -m pip install -r requirements.txt

Etapa 3: Criar componentes da interface do usuário do aplicativo

O Flask é uma estrutura python leve para aplicativos Web que fornece as noções básicas para roteamento de URL e renderização de página. Ele usa Jinja2 como seu mecanismo de modelo para renderizar o conteúdo do seu aplicativo. Para obter mais informações, confira a documentação do designer de modelo. Nesta seção, você adicionará os modelos necessários que fornecem a funcionalidade básica do seu aplicativo Web.

Etapa 3.1 Criar um modelo base

Um modelo de página base no Flask contém todas as partes compartilhadas de um conjunto de páginas, incluindo referências a arquivos CSS, arquivos de script e assim por diante. Os modelos de base também definem uma ou mais marcas de bloco que outros modelos que estendem a base devem substituir. Uma marca de bloco é delineada por {% block <name> %} e {% endblock %} no modelo base e no modelo estendido.

Na pasta raiz do aplicativo Web, crie a templates pasta. Na pasta de modelos, crie um arquivo chamado base.htmle adicione o conteúdo abaixo:

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

Etapa 3.2 Criar os modelos de aplicativo Web

Adicione os modelos a seguir na pasta de modelos. Esses modelos estendem o base.html modelo:

  • index.html: a home page do aplicativo Web. Os modelos usam a seguinte lógica: se um usuário não entrar, ele renderiza o botão de entrada. Se um usuário fizer login, ele exibirá as declarações do token de acesso, oferecerá um link para editar o perfil e chamará uma API do 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: demonstra como chamar uma 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: manipula erros de autenticação.

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

Etapa 4: Configurar seu aplicativo Web

Na pasta raiz do aplicativo Web, crie um arquivo chamado app_config.py. Este arquivo contém informações sobre o provedor de identidade do Azure AD B2C. O aplicativo Web usa essas informações para estabelecer uma relação de confiança com o Azure Active Directory B2C, conectar e desconectar usuários, adquirir tokens e validá-los. Adicione o seguinte conteúdo ao arquivo:

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

Atualize o código acima com as configurações de ambiente do Azure AD B2C, conforme explicado na seção Configurar o aplicativo Web de exemplo do artigo Configurar autenticação em um aplicativo Web Python de exemplo.

Etapa 5: Adicionar o código do aplicativo Web

Nesta seção, você adicionará as funções de exibição do Flask e os métodos de autenticação da biblioteca MSAL. Na pasta raiz do projeto, adicione um arquivo nomeado app.py com o seguinte código:

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

Etapa 6: Executar seu aplicativo Web

No Terminal, execute o aplicativo inserindo o comando a seguir, que executa o servidor de desenvolvimento do Flask. O servidor de desenvolvimento procura app.py por padrão. Em seguida, abra o navegador e navegue até a URL do aplicativo Web: http://localhost:5000.

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

[Opcional] Depurar seu aplicativo

O recurso de depuração oferece a oportunidade de pausar um programa em execução em uma linha de código específica. Ao pausar o programa, você pode examinar variáveis, executar código no painel console de depuração e, caso contrário, aproveitar os recursos descritos na Depuração. Para usar o depurador do Visual Studio Code, confira a documentação do VS Code.

Para alterar o nome do host e/ou o número da porta, use a args matriz do launch.json arquivo. O exemplo a seguir demonstra como configurar o nome localhost do host e o número da porta como 5001. Observe que, se você alterar o nome do host ou o número da porta, deverá atualizar o URI de redirecionamento ou seu aplicativo. Para obter mais informações, confira a etapa Registrar um aplicativo 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
        }
    ]
}