Dela via


Aktivera autentisering i ditt eget Python-webbprogram med hjälp av Azure Active Directory B2C

Viktigt!

Från och med den 1 maj 2025 är Azure AD B2C inte längre tillgängligt att köpa för nya kunder. Läs mer i våra vanliga frågor och svar.

I den här artikeln får du lära dig hur du lägger till Azure Active Directory B2C-autentisering (Azure AD B2C) i ditt eget Python-webbprogram. Du gör det möjligt för användare att logga in, logga ut, uppdatera profil och återställa lösenord med hjälp av Azure AD B2C-användarflöden. Den här artikeln använder Microsoft Authentication Library (MSAL) för Python för att förenkla tillägg av autentisering i python-webbprogrammet.

Syftet med den här artikeln är att ersätta exempelprogrammet som du använde i Konfigurera autentisering i ett Python-exempelwebbprogram med hjälp av Azure AD B2C med ditt eget Python-program.

Den här artikeln använder Python 3.9+ och Flask 2.1 för att skapa en grundläggande webbapp. Programmets vyer använder Jinja2-mallar.

Förutsättningar

Steg 1: Skapa Python-projektet

  1. I filsystemet skapar du en projektmapp för den här självstudien, till exempel my-python-web-app.

  2. I terminalen ändrar du katalogen till python-appmappen, till exempel cd my-python-web-app.

  3. Kör följande kommando för att skapa och aktivera en virtuell miljö med namnet .venv baserat på den aktuella tolken.

    sudo apt-get install python3-venv  # If needed
    python3 -m venv .venv
    source .venv/bin/activate
    
  4. Uppdatera pip i den virtuella miljön genom att köra följande kommando i terminalen:

    python -m pip install --upgrade pip
    
  5. Om du vill aktivera Flask-felsökningsfunktionerna växlar du Flask till utvecklingsmiljö development läge. Mer information om felsökning av Flask-appar finns i Flask-dokumentationen.

    export FLASK_ENV=development
    
  6. Öppna projektmappen i VS Code genom att köra code . kommandot eller genom att öppna VS Code och välja mappen Öppna fil>.

Steg 2: Installera beroenden för appen

Under rotmappen för webbappen skapar du requirements.txt filen. Kravfilen visar de paket som ska installeras med pip-installationen. Lägg till följande innehåll i filen requirements.txt:

Flask>=2
werkzeug>=2

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

Installera beroendena i terminalen genom att köra följande kommandon:

python -m pip install -r requirements.txt

Steg 3: Skapa appgränssnittskomponenter

Flask är ett enkelt Python-ramverk för webbprogram som tillhandahåller grunderna för URL-routning och sidåtergivning. Den använder Jinja2 som mallmotor för att återge innehållet i din app. Mer information finns i malldesignerdokumentationen. I det här avsnittet lägger du till de mallar som krävs som tillhandahåller de grundläggande funktionerna i webbappen.

Steg 3.1 Skapa en basmall

En bassidemall i Flask innehåller alla delade delar av en uppsättning sidor, inklusive referenser till CSS-filer, skriptfiler och så vidare. Basmallar definierar också en eller flera blocktaggar som andra mallar som utökar basen förväntas åsidosätta. En blocktagg avgränsas av {% block <name> %} och {% endblock %} i både basmallen och den utökade mallen.

Skapa mappen i webbappens templates rotmapp. I mappen templates skapar du en fil med namnet base.htmloch lägger sedan till innehållet nedan:

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

Steg 3.2 Skapa webbappmallarna

Lägg till följande mallar under mappen mallar. Mallarna utökar mallen base.html :

  • index.html: webbappens startsida. Mallarna använder följande logik: om en användare inte loggar in återges inloggningsknappen. Om en användare loggar in återger den åtkomsttokenens anspråk, och länk för att redigera profilen och anropar ett 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: Visar hur du anropar ett 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: Hanterar autentiseringsfel.

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

Steg 4: Konfigurera webbappen

I rotmappen för webbappen skapar du en fil med namnet app_config.py. Den här filen innehåller information om din Azure AD B2C-identitetsprovider. Webbappen använder den här informationen för att upprätta en förtroenderelation med Azure AD B2C, logga in och ut användare, hämta token och verifiera dem. Lägg till följande innehåll i filen:

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

Uppdatera koden ovan med dina Azure AD B2C-miljöinställningar enligt beskrivningen i avsnittet Konfigurera exempelwebbappeni artikeln Konfigurera autentisering i en Python-exempelwebbapp .

Steg 5: Lägg till webbappkoden

I det här avsnittet lägger du till flaskvyfunktionerna och autentiseringsmetoderna för MSAL-biblioteket. Under rotmappen för projektet lägger du till en fil med namnet app.py med följande kod:

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

Steg 6: Kör din webbapp

I terminalen kör du appen genom att ange följande kommando, som kör Flask-utvecklingsservern. Utvecklingsservern söker app.py som standard. Öppna sedan webbläsaren och gå till webbappens URL: http://localhost:5000.

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

[Valfritt] Felsöka din app

Felsökningsfunktionen ger dig möjlighet att pausa ett program som körs på en viss kodrad. När du pausar programmet kan du undersöka variabler, köra kod i panelen Felsökningskonsol och på annat sätt dra nytta av de funktioner som beskrivs i Felsökning. Om du vill använda Felsökningsprogrammet för Visual Studio Code kan du läsa dokumentationen för VS Code.

Om du vill ändra värdnamnet och/eller portnumret använder du args filens launch.json matris. I följande exempel visas hur du konfigurerar värdnamnet till localhost och portnumret till 5001. Observera att om du ändrar värdnamnet eller portnumret måste du uppdatera omdirigerings-URI:n eller ditt program. Mer information finns i steget Registrera ett webbprogram .

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