Configurare l'agente per usare OAuth

Un agente usa OAuth per accedere agli utenti e ottenere token per le risorse a valle (come Microsoft Graph) senza gestire le credenziali. Il servizio Azure Bot gestisce lo scambio dei token e l'agente recupera il token dell'utente risultante durante un turno.

Panoramica

L'uso di OAuth in un agente comporta tre attività:

  1. Configura OAuth su Azure Bot e registrazione app: crea una o più connessioni OAuth sulla tua risorsa Azure Bot, ciascuna supportata da una registrazione dell'app Microsoft Entra ID. Aggiungi l'autorizzazione utente usando la credenziale di identità federata copre l'approccio più comune. Sono supportati anche altri tipi di credenziali, come i segreti client o i certificati. Per l'insieme completo di opzioni, vedi Basi dell'autenticazione del servizio Bot.
  2. Configura le impostazioni corrispondenti nell'agente: ogni connessione OAuth in Azure Bot si mappa a un gestore OAuth nella configurazione dell'agente. Vedi Impostazioni. Per la configurazione generale degli agenti, consulta Cos'è SDK per agenti Microsoft 365.
  3. Usa i token in codice: durante un turno, recupera il token utente, o effettua uno scambio On-Behalf-Of (OBO), tramite l'API di autorizzazione utente dell'agente. Vedi Usa il token in codice (non-OBO) e Usa il token in codice (OBO).

Tieni a mente i seguenti concetti mentre leggi il resto di questo articolo:

  • Un Azure Bot può contenere più connessioni OAuth. Ad esempio, una connessione per Microsoft Graph e un'altra per GitHub. Ogni connessione è configurata indipendentemente in Azure Bot.
  • C'è una relazione 1:1 tra una connessione OAuth in Azure Bot e un gestore OAuth nell'agente. L'impostazione AzureBotOAuthConnectionName di un gestore dà il nome alla connessione Azure Bot che utilizza. Per usare due connessioni, definiamo due gestori.
  • L'API di autorizzazione utente dell'agente è la superficie che chiami nel codice. In .NET questo è AgentApplication.UserAuthorization, ad esempio GetTurnTokenAsync per leggere un token ed ExchangeTurnTokenAsync per eseguire uno scambio OBO. Le superfici equivalenti sono authorization in JavaScript e auth in Python.

Per esempi funzionanti, consulta l'accesso automatico e gli esempi OBO per:

Supporto della lingua per OAuth

L'SDK per agenti supporta OAuth per .NET, JavaScript e Python. I concetti fondamentali sono gli stessi in ogni linguaggio: gestori OAuth, collegamento di gestore alle route e scambio OBO. Solo il formato di configurazione e i nomi delle API dei gestori differiscono:

Lingua Dove si effettua la configurazione Superficie API
.NET appsettings.json (o codice in Program.cs) AgentApplication.UserAuthorization
JavaScript Variabili di ambiente .env AgentApplication.authorization
Python Variabili di ambiente .env AgentApplication.auth

Per JavaScript e Python, le chiavi .env usano gli stessi nomi gerarchici della struttura .NET appsettings.json , con ogni livello separato da un doppio carattere di sottolineatura (__). Le chiavi JavaScript mantengono i nomi delle foglie in notazione camel mostrati nelle tabelle (ad esempio, azureBotOAuthConnectionName). Le chiavi Python sono maiuscole (ad esempio, AZUREBOTOAUTHCONNECTIONNAME).

Importante

L'accesso automatico globale (AutoSignIn) e DefaultHandlerName sono supportati solo in .NET. In JavaScript e Python, si collegano gestori OAuth a route specifiche, come mostrato nella configurazione per route.

Impostazioni

Un oggetto autorizzazione utente all'interno di AgentApplication controlla come l'agente acquisisce i token utente. Ogni gestore dà almeno un nome alla connessione Azure Bot OAuth che utilizza. I seguenti esempi mostrano la struttura minima in ogni linguaggio. Le tabelle che seguono descrivono il resto delle proprietà disponibili, e le sezioni OBO coprono le impostazioni OBOConnectionName e OBOScopes.

In .NET, configura l'autorizzazione utente sotto AgentApplication in appsettings.json:

  "AgentApplication": {
    "UserAuthorization": {
      "DefaultHandlerName": "{{handler-name}}",
      "AutoSignIn": true | false,
      "Handlers": {
        "{{handler-name}}": {
          "Settings": {
            "AzureBotOAuthConnectionName": "{{azure-bot-connection-name}}"
          }
        }
      }
    }
  }

In JavaScript, configura l'autorizzazione utente con variabili di ambiente nel file .env:

# Connection used to authenticate the agent itself
connections__serviceConnection__settings__clientId=
connections__serviceConnection__settings__clientSecret=
connections__serviceConnection__settings__tenantId=
connectionsMap__0__connection=serviceConnection
connectionsMap__0__serviceUrl=*

# OAuth handler named "{{handler-name}}"
AgentApplication__UserAuthorization__Handlers__{{handler-name}}__Settings__azureBotOAuthConnectionName={{azure-bot-connection-name}}

In Python, configura l'autorizzazione utente con variabili di ambiente nel file .env:

# Connection used to authenticate the agent itself
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID=
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET=
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID=

# OAuth handler named "{{handler-name}}"
AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__{{handler-name}}__SETTINGS__AZUREBOTOAUTHCONNECTIONNAME={{azure-bot-connection-name}}

Proprietà UserAuthorization

La tabella seguente elenca le proprietà di primo livello di UserAuthorization che determinano come vengono selezionati i gestori e come i token vengono acquisiti per ogni attività in ingresso.

Proprietà Richiesto Type Descrizione
DefaultHandlerName No (non consigliato) string Solo .NET. Nome del gestore assegnato quando AutoSignIn restituisce true e non viene specificato alcun override per percorso.
AutoSignIn No bool o delegato Solo .NET. Quando il valore è true (predefinito), l'agente tenta di acquisire un token per ogni attività in arrivo. Sovrascrivi in fase di esecuzione con Options.AutoSignIn per filtrare i tipi di attività.
Handlers Sì (almeno uno) oggetto (dizionario) Mapping dei nomi dei gestori alle relative configurazioni. Ogni chiave deve essere univoca.

In .NET, per limitare a quali attività si applica l'accesso automatico, imposta un predicato simile a: Options.AutoSignIn = (context, ct) => Task.FromResult(context.Activity.IsType(ActivityTypes.Message));. JavaScript e Python non supportano l'accesso automatico globale. Invece, esegui l'accesso per ambito collegando i nomi specifici dei gestori a singole route, come mostrato negli esempi per percorso.

Proprietà impostazioni

La tabella seguente descrive l'oggetto Settings annidato applicato a un singolo gestore OAuth, che controlla la presentazione della scheda di accesso, il comportamento dei tentativi di ripetizione, i timeout e la configurazione opzionale dello scambio OBO.

Proprietà Richiesto Type Descrizione
AzureBotOAuthConnectionName string Nome della connessione OAuth definito sulla risorsa bot di Azure.
OBOConnectionName No (solo OBO) string Nome di una connessione dell'agents SDK usata per eseguire uno scambio di token On-Behalf-Of.
OBOScopes No (solo OBO) string[] Ambiti richiesti durante lo scambio OBO. Se omesso con OBOConnectionName, puoi chiamare ExchangeTurnTokenAsync manualmente.
Title No string Titolo della scheda di accesso personalizzata Si imposta su Accedi.
Text No string Testo del pulsante della scheda di accesso. L'impostazione predefinita è Accedi.
InvalidSignInRetryMax No int Numero massimo di tentativi consentiti quando l'utente inserisce un codice non valido. Il valore predefinito è 2.
InvalidSignInRetryMessage No string Messaggio mostrato dopo un inserimento di codice non valido. Il messaggio predefinito è Codice di accesso non valido. Inserire il codice a 6 cifre.
Timeout No int (ms) Numero di millisecondi prima che un tentativo di accesso in corso scada. Il valore predefinito è 900.000 (15 minuti).

Nota

AzureBotOAuthConnectionName, OBOConnectionName, OBOScopes, Title, e Text si applicano a tutti e tre i linguaggi (usando le lettere maiuscole e minuscole della chiave per linguaggio come descritto in Supporto linguistico per OAuth). InvalidSignInRetryMax, InvalidSignInRetryMessage, e Timeout sono le impostazioni .NET.

Quale tipo si deve usare?

Consulta la seguente tabella per decidere quale approccio si adatta al tuo scenario.

Scelta Utilizzare se
Accesso automatico (solo .NET) Vuoi che ogni attività in arrivo acquisisca automaticamente un token oppure vuoi che solo un sottoinsieme filtrato (ad esempio, solo i messaggi o tutto tranne gli eventi) lo faccia, fornendo un predicato a UserAuthorizationOptions.AutoSignIn. Supportato solo in .NET.
Per-route Solo alcuni gestori di route necessitano di token oppure percorsi diversi devono utilizzare connessioni OAuth diverse (e quindi token diversi). Questa opzione è l'unica in JavaScript e Python. In .NET, si tratta di un componente aggiuntivo con l'accesso automatico globale. Se entrambi sono abilitati in .NET, il turno ha accesso ai token da entrambi.

Usare il token nel codice (non OBO)

Questa sezione mostra come recuperare e utilizzare il token utente restituito direttamente dalla tua connessione OAuth del bot di Azure senza effettuare uno scambio On‑Behalf‑Of. In .NET, puoi usare l'accesso automatico globale o gestori per route. JavaScript e Python usano solo gestori per route. All'interno del tuo gestore di attività, recupera il token (GetTurnTokenAsync in .NET, authorization.getToken in JavaScript, auth.get_token in Python) il più tardi possibile così che l'SDK possa aggiornare il token se è vicino alla scadenza. I seguenti esempi illustrano entrambi i modelli.

Accesso automatico (solo .NET)

Nota

L'accesso automatico globale e DefaultHandlerName sono disponibili solo in .NET. Per JavaScript e Python, usa la configurazione per route.

Utilizza questa configurazione quando l'accesso automatico globale deve ottenere un token per ogni attività in ingresso senza la necessità di specificare gestori per route.

  "AgentApplication": {
    "UserAuthorization": {
      "DefaultHandlerName": "auto",
      "Handlers": {
        "auto": {
          "Settings": {
            "AzureBotOAuthConnectionName": "teams_sso",
          }
        }
      }
    }
  },

Il codice dell'agente sarà simile a:

public class MyAgent : AgentApplication
{
    [MessageRoute]
    public async Task OnMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken)
    {
        var token = await UserAuthorization.GetTurnTokenAsync(turnContext);

        // use the token 
    }
}

Configurazione per route

Utilizza una configurazione per route quando desideri un controllo granulare: solo le route che contrassegni esplicitamente acquisiscono token. La configurazione per route presenta i seguenti vantaggi:

  • Riduce il recupero inutile dei token.
  • Permette a route diverse di puntare a connessioni OAuth distinte (e quindi risorse o ambiti differenti).
  • Ti permette di mescolare route autenticate e non autenticate all'interno dello stesso agente.

Nell'esempio seguente un singolo gestore graph è collegato solo alla route dei messaggi.

In .NET, l'accesso automatico globale è disabilitato e il gestore graph viene collegato alla route usando autoSignInHandlers.

  "AgentApplication": {
    "UserAuthorization": {
      "AutoSignIn": false,
      "Handlers": {
        "graph": {
          "Settings": {
            "AzureBotOAuthConnectionName": "teams_sso",
          }
        }
      }
    }
  },

Il codice dell'agente sarà simile a:

public class MyAgent : AgentApplication
{
    [MessageRoute(autoSignInHandlers: "graph")]
    public async Task OnMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken)
    {
        var token = await UserAuthorization.GetTurnTokenAsync(turnContext, "graph");

        // use the token
    }
}

In JavaScript, passa un array di nomi di gestori come ultimo argomento alla registrazione della route. Solo questa route attiva l'accesso per il gestore graph.

AgentApplication__UserAuthorization__Handlers__graph__Settings__azureBotOAuthConnectionName=teams_sso
AgentApplication__UserAuthorization__Handlers__graph__Settings__title=Graph Sign In
AgentApplication__UserAuthorization__Handlers__graph__Settings__text=Sign in with Microsoft Graph

Il codice dell'agente sarà simile a:

class MyAgent extends AgentApplication {
  constructor () {
    super({ storage: new MemoryStorage() })
    // the `graph` handler runs only for this route
    this.onMessage('-me', this._profileRequest, ['graph'])
  }

  private _profileRequest = async (context, state) => {
    const tokenResponse = await this.authorization.getToken(context, 'graph')

    // use tokenResponse.token
  }
}

In Python, passa auth_handlers al decoratore della route. Solo questa route attiva l'accesso per il gestore GRAPH.

AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__GRAPH__SETTINGS__AZUREBOTOAUTHCONNECTIONNAME=teams_sso

Il codice dell'agente sarà simile a:

@AGENT_APP.message(re.compile(r"^/(me|profile)$", re.IGNORECASE), auth_handlers=["GRAPH"])
async def profile_request(context: TurnContext, state: TurnState) -> None:
    token_response = await AGENT_APP.auth.get_token(context, "GRAPH")

    # use token_response.token

Recuperare il token durante un turno

Recupera il token utente ogni volta che è necessario durante un turno. Puoi chiamarlo più volte. Chiamalo immediatamente prima dell'uso, così la logica di aggiornamento (se necessaria) viene gestita in modo trasparente.

Lingua Chiama
.NET GetTurnTokenAsync(turnContext, handlerName)
JavaScript authorization.getToken(context, handlerName)
Python auth.get_token(context, handler_name)

Usare il token nel codice (OBO)

On-Behalf-Of (OBO) si basa sul fatto che l'accesso iniziale dell'utente restituisce un token scambiabile. Questo richiede che gli scope della connessione OAuth includano uno che corrisponda a un scope esposto dall'API a valle (ad esempio, se l'ambito esposto è defaultScopes, l'ambito configurato potrebbe essere api://botid-{{clientId}}/defaultScopes). L'SDK degli agenti esegue quindi uno scambio Libreria di autenticazione Microsoft (MSAL) utilizzando una connessione configurata identificata da OBOConnectionName e l'elenco di OBOScopes. Quando OBOConnectionName e OBOScopes sono presenti nella configurazione, lo scambio avviene automaticamente e si ottiene il token finale tramite la chiamata token standard (GetTurnTokenAsync / getToken / get_token). Se manca uno dei due, puoi eseguire lo scambio esplicitamente durante l'esecuzione (ExchangeTurnTokenAsync in .NET, authorization.exchangeToken in JavaScript, auth.exchange_token in Python), permettendoti di risolvere dinamicamente la connessione o la lista di ambiti.

OBO nella configurazione

Usa questo modello quando conosci le risorse a valle e gli ambiti di cui hai bisogno al momento della configurazione. Quando vengono specificati sia OBOConnectionName che OBOScopes, l'SDK esegue automaticamente lo scambio On‑Behalf‑Of durante l'accesso all'account. Ciò significa che le chiamate successive a quella del token standard restituiscono direttamente il token OBO senza bisogno di codice aggiuntivo a runtime.

  "AgentApplication": {
    "UserAuthorization": {
      "AutoSignIn": false,
      "Handlers": {
        "graph": {
          "Settings": {
            "AzureBotOAuthConnectionName": "teams_sso",
            "OBOConnectionName": "ServiceConnection",
            "OBOScopes": [
              "https://graph.microsoft.com/.default"
            ]
          }
        }
      }
    }
  },
  "Connections": {
    "ServiceConnection": {
      "Settings": {
        "AuthType": "FederatedCredentials",
        "AuthorityEndpoint": "https://login.microsoftonline.com/{{TenantId}}",
        "ClientId": "{{ClientId}}",
        "FederatedClientId": "{{ManagedIdentityClientId}}",
        "Scopes": [
          "https://api.botframework.com/.default"
        ]
      }
    }
  },

Il codice dell'agente sarà simile a:

public class MyAgent : AgentApplication
{
    [MessageRoute(autoSignInHandlers: "graph")]
    public async Task OnMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken)
    {
        // returns the OBO token because OBOConnectionName and OBOScopes are configured
        var token = await UserAuthorization.GetTurnTokenAsync(turnContext, "graph");

        // use the token
    }
}

In JavaScript, definisci una connessione OBO nel mapping delle connessioni e ne fai riferimento nel gestore con oboConnectionName e oboScopes.

# Agent's own connection
connections__serviceConnection__settings__clientId=
connections__serviceConnection__settings__clientSecret=
connections__serviceConnection__settings__tenantId=

# OBO connection
connections__oboConnection__settings__clientId=
connections__oboConnection__settings__clientSecret=
connections__oboConnection__settings__tenantId=

connectionsMap__0__connection=serviceConnection
connectionsMap__0__serviceUrl=*
connectionsMap__1__connection=oboConnection
connectionsMap__1__serviceUrl=obo

AgentApplication__UserAuthorization__Handlers__graph__Settings__azureBotOAuthConnectionName=teams_sso
AgentApplication__UserAuthorization__Handlers__graph__Settings__oboConnectionName=oboConnection
AgentApplication__UserAuthorization__Handlers__graph__Settings__oboScopes=https://graph.microsoft.com/.default

Il codice dell'agente sarà simile a:

class MyAgent extends AgentApplication {
  constructor () {
    super({ storage: new MemoryStorage() })
    this.onActivity('message', this._onMessage, ['graph'])
  }

  private _onMessage = async (context, state) => {
    // returns the OBO token because oboConnectionName and oboScopes are configured
    const tokenResponse = await this.authorization.getToken(context, 'graph')

    // use tokenResponse.token
  }
}

In Python, definisci una connessione OBO sotto CONNECTIONS e nel fai riferimento nel gestore con OBOCONNECTIONNAME e OBOSCOPES.

# Agent's own connection
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID=
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET=
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID=

# OBO connection
CONNECTIONS__OBO__SETTINGS__CLIENTID=
CONNECTIONS__OBO__SETTINGS__CLIENTSECRET=
CONNECTIONS__OBO__SETTINGS__TENANTID=

AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__GRAPH__SETTINGS__AZUREBOTOAUTHCONNECTIONNAME=teams_sso
AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__GRAPH__SETTINGS__OBOCONNECTIONNAME=OBO
AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__GRAPH__SETTINGS__OBOSCOPES=https://graph.microsoft.com/.default

Il codice dell'agente sarà simile a:

@AGENT_APP.message(re.compile(r".*"), auth_handlers=["GRAPH"])
async def on_message(context: TurnContext, state: TurnState) -> None:
    # returns the OBO token because OBOCONNECTIONNAME and OBOSCOPES are configured
    token_response = await AGENT_APP.auth.get_token(context, "GRAPH")

    # use token_response.token

OBO Exchange in fase di esecuzione

Usa uno scambio di runtime quando non riesci a sistemare la risorsa a valle, gli ambiti o la connessione nella configurazione. Questa situazione si verifica, ad esempio, quando gli ambiti dipendono dal tenant (tenant), dal ruolo utente o da un flag di funzionalità. In questo modello, opzionalmente configuri la connessione OBO, poi chiami il metodo di scambio con gli ambiti che decidi al momento del turno. Ricevi un token scambiato che puoi applicare immediatamente.

Chiama ExchangeTurnTokenAsync con gli ambiti che decidi al turno.

  "AgentApplication": {
    "UserAuthorization": {
      "AutoSignIn": false,
      "Handlers": {
        "graph": {
          "Settings": {
            "AzureBotOAuthConnectionName": "teams_sso",
            "OBOConnectionName": "ServiceConnection"
          }
        }
      }
    }
  },
  "Connections": {
    "ServiceConnection": {
      "Settings": {
        "AuthType": "FederatedCredentials",
        "AuthorityEndpoint": "https://login.microsoftonline.com/{{TenantId}}",
        "ClientId": "{{ClientId}}",
        "FederatedClientId": "{{ManagedIdentityClientId}}",
        "Scopes": [
          "https://api.botframework.com/.default"
        ]
      }
    }
  },

Il codice dell'agente sarà simile a:

public class MyAgent : AgentApplication
{
    [MessageRoute(autoSignInHandlers: "graph")]
    public async Task OnMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken)
    {
        var scopes = GetScopes();

        var exchangedToken = await UserAuthorization.ExchangeTurnTokenAsync(turnContext, "graph", exchangeScopes: scopes);

        // use the token
    }
}

Chiama authorization.exchangeToken con il nome del gestore e gli ambiti che decidi al turno.

connections__oboConnection__settings__clientId=
connections__oboConnection__settings__clientSecret=
connections__oboConnection__settings__tenantId=

connectionsMap__1__connection=oboConnection
connectionsMap__1__serviceUrl=obo

AgentApplication__UserAuthorization__Handlers__graph__Settings__azureBotOAuthConnectionName=teams_sso
AgentApplication__UserAuthorization__Handlers__graph__Settings__oboConnectionName=oboConnection

Il codice dell'agente sarà simile a:

class MyAgent extends AgentApplication {
  constructor () {
    super({ storage: new MemoryStorage() })
    this.onActivity('message', this._onMessage, ['graph'])
  }

  private _onMessage = async (context, state) => {
    const scopes = getScopes()
    const exchangedToken = await this.authorization.exchangeToken(context, 'graph', { scopes })

    // use exchangedToken.token
  }
}

Chiama auth.exchange_token con gli ambiti che decidi al turno e il nome del gestore.

CONNECTIONS__MCS__SETTINGS__CLIENTID=
CONNECTIONS__MCS__SETTINGS__CLIENTSECRET=
CONNECTIONS__MCS__SETTINGS__TENANTID=

AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__MCS__SETTINGS__AZUREBOTOAUTHCONNECTIONNAME=teams_sso
AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__MCS__SETTINGS__OBOCONNECTIONNAME=MCS

Il codice dell'agente sarà simile a:

@AGENT_APP.message(re.compile(r".*"), auth_handlers=["MCS"])
async def on_message(context: TurnContext, state: TurnState) -> None:
    scopes = get_scopes()
    token_response = await AGENT_APP.auth.exchange_token(context, scopes, "MCS")

    # use token_response.token

Impostazioni regionali OAuth

Per le regioni non statunitensi, aggiorna l'endpoint del servizio token che il tuo agente utilizza.

L'esempio seguente mostra una configurazione .NET. Aggiungila a appsettings.json:

"RestChannelServiceClientFactory": {
   "TokenServiceEndpoint": "{{service-endpoint-uri}}"
}

Per service-endpoint-url, usa il valore appropriato dalla seguente tabella per i bot del cloud pubblico con residenza dei dati nella regione specificata.

URI Area geografica
https://europe.api.botframework.com Europa
https://unitedstates.api.botframework.com Stati Uniti
https://india.api.botframework.com India