OfficeDev / TeamsFx

Developer tools for building Teams apps
Other
427 stars 164 forks source link

Single Tenant in nodejs authentication error for teams bot #10999

Closed xianlin closed 1 month ago

xianlin commented 2 months ago

Describe the bug I have a teams bot with Multi tenant enabled in app registration and it works fine. However when I switched the app registration from multi tenant to single Tenant, it produced 401 Authentication failure error.

Of course I have edited my nodejs code to accommodate the single tenant by adding the MicrosoftAppTenatID of my azure app registration and MicrosoftAppType to Single tenant.

Did I miss some steps or is this a bug? Does anyone have the successful testing on the single tenant? Thanks.

To Reproduce

  1. Use VScode and install Teams Toolkit V.5.4.1
  2. Create "Notification Bot - Javascript with Restify as HTTP server"
  3. Run provision to provison Azure resource
  4. Use the sample code provided by the Teams Toolkit to test sending messages
  5. Deploy the app to app service with Node 18
  6. Everything works under default "MultiTenant" setup
  7. Change the App Registration Application Authentication to "SingleTenant"
  8. Update the sample code as the below (ensure the environment variables are loaded correctly of course)

update src/internal/initialzie.js

// Create bot.
const notificationApp = new ConversationBot({
  // The bot id and password to create CloudAdapter.
  // See https://aka.ms/about-bot-adapter to learn more about adapters.
  adapterConfig: {
    MicrosoftAppId: config.botId,
    MicrosoftAppPassword: config.botPassword,
    // MicrosoftAppType: "MultiTenant",
    MicrosoftAppType: "SingleTenant",
    MicrosoftAppTenantId: config.tenantId,  # loaded correctly
  },
...

});

package.json

  "dependencies": {
        "@microsoft/adaptivecards-tools": "^1.0.0",
        "@microsoft/teamsfx": "^2.3.0",
        "botbuilder": "^4.20.0",
        "restify": "^10.0.0"
    },

Expected behavior Should receive the test message but received 401 Authentication Failure

Reference Bot Framework Single Tenant V.S. Multi Tenant Discussion

yukun-dong commented 2 months ago

@xianlin Your steps look correct. Did your m365 account and Azure account belong to the same tenant?

xianlin commented 2 months ago

Yes both my m365 account and Azure account are under the same tenant ID

yukun-dong commented 2 months ago

Hi @xianlin,

I can make the bot work using your steps above. Could you please double check MicrosoftAppTenantId: config.tenantId is correctly loaded? and your account which installed this app also belongs to your tenant?

xianlin commented 2 months ago

Sure I'll check it again. Thanks

xianlin commented 1 month ago

@yukun-dong , I tried again but still encountered 401 error when tried run "curl -X POST https://my_bot_web_app/api/notification". My account installed with this bot web app belongs to the correct tenant and it is successfully loaded to the Nodejs code.

Here is a snippet of my 401 error:

{ "body": { "type": "message", "serviceUrl": "https://smba.trafficmanager.net/emea/", "channelId": "msteams", "from": { "id": "28:4f026db4-208d-46e6-bf70-MASKED_FOR_PRIVACY", "name": "MY_BOT_TEST" }, "conversation": { "conversationType": "personal", "id": "a:116XtG9a4x2E4TUdg8fRrGS-bYPJeDZg8PzJDyxIZSgVTKfQzZeRK7BsHw71A6_Wl9HAvYfAuMpopAtX97iFpR54Tb6aCfJA0s-SfNitEtNr90-MASKED_FOR_PRIVACY", "tenantId": "01c999f0-c6f3-47dc-92cf-MASKED_FOR_PRIVACY" }, "recipient": { "id": "29:1ztu73dhl17_ifUahz1jrajozfJk1yGi4RTEP3MyyldVKvMs6X9B0nurhtl7Zph6U2V2h9hUi0fYwdF50Hi23lw", "aadObjectId": "3fd84a66-b697-4f72-8554-c2c2fe0777ad" (I couldn't find this aad Object ID in my tenant) }, "locale": "en-GB", "text": "The bot encountered unhandled error: Authorization has been denied for this request.", "inputHint": "acceptingInput", "replyToId": "7aba3fd3-7a4d-422b-..." } }

I confirmed that the App ID "4f026db4-208d-46e6-bf70-MASKED_FOR_PRIVACY" belongs to Tenant ID "01c999f0-c6f3-47dc-92cf-MASKED_FOR_PRIVACY".

And if I changed NodeJS code to "MultiTenant" and run, it will report error because my App registration is still using "Single Tenant"

{"code":"Internal","message":"ServerError: unauthorized_client: 700016 - [2024-03-19 04:33:34Z]: AADSTS700016: Application with identifier '4f026db4-208d-46e6-bf70-MASKED_FOR_PRIVACY' 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: a40c3fb2-4eec-4b80-b9aa-95bec5024d00 Correlation ID: 541c6fc5-67d0-443c-b3b8-dd32f1d85fcf Timestamp: 2024-03-19 04:33:34Z - Correlation ID: 541c6fc5-67d0-443c-b3b8-dd32f1d85fcf - Trace ID: a40c3fb2-4eec-4b80-b9aa-95bec5024d00"}
yukun-dong commented 1 month ago

Hi @xianlin After you switch the App Registration tenant type, could you please try clear the notification storage and try if it works?

xianlin commented 1 month ago

Hi @xianlin After you switch the App Registration tenant type, could you please try clear the notification storage and try if it works?

you mean delete the Azure Blog Storage container and start over again? sure I will try it later. thank you.

xianlin commented 1 month ago

I deleted the blob from the Azure Storage container "my-bot-test" and remove/re-install the Bot Zip package again in teams, the new blob get created in the Azure storage and I tried to run "curl -X POST https://my_bot_app/api/notification" and here is the nodejs error with verbosed output:

azure:core-http:info ServiceClient: creating signing policy from provided credentials
azure:core-http:info ServiceClient: using default request policies
azure:core-http:info ServiceClient: creating signing policy from provided credentials
azure:core-http:info ServiceClient: using default request policies
azure:core-http:info Request: {
  "streamResponseStatusCodes": {},
  "url": "https://smba.trafficmanager.net/emea/v3/conversations/a%3A116XtG9a4x2E4TUdg8fRrGS-bYPJeDZg8PzJDyxIZSgVTKfQzZeRK7BsHw71A6_Wl9HAvYfAuMpopAtX97iFpR54Tb6aCfJA0s-SfNitEtNr90-lGK5jSphLnmkMahYJd/pagedmembers?pageSize=REDACTED",
  "method": "GET",
  "headers": {
    "_headersMap": {
      "accept": "*/*",
      "user-agent": "Microsoft-BotFramework/3.1 botframework-connector/4.22.1 core-http/3.0.4 Node/v18.19.1 OS/(x64-Linux-6.5.0-26-generic)",
      "authorization": "REDACTED"
    }
  },
  "withCredentials": false,
  "timeout": 0,
  "requestId": "19956cef-a95f-4d8d-91a2-e0c43204dd54"
}
azure:core-http:info Response status code: 401
azure:core-http:info Headers: {
  "_headersMap": {
    "connection": "close",
    "content-length": "61",
    "content-type": "application/json; charset=utf-8",
    "date": "Tue, 19 Mar 2024 08:20:36 GMT",
    "ms-cv": "/eSzLdL8GkqBNGUlB+xoOQ.0",
    "server": "Microsoft-HTTPAPI/2.0"
  }
}
azure:core-http:info ServiceClient: creating signing policy from provided credentials
azure:core-http:info ServiceClient: using default request policies
azure:core-http:info ServiceClient: creating signing policy from provided credentials
azure:core-http:info ServiceClient: using default request policies
azure:core-http:info Request: {
  "streamResponseStatusCodes": {},
  "url": "https://smba.trafficmanager.net/emea/v3/conversations/a%3A116XtG9a4x2E4TUdg8fRrGS-bYPJeDZg8PzJDyxIZSgVTKfQzZeRK7BsHw71A6_Wl9HAvYfAuMpopAtX97iFpR54Tb6aCfJA0s-SfNitEtNr90-lGK5jSphLnmkMahYJd/activities/22cf293d-d1a4-4da3-9964-188cb33b9946",
  "method": "POST",
  "headers": {
    "_headersMap": {
      "content-type": "application/json; charset=utf-8",
      "x-ms-conversation-id": "REDACTED",
      "accept": "*/*",
      "user-agent": "Microsoft-BotFramework/3.1 botframework-connector/4.22.1 core-http/3.0.4 Node/v18.19.1 OS/(x64-Linux-6.5.0-26-generic)",
      "authorization": "REDACTED"
    }
  },
  "withCredentials": false,
  "timeout": 0,
  "requestId": "9b0bd12b-2aab-41d3-a313-28fd4e2d2239"
}
azure:core-http:info Response status code: 401
azure:core-http:info Headers: {
  "_headersMap": {
    "connection": "close",
    "content-length": "61",
    "content-type": "application/json; charset=utf-8",
    "date": "Tue, 19 Mar 2024 08:20:37 GMT",
    "ms-cv": "dnt7IZHiOE6W6qO/gLSMFw.0",
    "server": "Microsoft-HTTPAPI/2.0"
  }
}
[onTurnError] unhandled error RestError: Authorization has been denied for this request. 
 {
  "name": "RestError",
  "statusCode": 401,
  "request": {
    "streamResponseStatusCodes": {},
    "url": "https://smba.trafficmanager.net/emea/v3/conversations/a%3A116XtG9a4x2E4TUdg8fRrGS-bYPJeDZg8PzJDyxIZSgVTKfQzZeRK7BsHw71A6_Wl9HAvYfAuMpopAtX97iFpR54Tb6aCfJA0s-SfNitEtNr90-lGK5jSphLnmkMahYJd/activities/22cf293d-d1a4-4da3-9964-188cb33b9946",
    "method": "POST",
    "headers": {
      "_headersMap": {
        "content-type": "application/json; charset=utf-8",
        "x-ms-conversation-id": "REDACTED",
        "accept": "*/*",
        "user-agent": "Microsoft-BotFramework/3.1 botframework-connector/4.22.1 core-http/3.0.4 Node/v18.19.1 OS/(x64-Linux-6.5.0-26-generic)",
        "authorization": "REDACTED"
      }
    },
    "withCredentials": false,
    "timeout": 0,
    "requestId": "9b0bd12b-2aab-41d3-a313-28fd4e2d2239"
  },
  "details": {
    "message": "Authorization has been denied for this request."
  },
  "message": "Authorization has been denied for this request."
}
azure:core-http:info Request: {
  "streamResponseStatusCodes": {},
  "url": "https://smba.trafficmanager.net/emea/v3/conversations/a%3A116XtG9a4x2E4TUdg8fRrGS-bYPJeDZg8PzJDyxIZSgVTKfQzZeRK7BsHw71A6_Wl9HAvYfAuMpopAtX97iFpR54Tb6aCfJA0s-SfNitEtNr90-lGK5jSphLnmkMahYJd/activities/22cf293d-d1a4-4da3-9964-188cb33b9946",
  "method": "POST",
  "headers": {
    "_headersMap": {
      "content-type": "application/json; charset=utf-8",
      "x-ms-conversation-id": "REDACTED",
      "accept": "*/*",
      "user-agent": "Microsoft-BotFramework/3.1 botframework-connector/4.22.1 core-http/3.0.4 Node/v18.19.1 OS/(x64-Linux-6.5.0-26-generic)",
      "authorization": "REDACTED"
    }
  },
  "withCredentials": false,
  "timeout": 0,
  "requestId": "5d3396ad-a45d-4342-9b1a-c1da4754ad85"
}
azure:core-http:info Response status code: 401
azure:core-http:info Headers: {
  "_headersMap": {
    "connection": "close",
    "content-length": "61",
    "content-type": "application/json; charset=utf-8",
    "date": "Tue, 19 Mar 2024 08:20:37 GMT",
    "ms-cv": "LArIo5vO2E24kwGtquiavw.0",
    "server": "Microsoft-HTTPAPI/2.0"
  }
}

I still get 401 error and still have the response output from my curl command as the below:

{
  "body": {
    "type": "message",
    "serviceUrl": "https://smba.trafficmanager.net/emea/",
    "channelId": "msteams",
    "from": {
      "id": "28:4f026db4-208d-46e6-bf70-MASK_FOR_PRIVACY",
      "name": "MY_BOT_TEST"
    },
    "conversation": {
      "conversationType": "personal",
      "id": "a:116XtG9a4x2E4TUdg8fRrGS-bYPJeDZg8PzJDyxIZSgVTKfQzZeRK7BsHw71A6_Wl9HAvYfAuMpopAtX97iFpR54Tb6aCfJA0s-SfNitEtNr90-lGK5jSphLnmkMahYJd",
      "tenantId": "01c999f0-c6f3-47dc-92cf-MASK_FOR_PRIVACY"
    },
    "recipient": {
      "id": "29:1ztu73dhl17_ifUahz1jrajozfJk1yGi4RTEP3MyyldVKvMs6X9B0nurhtl7Zph6U2V2h9hUi0fYwdF50Hi23lw",
      "aadObjectId": "3fd84a66-b697-4f72-8554-c2c2fe0777ad"
    },
    "locale": "en-GB",
    "text": "The bot encountered unhandled error: Authorization has been denied for this request.",
    "inputHint": "acceptingInput",
    "replyToId": "22cf293d-d1a4-4da3-9964-188cb33b9946"
  },
  "withCredentials": false
}

I am using ngrok for the testing from my local machine but it should be working as before when I used "MultiTenant".

xianlin commented 1 month ago

my snippet of the API logic:

server.post(
  "/api/notification",
  restify.plugins.queryParser(),
  restify.plugins.bodyParser(), // Add more parsers if needed

  async (req, res) => {
    const pageSize = 100;
    let continuationToken = undefined;
    do {
      const pagedData = await notificationApp.notification.getPagedInstallations(
        pageSize,
        continuationToken
      );
      const installations = pagedData.data;
      continuationToken = pagedData.continuationToken;

      for (const target of installations) {
        await target.sendAdaptiveCard(
          AdaptiveCards.declare(notificationTemplate).render({
            title: "New Event Occurred!",
            appName: "Contoso App Notification",
            description: `This is a sample http-triggered notification to ${target.type}`,
            notificationUrl: "https://aka.ms/teamsfx-notification-new",
          })
        );
      }
    } while (continuationToken);

  res.json({ "code": 200, "msg": "Message sent successfully." });
  }
xianlin commented 1 month ago

I have seen comments like "Multi-tenant is required because the Bot Framework authentication occurs on the Microsoft server for the service-to-service protocol authentication purpose, making single tenant never work".

Is it still true?

https://stackoverflow.com/questions/77307827/singletenant-ms-teams-apps-returns-401-authorization-has-been-denied-for-this

yukun-dong commented 1 month ago

Hi @xianlin, I don't see any problem with your code and configuration. But I also see some comments saying "single tenant not supported", for example: https://github.com/microsoft/botbuilder-js/issues/1447, so I suggest you creating issue on https://github.com/microsoft/botbuilder-js repo to seek clarification on this problem.

xianlin commented 1 month ago

I am trying to manually config the Azure Bot Service to use the SingleTenant after switch from MultiTenant to SingleTenant in the AAD app registration as per https://github.com/OfficeDev/TeamsFx/issues/10347

xianlin commented 1 month ago

after made the above changes to supply the msAppTenantID and msAppType, my bot works with Single Tenant mode.

The test was done with deployed to Azure, not using local mode with ngrok.

yukun-dong commented 1 month ago

Hi @xianlin , could you kindly confirm if you've successfully resolved this issue? Thanks.

xianlin commented 1 month ago

Yes @yukun-dong , I confirmed that I have resolved this issue. Thank you