I am developing a Teams bot using FastAPI and the botbuilder-core library in Python. I want to restrict the bot to be used only within my company and keep it as a single tenant application for confidentiality reasons. However, I am encountering an issue where the bot tries to send a message but fails with an error related to the access_token.
Here's the relevant code snippet:
CONFIG = DefaultConfig()
# BotFramework Adapter setup
ADAPTER = CloudAdapter(ConfigurationBotFrameworkAuthentication(CONFIG))
# Teams Bot
BOT = TeamsBot()
@app.post("/api/messages")
@app.options("/api/messages")
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_code=HTTPStatus.UNSUPPORTED_MEDIA_TYPE)
activity = Activity().deserialize(body)
if not isinstance(activity, Activity):
print(f"Error: Expected Activity, got {type(activity)} instead.")
return Response(status_code=400)
auth_header = req.headers["Authorization"] if "Authorization" in req.headers else ""
response = await ADAPTER.process_activity(auth_header, activity, BOT.on_turn)
if response:
return json_response(data=response.body, status=response.status)
return Response(status_code=201)
Traceback:
Traceback (most recent call last):
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\uvicorn\protocols\http\h11_impl.py", line 408, in run_asgi
result = await app( # type: ignore[func-returns-value]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\uvicorn\middleware\proxy_headers.py", line 84, in __call__
return await self.app(scope, receive, send)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\fastapi\applications.py", line 292, in __call__
await super().__call__(scope, receive, send)
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\starlette\applications.py", line 122, in __call__
await self.middleware_stack(scope, receive, send)
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\starlette\middleware\errors.py", line 184, in __call__
raise exc
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\starlette\middleware\errors.py", line 162, in __call__
await self.app(scope, receive, _send)
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\starlette\middleware\sessions.py", line 86, in __call__
await self.app(scope, receive, send_wrapper)
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\starlette\middleware\exceptions.py", line 79, in __call__
raise exc
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\starlette\middleware\exceptions.py", line 68, in __call__
await self.app(scope, receive, sender)
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\fastapi\middleware\asyncexitstack.py", line 20, in __call__
raise e
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\fastapi\middleware\asyncexitstack.py", line 17, in __call__
await self.app(scope, receive, send)
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\starlette\routing.py", line 718, in __call__
await route.handle(scope, receive, send)
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\starlette\routing.py", line 276, in handle
await self.app(scope, receive, send)
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\starlette\routing.py", line 66, in app
response = await func(request)
^^^^^^^^^^^^^^^^^^^
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\fastapi\routing.py", line 273, in app
raw_response = await run_endpoint_function(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\fastapi\routing.py", line 190, in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject\_api\fast_api.py", line 113, in messages
return Response(status_code=201)
^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\botbuilder\core\cloud_adapter_base.py", line 364, in process_activity
await self.run_pipeline(context, logic)
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\botbuilder\core\bot_adapter.py", line 181, in run_pipeline
raise error
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\botbuilder\core\bot_adapter.py", line 174, in run_pipeline
return await self._middleware.receive_activity_with_status(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\botbuilder\core\middleware_set.py", line 69, in receive_activity_with_status
return await self.receive_activity_internal(context, callback)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\botbuilder\core\middleware_set.py", line 79, in receive_activity_internal
return await callback(context)
^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject\services\ProjectName\teams_bot\bot.py", line 69, in on_turn
await super().on_turn(turn_context)
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\botbuilder\core\activity_handler.py", line 70, in on_turn
await self.on_message_activity(turn_context)
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject\services\ProjectName\teams_bot\bot.py", line 27, in on_message_activity
await self.get_template_quote(turn_context)
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject\services\ProjectName\teams_bot\bot.py", line 223, in get_template_quote
await self._send_file_card(turn_context, filename, file_size)
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject\services\ProjectName\teams_bot\bot.py", line 250, in _send_file_card
await turn_context.send_activity(reply_activity)
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\botbuilder\core\turn_context.py", line 173, in send_activity
result = await self.send_activities([activity_or_text])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\botbuilder\core\turn_context.py", line 225, in send_activities
return await self._emit(self._on_send_activities, output, logic())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\botbuilder\core\turn_context.py", line 303, in _emit
return await logic
^^^^^^^^^^^
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\botbuilder\core\turn_context.py", line 220, in logic
responses = await self.adapter.send_activities(self, output)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\botbuilder\core\cloud_adapter_base.py", line 93, in send_activities
response = await connector_client.conversations.reply_to_activity(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\botframework\connector\aio\operations_async\_conversations_operations_async.py", line 523, in reply_to_activity
response = await self._client.async_send(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\msrest\async_client.py", line 115, in async_send
pipeline_response = await self.config.pipeline.run(request, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\msrest\pipeline\async_abc.py", line 159, in run
return await first_node.send(pipeline_request, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\msrest\pipeline\async_abc.py", line 79, in send
response = await self.next.send(request, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\msrest\pipeline\async_requests.py", line 99, in send
self._creds.signed_session(session)
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\botframework\connector\auth\app_credentials.py", line 98, in signed_session
auth_token = self.get_access_token()
^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\X(Aspe\PycharmProjects\TeamsBotProject_Main\venv\Lib\site-packages\botframework\connector\auth\microsoft_app_credentials.py", line 65, in get_access_token
return auth_token["access_token"]
~~~~~~~~~~^^^^^^^^^^^^^^^^
KeyError: 'access_token'
Error:
{'error': 'unauthorized_client', 'error_description': "AADSTS700016: Application with identifier 'AppID' was not found in the directory 'Bot Framework'. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You may have sent your authentication request to the wrong tenant. Trace ID: 00 Correlation ID: 00 Timestamp: 2024-03-25 20:35:48Z", 'error_codes': [700016], 'timestamp': '2024-03-25 20:35:48Z', 'trace_id': '00', 'correlation_id': '00', 'error_uri': 'https://login.microsoftonline.com/error?code=700016'}
In the code above, I am passing only the app ID and app secret to the ConfigurationBotFrameworkAuthentication instance. I have noticed that the error does not occur when I switch to multi-tenant mode, but I want to keep the bot as a single tenant application.
I have a feeling that I might need to pass additional information to make proper verification or change some settings in Azure. In my project, under the "Authentication" tab, I have selected "Single Tenant,"
but in the bot's "Configuration" tab, it shows "MultiTenant." I'm not sure if this discrepancy is causing the problem.
I am running the bot using FastAPI, and the /api/messages endpoint is responsible for handling the Teams messages. My goal is to allow users within my company to freely add this bot to their collection and use it.
I have observed that when I send a message, it doesn't give me an error about the access_token, but it shows that the bot is trying to send a message and fails.
I would appreciate any guidance on how to properly configure the authentication for a single tenant Teams bot using FastAPI and the botbuilder-core library. Do I need to pass additional information or modify any settings in Azure to resolve this issue?


As the comments above have stated, although it's non-intuitive, you need to enable Multi-Tenancy for the app so that the bot will work. However, actual messages FROM the user include a Tenant Id in the payload, so it's possible to filter on that and block anything else. One way to do this is using Middleware, for which there's a sample (in C#, not Python, I'm afraid, but hopefully it can set you on the right starting point): https://github.com/OfficeDev/BotBuilder-MicrosoftTeams-dotnet/blob/master/CSharp/Microsoft.Bot.Builder.Teams/Middlewares/TeamsTenantFilteringMiddleware.cs or you could do it in your handler or even in the bot itself in the OnTurn or OnMessage event handlers.
This definitely provides some measure of protection because only the Bot Framework services have your bot's endpoint address - it's never released to end clients - so if anything reaches your bot, you can filter in the middleware or Handler or similar. However, if you want a higher level of security, so that if a malicious actor somehow found and called your bot's web endpoint it would also get blocked, then you could look to implement SSO, in which case you could ensure you have (a) a valid token and (b) it's 100% from the correct tenant. You can read more about that here: https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/authentication/bot-sso-overview