pythonazureazure-web-app-servicebotframeworkazure-bot-service

Bot Framework SDK Python App Fails with UserAssignedMSI but Works with MultiTenant on Azure


I have a bot application built with the Bot Framework SDK in Python that works perfectly with MicrosoftAppType: MultiTenant. However, when I try to configure it to use MicrosoftAppType: UserAssignedMSI, the bot fails to connect when testing via the Azure Bot Service's "Test in Web Chat" feature.

Setup Details: My code is deployed on an Azure Web App. I'm using Azure Bot Service to connect to the bot. The setup works fine with MicrosoftAppType: MultiTenant. I cannot use MultiTenant or SingleTenant as options for MicrosoftAppType due to specific requirements. I'm encountering issues specifically with MicrosoftAppType: UserAssignedMSI. I have already added the User Identity in the Azure Web App's Identity section when configuring UserAssignedMSI. I've included my code snippets below for app.py and config.py. I'm looking for help to understand why this issue occurs with UserAssignedMSI and how to fix it. Any suggestions for alternative solutions are also welcome.

app.py


# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

import sys
import traceback
from datetime import datetime
from http import HTTPStatus

from aiohttp import web
from aiohttp.web import Request, Response, json_response
from botbuilder.core import (
    TurnContext,
)
from botbuilder.core.integration import aiohttp_error_middleware
from botbuilder.integration.aiohttp import CloudAdapter, ConfigurationBotFrameworkAuthentication
from botbuilder.schema import Activity, ActivityTypes

from bots import EchoBot
from config import DefaultConfig

CONFIG = DefaultConfig()

# Create adapter.
# See https://aka.ms/about-bot-adapter to learn more about how bots work.
ADAPTER = CloudAdapter(ConfigurationBotFrameworkAuthentication(CONFIG))


# Catch-all for errors.
async def on_error(context: TurnContext, error: Exception):
    # This check writes out errors to console log .vs. app insights.
    # NOTE: In production environment, you should consider logging this to Azure
    #       application insights.
    print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr)
    traceback.print_exc()

    # Send a message to the user
    await context.send_activity("The bot encountered an error or bug.")
    await context.send_activity(
        "To continue to run this bot, please fix the bot source code."
    )
    # Send a trace activity if we're talking to the Bot Framework Emulator
    if context.activity.channel_id == "emulator":
        # Create a trace activity that contains the error object
        trace_activity = Activity(
            label="TurnError",
            name="on_turn_error Trace",
            timestamp=datetime.utcnow(),
            type=ActivityTypes.trace,
            value=f"{error}",
            value_type="https://www.botframework.com/schemas/error",
        )
        # Send a trace activity, which will be displayed in Bot Framework Emulator
        await context.send_activity(trace_activity)


ADAPTER.on_turn_error = on_error

# Create the Bot
BOT = EchoBot()


# Listen for incoming requests on /api/messages
async def messages(req: Request) -> Response:
    return await ADAPTER.process(req, BOT)


APP = web.Application(middlewares=[aiohttp_error_middleware])
APP.router.add_post("/api/messages", messages)

if __name__ == "__main__":
    try:
        web.run_app(APP, host="localhost", port=CONFIG.PORT)
    except Exception as error:
        raise error

config.py

import os

""" Bot Configuration """


class DefaultConfig:
    """ Bot Configuration """

    PORT = 3978
    APP_ID = os.environ.get("MicrosoftAppId", "MyMicrosoftAppId")
    APP_PASSWORD = os.environ.get("MicrosoftAppPassword", "")
    APP_TYPE = os.environ.get("MicrosoftAppType", "UserAssignedMSI")
    APP_TENANTID = os.environ.get("MicrosoftAppTenantId", "MyMicrosoftAppTenantId")

Configured MultiTenant Setup:

I initially configured the bot with MicrosoftAppType: MultiTenant, and it worked perfectly when tested in the Azure Bot Service's "Test in Web Chat" feature. The bot responded as expected, indicating a successful connection and authentication. Switched to UserAssignedMSI:

I changed the configuration to use MicrosoftAppType: UserAssignedMSI to meet specific requirements. I added the User Identity to the Azure Web App's Identity section and verified that the UserAssignedMSI is correctly set up in Azure. Expected Outcome:

I expected the bot to connect and authenticate properly using the UserAssignedMSI, similar to how it worked with MultiTenant, and respond correctly when tested in the "Test in Web Chat" feature. Actual Outcome:

The bot fails to connect or authenticate when using UserAssignedMSI, and I'm unable to see any detailed error messages that would help diagnose the issue. I'm unsure whether I missed some configuration steps specific to UserAssignedMSI or if there are additional permissions required. Any help to resolve this issue or alternative suggestions for using UserAssignedMSI would be highly appreciated.


Solution

  • Enclose below code with init_func() in app.py:

    APP = web.Application(middlewares=[aiohttp_error_middleware])
    APP.router.add_post("/api/messages", messages)
    
    if __name__ == "__main__":
        try:
            web.run_app(APP, host="localhost", port=CONFIG.PORT)
        except Exception as error:
            raise error
    

    Modified app.py:

    CONFIG = DefaultConfig()
    
    SETTINGS = BotFrameworkAdapterSettings(CONFIG.APP_ID, CONFIG.APP_PASSWORD)
    ADAPTER = BotFrameworkAdapter(SETTINGS)
    
    async def on_error(context: TurnContext, error: Exception):
        print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr)
        traceback.print_exc()
    
        # Send a message to the user
        await context.send_activity("The bot encountered an error or bug.")
        await context.send_activity(
            "To continue to run this bot, please fix the bot source code."
        )
        # Send a trace activity if we're talking to the Bot Framework Emulator
        if context.activity.channel_id == "emulator":
            # Create a trace activity that contains the error object
            trace_activity = Activity(
                label="TurnError",
                name="on_turn_error Trace",
                timestamp=datetime.utcnow(),
                type=ActivityTypes.trace,
                value=f"{error}",
                value_type="https://www.botframework.com/schemas/error",
            )
            await context.send_activity(trace_activity)
    
    
    ADAPTER.on_turn_error = on_error
    
    BOT = MyBot()
    
    async def messages(req: Request) -> Response:
        # Main bot message handler.
        if "application/json" in req.headers["Content-Type"]:
            body = await req.json()
        else:
            return Response(status=415)
    
        activity = Activity().deserialize(body)
        auth_header = req.headers["Authorization"] if "Authorization" in req.headers else ""
    
        response = await ADAPTER.process_activity(activity, auth_header, BOT.on_turn)
        if response:
            return json_response(data=response.body, status=response.status)
        return Response(status=201)
    
    
    def init_func(argv):
        APP = web.Application(middlewares=[aiohttp_error_middleware])
        APP.router.add_post("/api/messages", messages)
        return APP
    if __name__ == "__main__":
        APP = init_func(None)
    
        try:
            web.run_app(APP, host="0.0.0.0", port=CONFIG.PORT)
        except Exception as error:
            raise error
    

    Refer MSDOC to deploy Python SDK echo bot to Azure Bot with User Assigned Managed Identity:

    Create a Python Azure Bot with Managed Identity enabled and an App Service.

    Configure the messaging endpoint in Azure Bot by adding the App Service endpoint in Azure Bot=>Configuration=>Message Endpoint(https://<app_name>.azurewebsites.net/api/messages):

    enter image description here

    Link the Azure Bot with App Service:

    Go to App Service=>Identity=>User Assigned=>Add=>Select your Bot User assigned managed Identity:

    enter image description here

    enter image description here

    config.py:

    
    class DefaultConfig:
        """ Bot Configuration """
    
        PORT = 3978
        APP_ID = os.environ.get("MicrosoftAppId", "MicrosoftApp_Id")
        APP_PASSWORD = os.environ.get("MicrosoftAppPassword", "")
        APP_TYPE = os.environ.get("MicrosoftAppType", "MicrosoftApp_Type")
        APP_TENANTID = os.environ.get("MicrosoftAppTenantId", "MicrosoftApp_TenantId")
    

    Deploy the bot to Azure App Service and add the Startup command (given below) in theAzure App Service=>Configuration=>General settings:

    python3 -m aiohttp.web -H 0.0.0.0 -P 8000 app:init_func
    

    Able to Test Web Chat in Azure Bot:

    enter image description here

    Check the App Service=>Monitoring=>Log Stream to identify the error logs that are preventing the Bot from responding properly.