Esercizio - Aggiungere l'accesso Single Sign-On

Completato

In questo esercizio si aggiunge l'accesso Single Sign-On all'estensione del messaggio per autenticare le query utente.

Screenshot di una richiesta di autenticazione in un'estensione del messaggio basata sulla ricerca. Viene visualizzato un collegamento per l'accesso.

Configurare la registrazione dell'app per le API back-end

Creare prima di tutto una registrazione dell'app Microsoft Entra per l'API back-end. Ai fini di questo esercizio ne si crea uno nuovo, tuttavia, in un ambiente di produzione, si userà una registrazione dell'app esistente.

In una finestra del browser:

  1. Passare al portale di Azure
  2. Aprire il menu del portale e selezionare Microsoft Entra ID
  3. Selezionare Registrazioni app e quindi Nuova registrazione
  4. Nel modulo Registra un'applicazione specificare i valori seguenti:
    1. Nome: API Products
    2. Tipi di account di supporto: account in qualsiasi directory organizzativa (qualsiasi tenant di Microsoft Entra ID - Multitenant)
  5. Selezionare Registra per creare la registrazione dell'app
  6. Nel menu a sinistra della registrazione dell'app selezionare Esporre un'API
  7. Selezionare Aggiungi e salva per creare un nuovo URI ID applicazione
  8. Nella sezione Ambiti definiti da questa API selezionare Aggiungi un ambito
  9. Nel modulo Aggiungi un ambito specificare i valori seguenti:
    1. Nome ambito: Product.Read
    2. Chi può fornire il consenso?: Amministratori e utenti
    3. Nome visualizzato del consenso amministratore: Leggere i prodotti
    4. Descrizione del consenso amministratore: consente all'app di leggere i dati del prodotto
    5. Nome visualizzato consenso utente: Leggere i prodotti
    6. Descrizione del consenso utente: consente all'app di leggere i dati del prodotto
    7. Stato: abilitato
  10. Selezionare Aggiungi ambito per creare l'ambito

Prendere quindi nota dell'ID di registrazione dell'app e dell'ID ambito. Questi valori sono necessari per configurare la registrazione dell'app usata per ottenere un token di accesso per l'API back-end.

  1. Nel menu a sinistra della registrazione dell'app selezionare Manifesto
  2. Copiare il valore della proprietà appId e salvarlo per un uso successivo
  3. Copiare il valore della proprietà api.oauth2PermissionScopes[0].id e salvarlo per un uso successivo

Poiché sono necessari questi valori nel progetto, aggiungerli al file di ambiente.

In Visual Studio e nel progetto TeamsApp:

  1. Nella cartella env aprire .env.local

  2. Nel file creare le variabili di ambiente seguenti e impostare i valori sull'ID di registrazione dell'app e sull'ID ambito:

    BACKEND_API_ENTRA_APP_ID=<app-registration-id>
    BACKEND_API_ENTRA_APP_SCOPE_ID=<scope-id>
    
  3. Save your changes

Creare un file manifesto di registrazione dell'app per l'autenticazione con l'API back-end

Per eseguire l'autenticazione con l'API back-end, è necessaria una registrazione dell'app per ottenere un token di accesso con cui chiamare l'API.

Creare quindi un file manifesto di registrazione dell'app. Il manifesto definisce gli ambiti di autorizzazione dell'API e l'URI di reindirizzamento nella registrazione dell'app.

In Visual Studio e nel progetto TeamsApp:

  1. Nella cartella infra\entra creare un file denominato entra.products.api.manifest.json

  2. Nel file aggiungere il codice seguente:

    {
      "id": "${{PRODUCTS_API_ENTRA_APP_OBJECT_ID}}",
      "appId": "${{PRODUCTS_API_ENTRA_APP_ID}}",
      "name": "${{APP_INTERNAL_NAME}}-product-api-${{TEAMSFX_ENV}}",
      "accessTokenAcceptedVersion": 2,
      "signInAudience": "AzureADMultipleOrgs",
      "optionalClaims": {
        "idToken": [],
        "accessToken": [
          {
            "name": "idtyp",
            "source": null,
            "essential": false,
            "additionalProperties": []
          }
        ],
        "saml2Token": []
      },
      "requiredResourceAccess": [
        {
          "resourceAppId": "${{BACKEND_API_ENTRA_APP_ID}}",
          "resourceAccess": [
            {
              "id": "${{BACKEND_API_ENTRA_APP_SCOPE_ID}}",
              "type": "Scope"
            }
          ]
        }
      ],
      "oauth2Permissions": [],
      "preAuthorizedApplications": [],
      "identifierUris": [],
      "replyUrlsWithType": [
        {
          "url": "https://token.botframework.com/.auth/web/redirect",
          "type": "Web"
        }
      ]
    }
    
  3. Save your changes

La proprietà requiredResourceAccess specifica l'ID di registrazione dell'app e l'ID ambito dell'API back-end.

La proprietà replyUrlsWithType specifica l'URI di reindirizzamento utilizzato dal servizio token bot framework per restituire il token di accesso al servizio token dopo l'autenticazione dell'utente.

Aggiornare quindi il flusso di lavoro automatizzato per creare e aggiornare la registrazione dell'app.

Nel progetto TeamsApp:

  1. Apri teamsapp.local.yml

  2. Nel file trovare il passaggio che usa l'azione addApp/update

  3. Dopo l'azione, aggiungere le azioni aadApp/create e aadApp/update per creare e aggiornare la registrazione dell'app:

      - uses: aadApp/create
        with:
            name: ${{APP_INTERNAL_NAME}}-products-api-${{TEAMSFX_ENV}}
            generateClientSecret: true
            signInAudience: AzureADMultipleOrgs
        writeToEnvironmentFile:
            clientId: PRODUCTS_API_ENTRA_APP_ID
            clientSecret: SECRET_PRODUCTS_API_ENTRA_APP_CLIENT_SECRET
            objectId: PRODUCTS_API_ENTRA_APP_OBJECT_ID
            tenantId: PRODUCTS_API_ENTRA_APP_TENANT_ID
            authority: PRODUCTS_API_ENTRA_APP_OAUTH_AUTHORITY
            authorityHost: PRODUCTS_API_ENTRA_APP_OAUTH_AUTHORITY_HOST
    
      - uses: aadApp/update
        with:
            manifestPath: "./infra/entra/entra.products.api.manifest.json"
            outputFilePath : "./infra/entra/build/entra.products.api.${{TEAMSFX_ENV}}.json"
    
  4. Save your changes

L'azione aadApp/create crea una nuova registrazione dell'app con il nome, il gruppo di destinatari e genera un segreto client specificato. La proprietà writeToEnvironmentFile scrive l'ID di registrazione dell'app, il segreto client, l'ID oggetto, l'ID tenant, l'autorità e l'host dell'autorità nei file di ambiente. Il segreto client viene crittografato e archiviato in modo sicuro nel file env.local.user . Il nome della variabile di ambiente per il segreto client è preceduto da SECRET_ e indica a Teams Toolkit di non scrivere il valore nei log.

L'azione aadApp/update aggiorna la registrazione dell'app con il file manifesto specificato.

Centralizzare il nome dell'impostazione di connessione

Innanzitutto, centralizzare il nome dell'impostazione di connessione nel file di ambiente e aggiornare la configurazione dell'app per accedere al valore della variabile di ambiente in fase di esecuzione.

Continuare in Visual Studio e nel progetto TeamsApp:

  1. Nella cartella env aprire .env.local

  2. Nel file aggiungere il codice seguente:

    CONNECTION_NAME=ProductsAPI
    
  3. Apri teamsapp.local.yml

  4. Nel file trovare il passaggio che usa l'azione file/createOrUpdateJsonFile destinata a ./appsettings. Development.json file. Aggiornare la matrice di contenuto per includere la variabile di ambiente CONNECTION_NAME e scrivere il valore in appsettings. Development.json file:

      - uses: file/createOrUpdateJsonFile
        with:
          target: ../ProductsPlugin/appsettings.Development.json
          content:
            BOT_ID: ${{BOT_ID}}
            BOT_PASSWORD: ${{SECRET_BOT_PASSWORD}}
            CONNECTION_NAME: ${{CONNECTION_NAME}}
    
  5. Save your changes

Aggiornare quindi la configurazione dell'app per accedere alla variabile di ambiente CONNECTION_NAME .

Nel progetto ProductsPlugin:

  1. Apri Config.cs

  2. Nella classe ConfigOptions aggiungere una nuova proprietà denominata CONNECTION_NAME

    public class ConfigOptions
    {
      public string BOT_ID { get; set; }
      public string BOT_PASSWORD { get; set; }
      public string CONNECTION_NAME { get; set; }
    }
    
  3. Save your changes

  4. Apri Program.cs

  5. Nel file aggiornare il codice che legge la configurazione dell'app per includere la proprietà CONNECTION_NAME

    var config = builder.Configuration.Get<ConfigOptions>();
    builder.Configuration["MicrosoftAppType"] = "MultiTenant";
    builder.Configuration["MicrosoftAppId"] = config.BOT_ID;
    builder.Configuration["MicrosoftAppPassword"] = config.BOT_PASSWORD;
    builder.Configuration["ConnectionName"] = config.CONNECTION_NAME;
    
  6. Save your changes

Aggiornare quindi il codice del bot per usare il nome dell'impostazione di connessione in fase di esecuzione.

  1. Nella cartella Cerca aprire SearchApp.cs

  2. Nella classe SearchApp creare un costruttore che accetta un oggetto IConfiguration e assegna il valore della proprietà CONNECTION_NAME a un campo privato denominato connectionName

    public class SearchApp : TeamsActivityHandler
    {
      private readonly string connectionName;
    
      public SearchApp(IConfiguration configuration)
      {
        connectionName = configuration["CONNECTION_NAME"];
      }  
    }
    
  3. Save your changes

Configurare l'impostazione di connessione dell'API Products

Per eseguire l'autenticazione con l'API back-end, è necessario configurare un'impostazione di connessione nella risorsa di Azure Bot.

Continuare in Visual Studio e nel progetto TeamsApp:

  1. Nella cartella infra aprire azure.parameters.local.json

  2. Nel file aggiungere i parametri backendApiEntraAppClientId, productsApiEntraAppClientId, productsApiEntraAppClientSecret e connectionName

    {
      "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
      "contentVersion": "1.0.0.0",
      "parameters": {
        "resourceBaseName": {
          "value": "bot-${{RESOURCE_SUFFIX}}-${{TEAMSFX_ENV}}"
        },
        "botEntraAppClientId": {
          "value": "${{BOT_ID}}"
        },
        "botDisplayName": {
          "value": "${{APP_DISPLAY_NAME}}"
        },
        "botAppDomain": {
          "value": "${{BOT_DOMAIN}}"
        },
        "backendApiEntraAppClientId": {
          "value": "${{BACKEND_API_ENTRA_APP_ID}}"
        },
        "productsApiEntraAppClientId": {
          "value": "${{PRODUCTS_API_ENTRA_APP_ID}}"
        },
        "productsApiEntraAppClientSecret": {
          "value": "${{SECRET_PRODUCTS_API_ENTRA_APP_CLIENT_SECRET}}"
        },
        "connectionName": {
          "value": "${{CONNECTION_NAME}}"
        }
      }
    }
    
  3. Save your changes

Aggiornare quindi il file Bicep per includere i nuovi parametri e passarli alla risorsa di Azure Bot.

  1. Nella cartella infra aprire il file denominato azure.local.bicep

  2. Nel file, dopo la dichiarazione del parametro botAppDomain, aggiungere le dichiarazioni di parametro backendApiEntraAppClientId, productsApiEntraAppClientId, productsApiEntraAppClientSecret e connectionName

    param backendApiEntraAppClientId string
    param productsApiEntraAppClientId string
    @secure()
    param productsApiEntraAppClientSecret string
    param connectionName string
    
  3. Nella dichiarazione del modulo azureBotRegistration aggiungere i nuovi parametri

    module azureBotRegistration './botRegistration/azurebot.bicep' = {
      name: 'Azure-Bot-registration'
      params: {
        resourceBaseName: resourceBaseName
        botEntraAppClientId: botEntraAppClientId
        botAppDomain: botAppDomain
        botDisplayName: botDisplayName
        backendApiEntraAppClientId: backendApiEntraAppClientId
        productsApiEntraAppClientId: productsApiEntraAppClientId
        productsApiEntraAppClientSecret: productsApiEntraAppClientSecret
        connectionName: connectionName
      }
    }
    
  4. Salvare le modifiche.

Aggiornare infine il file Bicep di registrazione del bot per includere la nuova impostazione di connessione.

  1. Nella cartella infra/botRegistration aprire azurebot.bicep

  2. Nel file, dopo la dichiarazione del parametro botAppDomain, aggiungere le dichiarazioni di parametro backendApiEntraAppClientId, productsApiEntraAppClientId, productsApiEntraAppClientSecret e connectionName

    param backendApiEntraAppClientId string
    param productsApiEntraAppClientId string
    @secure()
    param productsApiEntraAppClientSecret string
    param connectionName string
    
  3. Nel file creare una nuova risorsa denominata botServicesProductsApiConnection

    resource botServicesProductsApiConnection 'Microsoft.BotService/botServices/connections@2022-09-15' = {
      parent: botService
      name: connectionName
      location: 'global'
      properties: {
        serviceProviderDisplayName: 'Azure Active Directory v2'
        serviceProviderId: '30dd229c-58e3-4a48-bdfd-91ec48eb906c'
        clientId: productsApiEntraAppClientId
        clientSecret: productsApiEntraAppClientSecret
        scopes: 'api://${backendApiEntraAppClientId}/Product.Read'
        parameters: [
          {
            key: 'tenantID'
            value: 'common'
          }
          {
            key: 'tokenExchangeUrl'
            value: 'api://${botAppDomain}/botid-${botEntraAppClientId}'
          }
        ]
      }
    }
    
  4. Save your changes

Configurare l'autenticazione nell'estensione del messaggio

Per autenticare le query utente nell'estensione del messaggio, usare Bot Framework SDK per ottenere un token di accesso per l'utente dal servizio token bot framework. Il token di accesso può quindi essere usato per accedere ai dati da un servizio esterno.

Per semplificare il codice, creare una classe helper che gestisce l'autenticazione utente.

Continuare in Visual Studio e nel progetto ProductsPlugin:

  1. Creare una nuova cartella denominata Helpers

  2. Nella cartella Helpers creare un nuovo file di classe denominato AuthHelpers.cs

  3. Nel file aggiungere il codice seguente:

    using Microsoft.Bot.Connector.Authentication;
    using Microsoft.Bot.Schema;
    using Microsoft.Bot.Schema.Teams;
    
    internal static class AuthHelpers
    {
        internal static async Task<MessagingExtensionResponse> CreateAuthResponse(UserTokenClient userTokenClient, string connectionName, Activity activity, CancellationToken cancellationToken)
        {
            var resource = await userTokenClient.GetSignInResourceAsync(connectionName, activity, null, cancellationToken);
    
            return new MessagingExtensionResponse
            {
                ComposeExtension = new MessagingExtensionResult
                {
                    Type = "auth",
                    SuggestedActions = new MessagingExtensionSuggestedAction
                    {
                        Actions = [
                            new() {
                                Type = ActionTypes.OpenUrl,
                                Value = resource.SignInLink,
                                Title = "Sign In",
                            },
                        ],
                    },
                },
            };
        }
    
        internal static async Task<TokenResponse> GetToken(UserTokenClient userTokenClient, string state, string userId, string channelId, string connectionName, CancellationToken cancellationToken)
        {
            var magicCode = string.Empty;
    
            if (!string.IsNullOrEmpty(state))
            {
                if (int.TryParse(state, out var parsed))
                {
                    magicCode = parsed.ToString();
                }
            }
    
            return await userTokenClient.GetUserTokenAsync(userId, connectionName, channelId, magicCode, cancellationToken);
        }
    
        internal static bool HasToken(TokenResponse tokenResponse) => tokenResponse != null && !string.IsNullOrEmpty(tokenResponse.Token);
    }
    
  4. Save your changes

I tre metodi helper nella classe AuthHelpers gestiscono l'autenticazione utente nell'estensione del messaggio.

  • Il metodo CreateAuthResponse costruisce una risposta che esegue il rendering di un collegamento di accesso nell'interfaccia utente. Il collegamento di accesso viene recuperato dal servizio token usando il metodo GetSignInResourceAsync .
  • Il metodo GetToken usa il client del servizio token per ottenere un token di accesso per l'utente corrente. Il metodo usa un codice magic per verificare l'autenticità della richiesta.
  • Il metodo HasToken controlla se la risposta del servizio token contiene un token di accesso. Se il token non è Null o vuoto, il metodo restituisce true.

Aggiornare quindi il codice dell'estensione del messaggio per usare i metodi helper per autenticare le query utente.

  1. Nella cartella Cerca aprire SearchApp.cs

  2. Nella parte superiore del file aggiungere l'istruzione using seguente:

    using Microsoft.Bot.Connector.Authentication;
    
  3. Nel metodo OnTeamsMessagingExtensionQueryAsync aggiungere il codice seguente all'inizio del metodo:

    var userTokenClient = turnContext.TurnState.Get<UserTokenClient>();
    var tokenResponse = await AuthHelpers.GetToken(userTokenClient, query.State, turnContext.Activity.From.Id, turnContext.Activity.ChannelId, connectionName, cancellationToken);
    
    if (!AuthHelpers.HasToken(tokenResponse))
    {
        return await AuthHelpers.CreateAuthResponse(userTokenClient, connectionName, (Activity)turnContext.Activity, cancellationToken);
    }
    
  4. Save your changes

Aggiungere quindi il dominio del servizio token al file manifesto dell'app per assicurarsi che il client possa considerare attendibile il dominio durante l'avvio di un flusso di Single Sign-On.

Nel progetto TeamsApp:

  1. Nella cartella appPackage aprire manifest.json

  2. Nel file aggiornare la matrice validDomains , aggiungere il dominio del servizio token:

    "validDomains": [
        "token.botframework.com",
        "${{BOT_DOMAIN}}"
    ]
    
  3. Save your changes

Creare e aggiornare le risorse

Con tutto ciò che è ora disponibile, eseguire il processo Preparare le dipendenze delle app di Teams per creare nuove risorse e aggiornarne di quelle esistenti.

Continuare in Visual Studio:

  1. In Esplora soluzioni fare clic con il pulsante destro del mouse sul progetto TeamsApp
  2. Espandere il menu Teams Toolkit, selezionare Prepare Teams App Dependencies (Preparare le dipendenze delle app di Teams)
  3. Nella finestra di dialogo Account Microsoft 365 selezionare Continua
  4. Nella finestra di dialogo Provision (Provision ) selezionare Provisioning (Provisioning)
  5. Nella finestra di dialogo di avviso di Teams Toolkit selezionare Provisioning
  6. Nella finestra di dialogo Informazioni di Teams Toolkit selezionare l'icona a croce per chiudere la finestra di dialogo

Eseguire ed eseguire il debug

Con il provisioning delle risorse, avviare una sessione di debug per testare l'estensione del messaggio.

  1. Per avviare una nuova sessione di debug, premere F5 o selezionare Avvia dalla barra degli strumenti

  2. Attendere l'apertura di una finestra del browser e la finestra di dialogo di installazione dell'app viene visualizzata nel client Web di Microsoft Teams. Se richiesto, immettere le credenziali dell'account Microsoft 365.

  3. Nella finestra di dialogo di installazione dell'app selezionare Aggiungi

  4. Aprire una chat di Microsoft Teams nuova o esistente

  5. Nell'area di composizione dei messaggi selezionare + per aprire la selezione app

  6. Nell'elenco delle app selezionare Prodotti Contoso per aprire l'estensione del messaggio

  7. Nella casella di testo immettere hello

  8. Viene visualizzato un messaggio che indica che è necessario accedere per usare questa app

    Screenshot di una richiesta di autenticazione in un'estensione del messaggio basata sulla ricerca. Viene visualizzato un collegamento per l'accesso.

  9. Seguire il collegamento di accesso per avviare il flusso di autenticazione

  10. Consenso alle autorizzazioni richieste e ritorno a Microsoft Teams

    Screenshot della finestra di dialogo di consenso per l'autorizzazione dell'API Microsoft Entra.

  11. Attendere il completamento della ricerca e visualizzare i risultati

  12. Nell'elenco dei risultati selezionare hello per incorporare una scheda nella finestra di messaggio compose

Tornare a Visual Studio e selezionare Arresta dalla barra degli strumenti o premere MAIUSC + F5 per arrestare la sessione di debug.