How can Azure app subscribe to Team chat messages not part of the same Azure account/ tenant?

Anand P 1 Reputation point
2021-09-03T14:55:25.143+00:00

Objective: Subscribe a listener application to receive all ‘chat message’ events in the team/ channel for which the subscription has been created (or renewed).
Reference:
https://learn.microsoft.com/en-us/graph/api/subscription-post-subscriptions?view=graph-rest-1.0&tabs=javascript#example

We are using the Graph Subscribe API to read all Team chat messages, our understanding is that we would need to create an Azure app for this (as opposed to a plain-vanilla Teams Bot app using Bot API). Questions:
a) Does the Microsoft authorization hierarchy allow an Azure app to read Team chat messages from Microsoft accounts that are not part of the same Azure account/ tenant? What are the pre-requisites to do so?
b) Can such an app be published to the Teams marketplace? We would prefer this to improve the discoverability of the app across all organizations that use Teams, including those that do not have an Azure account.
c) Graph Subscribe API to read all Team chat messages requires the subscription to be renewed every 60 minutes. This makes the solution complex and prone to breakdown. Is there an alternate solution using which the subscription can be configured to be in non-expiry mode?

Error message we are getting while subscribing with POST - https://graph.microsoft.com/beta/subscriptions:
Operation: Create; Exception: [Status Code: Forbidden; Reason: Required permissions to access tenant-wide channel message subscription ('ChannelMessage.Read.All') is missing.]
This is when using the Bearer token generated from the common endpoint that we used with our Organizational account.
We also tried to give app roles like: https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-add-app-roles-in-azure-ad-apps
but it doesn't work either.

Microsoft Graph
Microsoft Graph
A Microsoft programmability model that exposes REST APIs and client libraries to access data on Microsoft 365 services.
10,716 questions
Microsoft Teams Development
Microsoft Teams Development
Microsoft Teams: A Microsoft customizable chat-based workspace.Development: The process of researching, productizing, and refining new or existing technologies.
2,886 questions
{count} votes

3 answers

Sort by: Most helpful
  1. Ramjot Singh 861 Reputation points Microsoft Employee
    2021-09-06T07:27:32.507+00:00

    a) Does the Microsoft authorization hierarchy allow an Azure app to read Team chat messages from Microsoft accounts that are not part of the same Azure account/ tenant? What are the pre-requisites to do so?
    You can use multi-tenant app. Refer to https://learn.microsoft.com/en-us/azure/active-directory/develop/single-and-multi-tenant-apps

    b) Can such an app be published to the Teams marketplace? We would prefer this to improve the discoverability of the app across all organizations that use Teams, including those that do not have an Azure account.
    You can use Graph in any app. However, looking at your scenario of listening to an entire tenant's messages means that Graph app will be working behind the scenes. For example, listening to messages, performing analytics on them and then delivering value through another Teams app which will be published to store.

    c) Graph Subscribe API to read all Team chat messages requires the subscription to be renewed every 60 minutes. This makes the solution complex and prone to breakdown. Is there an alternate solution using which the subscription can be configured to be in non-expiry mode?
    We intend to extend the expiration but it will never be non-expiry mode due to security reasons.

    Adding roles in the app is not enough. You need admin of the tenant you intend to use the app in to consent to your app. Please refer to https://learn.microsoft.com/en-us/azure/active-directory/manage-apps/grant-admin-consent

    2 people found this answer helpful.

  2. Anand P 1 Reputation point
    2021-09-23T13:37:26.963+00:00

    Hi Ramjot,
    Thanks for the pointers shared so far; we are able to successfully read chat messages from the Graph Subscription API, across tenants.

    However, the ID of the user who posted the chat message is not available in the chat message payload. This is required for our app to respond to this user (privately). Refer to the Graph Subscription payload below as opposed to payload of messages with RSC using Teams Bot API.

    Payload using Graph Subscription API (does NOT contain user identifier):

        {  
          value: [  
            {  
              subscriptionId: 'ae--------------',  
              changeType: 'created',  
              clientState: null,  
              subscriptionExpirationDateTime: '2021-09-23T04:08:45-07:00',  
              resource: "teams('9d----------------')/channels('19:a-----------@thread.tacv2')/messages('16------')/replies('16-------')",  
              resourceData: {  
                id: '16----------',  
                '@odata.type': '#Microsoft.Graph.chatMessage',  
                '@odata.id': "teams('9d--------------')/channels('19:a---------@thread.tacv2')/messages('16-----')/replies('16---------')"  
              },  
              encryptedContent: {  
                data: 'qVuJB/PkQaKAbqp+zpxTm0hsQiDGUemanHuW1Y',  
                dataSignature: 'JM---------------',  
                dataKey: 'mtg--------------------',  
                encryptionCertificateId: '------',  
                encryptionCertificateThumbprint: 'B---------------------'  
              },  
              tenantId: '8-------------------'  
            }  
          ],  
          validationTokens: [  
            'eyJ0eXAiOiJ----------------'  
          ]  
        }  
    

    Payload with RSC using Teams Bot API (contains user identifier):

    TurnContext {  
      _respondedRef: { responded: false },  
      _turnState: TurnContextStateCollection [Map] {  
        Symbol(BotIdentity) => ClaimsIdentity { claims: [Array], authenticationType: true },  
        Symbol(ConnectorClient) => ConnectorClient {  
          _withCredentials: false,  
          _httpClient: [AxiosHttpClient],  
          _requestPolicyOptions: [RequestPolicyOptions],  
          _requestPolicyFactories: [Array],  
          baseUri: 'https://smba.trafficmanager.net/in/',  
          requestContentType: 'application/json; charset=utf-8',  
          credentials: [MicrosoftAppCredentials],  
          attachments: [Attachments],  
          conversations: [Conversations]  
        },  
        Symbol(OAuthScope) => 'https://api.botframework.com',  
        'botCallbackHandler' => [AsyncFunction],  
        turn: { locale: 'en-GB' }  
      },  
      _onSendActivities: [],  
      _onUpdateActivity: [],  
      _onDeleteActivity: [],  
      _turn: 'turn',  
      _locale: 'locale',  
      bufferedReplyActivities: [],  
      _adapter: BotFrameworkAdapter {  
        middleware: MiddlewareSet { middleware: [Array] },  
        BotIdentityKey: Symbol(BotIdentity),  
        ConnectorClientKey: Symbol(ConnectorClient),  
        OAuthScopeKey: Symbol(OAuthScope),  
        TokenApiClientCredentialsKey: Symbol(TokenApiClientCredentials),  
        settings: {  
          appId: '42--------------------',  
          appPassword: '0------------------',  
          channelService: undefined,  
          openIdMetadata: undefined  
        },  
        credentials: MicrosoftAppCredentials {  
          refreshingToken: null,  
          appId: '42-------------------------',  
          _tenant: 'botframework.com',  
          _oAuthEndpoint: 'https://login.microsoftonline.com/botframework.com',  
          authenticationContext: [AuthenticationContext],  
          _oAuthScope: 'https://api.botframework.com',  
          tokenCacheKey: '42-------------------https://api.botframework.com-cache',  
          appPassword: '0-------------------------'  
        },  
        credentialsProvider: SimpleCredentialProvider {  
          appId: '42-------------------',  
          appPassword: '0----------------4'  
        },  
        isEmulatingOAuthCards: false,  
        authConfiguration: AuthenticationConfiguration {  
          requiredEndorsements: [],  
          validateClaims: undefined  
        },  
        turnError: [AsyncFunction]  
      },  
      _activity: {  
        text: '<at>converseBot2-local-debug</at> intro \n',  
        textFormat: 'plain',  
        attachments: [ [Object] ],  
        type: 'message',  
        timestamp: 2021-09-23T10:52:07.986Z,  
        localTimestamp: 2021-09-23T10:52:07.986Z,  
        id: '16**********',  
        channelId: 'msteams',  
        serviceUrl: 'https://smba.trafficmanager.net/in/',  
        from: {  
          id: '29:1mOwl*************************',  
          name: 'FNU LNU',  
          aadObjectId: 'd4*******************'  
        },  
        conversation: {  
          isGroup: true,  
          conversationType: 'channel',  
          tenantId: '8f*****************',  
          id: '19:a********************@thread.tacv2;messageid=16********'  
        },  
        recipient: {  
          id: '28:42******************2',  
          name: 'converseBot2-local-debug'  
        },  
        entities: [ [Object], [Object] ],  
        channelData: {  
          teamsChannelId: '19:a*******************@thread.tacv2',  
          teamsTeamId: '19:0*******************@thread.tacv2',  
          channel: [Object],  
          team: [Object],  
          tenant: [Object]  
        },  
        locale: 'en-GB',  
        localTimezone: 'Asia/Calcutta',  
        rawTimestamp: '2021-09-23T10:52:07.9862382Z',  
        rawLocalTimestamp: '2021-09-23T16:22:07.9862382+05:30',  
        callerId: 'urn:botframework:azure'  
      }  
    }  
    

  3. Ramjot Singh 861 Reputation points Microsoft Employee
    2021-09-23T15:14:32.497+00:00

    You need to decrypt the encryptedContent to get the full content. Please refer to https://learn.microsoft.com/en-us/graph/webhooks-with-resource-data?context=graph%2Fapi%2F1.0&view=graph-rest-1.0 on how to decrypt.