Configurer votre assistant pour utiliser OAuth

Un assistant utilise OAuth pour connecter les utilisateurs et obtenir des jetons pour les ressources en aval (comme Microsoft Graph) sans gérer les identifiants lui-même. Azure Bot Service gère l’échange de jetons, et l’assistant récupère le jeton utilisateur résultant lors d’un tour.

Vue d’ensemble

L’utilisation d’OAuth dans un assistant implique trois activités :

  1. Configurez OAuth sur le bot Azure et l’enregistrement de l’application : créez une ou plusieurs connexions OAuth sur votre ressource Azure Bot, chacune soutenue par un enregistrement d’application Microsoft Entra ID. Ajouter une autorisation utilisateur à l’aide d’un identifiant d’identité fédéré couvre l’approche la plus courante. D’autres types de certifications, tels que les clés secrètes client ou les certificats, sont également pris en charge. Pour l’ensemble complet des options, voir Bases de l’authentification Bot Service.
  2. Configurer les paramètres correspondants dans l’assistant : chaque connexion OAuth sur Azure Bot correspond à un gestionnaire OAuth dans la configuration de l’assistant. Consultez Paramètres. Pour la configuration générale des assistants, voir Qu’est-ce que Microsoft 365 Agents SDK.
  3. Utiliser les jetons en code : lors d’un tour, récupérez le jeton utilisateur — ou effectuez un échange au nom de — via l’API d’autorisation utilisateur de l’assistant. Voir Utiliser le jeton en code (hors au nom de) et Utiliser le jeton en code (au nom de).

Gardez à l’esprit les concepts suivants en lisant la suite de cet article :

  • Un bot Azure peut contenir plusieurs connexions OAuth. Par exemple, une connexion pour Microsoft Graph et une autre pour GitHub. Chaque connexion est configurée indépendamment sur Azure Bot.
  • Il existe une relation 1:1 entre une connexion OAuth sur Azure Bot et un gestionnaire OAuth dans l’assistant. Le paramètre AzureBotOAuthConnectionName d’un gestionnaire nomme la connexion Azure Bot qu’il utilise. Pour utiliser deux connexions, définissons deux gestionnaires.
  • L’API d’autorisation utilisateur de l’assistant est la surface que vous appelez dans le code. Dans .NET, c’est AgentApplication.UserAuthorization— par exemple, GetTurnTokenAsync lire un jeton et ExchangeTurnTokenAsync effectuer un échange au nom de. Les surfaces équivalentes sont authorization en JavaScript et auth en Python.

Pour des exemples en fonctionnement, voir les exemples d’auto-connexion et au nom de pour :

Prise en charge linguistique pour OAuth

Le kit de développement logiciel des assistants prend en charge OAuth pour .NET, JavaScript et Python. Les concepts fondamentaux sont les mêmes dans tous les langages : gestionnaires OAuth, attachement de gestionnaires aux itinéraires, et échange au nom de. Seuls le format de configuration et les noms des API des gestionnaires diffèrent :

Langage Où configurer Surface d’API
.NET appsettings.json (ou code dans Program.cs) AgentApplication.UserAuthorization
JavaScript .env variables d’environnements AgentApplication.authorization
Python Variables d’environnement .env AgentApplication.auth

Pour JavaScript et Python, les clés .env utilisent les mêmes noms hiérarchiques que la structure .NET appsettings.json, chaque niveau étant séparé par un double soulignement (__). Les clés JavaScript conservent les noms terminaux utilisant la casse Camel-Case affichés dans les tables (par exemple, azureBotOAuthConnectionName). Les clés Python sont en majuscules (par exemple, AZUREBOTOAUTHCONNECTIONNAME).

Important

La connexion automatique globale (AutoSignIn) et DefaultHandlerName sont prises en charge uniquement en .NET. Dans JavaScript et Python, vous associez des gestionnaires OAuth à des routes spécifiques, comme montré dans Configuration par route.

Configurations

Un objet d’autorisation utilisateur contenu dans AgentApplication détermine comment l’assistant acquiert les jetons utilisateur. Au minimum, chaque gestionnaire nomme la connexion Azure Bot OAuth qu’il utilise. Les exemples suivants montrent la structure minimale de chaque langue. Les tables qui suivent décrivent le reste des propriétés disponibles, et les sections au nom de couvrent les paramètres OBOConnectionName et OBOScopes.

Dans .NET, configurez l’autorisation utilisateur sous AgentApplication dans appsettings.json :

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

En JavaScript, configurez l’autorisation utilisateur avec des variables d’environnement dans le fichier .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}}

En Python, configurez l’autorisation utilisateur avec des variables d’environnement dans le fichier .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}}

Propriétés UserAuthorization

Le tableau suivant répertorie les propriétés de premier niveau de UserAuthorization qui déterminent comment les gestionnaires sont sélectionnés et comment les jetons sont acquis pour chaque activité entrante.

Propriété Obligatoire Type Description
DefaultHandlerName Non (recommandé) chaine .NET uniquement. Nom du gestionnaire utilisé quand AutoSignIn prend la valeur true et qu’aucun remplacement par routage n’est spécifié.
AutoSignIn Non bool ou délégué .NET uniquement. Lorsqu’il est vrai (par défaut), l’assistant tente d’acquérir un jeton pour chaque activité entrante. Remplacez par Options.AutoSignIn au moment du runtime pour filtrer les types d’activité.
Handlers Oui (au moins un) objet (dictionnaire) Correspondance du nom du gestionnaire à sa configuration. Chaque clé doit être unique.

Dans .NET, pour restreindre les activités auxquelles s’applique la connexion automatique, définissez un prédicat similaire à : Options.AutoSignIn = (context, ct) => Task.FromResult(context.Activity.IsType(ActivityTypes.Message));. JavaScript et Python ne supportent pas la connexion automatique globale. À la place, limitez la connexion en associant des noms spécifiques de gestionnaires à des routes individuelles, comme montré dans les exemples par route .

Propriétés des paramètres

Le tableau suivant décrit l’objet Settings imbriqué appliqué à un gestionnaire OAuth individuel, contrôlant la présentation de la carte d’accession, le comportement de nouvelle tentative, les délais d’attente et la configuration optionnelle de l’échange OBO.

Propriété Obligatoire Type Description
AzureBotOAuthConnectionName Oui chaine Nom de connexion OAuth défini sur la ressource Azure Bot.
OBOConnectionName Non (OBO uniquement) chaine Nom d’une connexion du Kit de développement logiciel (SDK) Assistants utilisée pour effectuer un échange de jetons on-Behalf-Of.
OBOScopes Non (OBO uniquement) string[] Étendues demandées lors de l’échange OBO. S’il est omis avec OBOConnectionName, vous pouvez appeler ExchangeTurnTokenAsync manuellement.
Title Non chaine Titre de la carte de connexion personnalisée. Valeur par défaut définie sur Se connecter.
Text Non chaine Texte du bouton de la carte de connexion. Valeur par défaut définie sur Veuillez vous connecter.
InvalidSignInRetryMax Non int Nombre maximal de tentatives autorisées lorsque l’utilisateur saisit un code invalide. La valeur par défaut est 2.
InvalidSignInRetryMessage Non chaine Message affiché après une entrée de code non valide. Par défaut , le code de connexion est non valide. Veuillez entrer le code à 6 chiffres.
Timeout Non int (ms) Nombre de millisecondes avant l’expiration d’une tentative de connexion en cours. La durée par défaut est de 900 000 (15 minutes).

Note

AzureBotOAuthConnectionName, OBOConnectionName, OBOScopes, Title, et Text appliquez les trois langages (à l’aide de la casse principale par langue décrite dans Prise en charge linguistique pour OAuth). InvalidSignInRetryMax, InvalidSignInRetryMessage et Timeout sont les paramètres .NET.

Quel type devez-vous utiliser ?

Utilisez le tableau suivant pour décider quelle approche convient à votre situation.

Option Cas d’utilisation
Connexion automatique (.NET uniquement) Vous souhaitez que chaque activité entrante acquière automatiquement un jeton, ou vous souhaitez un sous-ensemble filtré (par exemple, uniquement les messages ou tout sauf les événements) en fournissant un prédicat à UserAuthorizationOptions.AutoSignIn. Pris en charge uniquement en .NET.
Par route Seuls des gestionnaires de routes spécifiques ont besoin de jetons, ou des routes différentes doivent utiliser des connexions OAuth différentes (et donc des jetons différents). Cette option est la seule en JavaScript et Python. En .NET, ceci est cumulatif avec la connexion automatique globale. Si les deux sont activés en .NET, l’activité a accès aux jetons de chacune.

Utiliser le jeton dans le code (non-OBO)

Cette section explique comment récupérer et utiliser le jeton utilisateur retourné directement par votre connexion OAuth du bot Azure sans effectuer un échange d’authentification déléguée. En .NET, vous pouvez utiliser la connexion automatique globale ou des gestionnaires par route. JavaScript et Python utilisent uniquement des gestionnaires par route. Dans votre gestionnaire d’activité, récupérez le jeton (GetTurnTokenAsync en .NET, authorization.getToken en JavaScript, auth.get_token en Python) le plus tard possible afin que le kit de développement logiciel puisse actualiser le jeton s’il est proche de l’expiration. Les exemples suivants illustrent les deux approches.

Connexion automatique (.NET uniquement)

Note

La connexion automatique globale et DefaultHandlerName sont disponibles uniquement en .NET. Pour JavaScript et Python, utilisez la configuration par route.

Utilisez cette configuration lorsque la connexion automatique globale doit acquérir un jeton pour chaque activité entrante sans avoir à spécifier de gestionnaires par routage.

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

Votre code d’assistant ressemblerait à ceci :

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

        // use the token 
    }
}

Configuration par route

Utilisez une configuration par route lorsque vous souhaitez un contrôle précis : seules les routes que vous marquez explicitement acquièrent des jetons. La configuration par route présente les avantages suivants :

  • Cela réduit la récupération inutile de jetons.
  • Il permet à différentes routes de cibler des connexions OAuth distinctes (et donc des ressources ou des périmètres différents).
  • Cela permet de mélanger des routes authentifiées et non authentifiées au sein d’un même assistant.

Dans l’exemple suivant, un seul graph gestionnaire est attaché uniquement à la route du message.

Dans .NET, la connexion automatique globale est désactivée et le gestionnaire graph est attaché à la route en utilisant autoSignInHandlers.

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

Votre code d’assistant ressemblerait à ceci :

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
    }
}

En JavaScript, passez une table de noms de gestionnaires comme dernier argument à l’enregistrement de la route. Seul cette route déclenche la connexion pour le gestionnaire 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

Votre code d’assistant ressemblerait à ceci :

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
  }
}

En Python, passez auth_handlers au décorateur de route. Seul cette route déclenche la connexion pour le gestionnaire GRAPH.

AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__GRAPH__SETTINGS__AZUREBOTOAUTHCONNECTIONNAME=teams_sso

Votre code d’assistant ressemblerait à ceci :

@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

Récupérer le jeton pendant un tour

Récupérez le jeton utilisateur chaque fois que vous en avez besoin pendant un tour. Vous pouvez l’appeler plusieurs fois. Appelez-la immédiatement avant utilisation afin que la logique de rafraîchissement (si nécessaire) soit gérée de manière transparente.

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

Utiliser le jeton dans le code (OBO)

On-Behalf-Of (OBO) repose sur le retour initial de la connexion utilisateur d’un jeton échangeable. Cela nécessite que les scopes de la connexion OAuth incluent un scope correspondant à un scope exposé par l’API en aval (par exemple, si le scope exposé est defaultScopes, le scope configuré pourrait être api://botid-{{clientId}}/defaultScopes). Le SDK Agents effectue alors un échange via la Microsoft Authentication Library (MSAL) en utilisant une connexion configurée identifiée par OBOConnectionName et la liste de OBOScopes. Lorsque OBOConnectionName et OBOScopes sont présents en configuration, l’échange se produit automatiquement et vous obtenez le jeton final via l’appel de jeton standard (GetTurnTokenAsync / getToken / get_token). Si l’un des deux est manquant, vous pouvez effectuer l’échange explicitement pendant l’exécution (ExchangeTurnTokenAsync en .NET, authorization.exchangeToken en JavaScript, auth.exchange_token en Python), ce qui vous permet de déterminer dynamiquement la connexion ou la liste de portées.

OBO dans la configuration

Utilisez ce modèle lorsque vous connaissez la ressource en aval et les portées nécessaires au moment de la configuration. Lorsque vous fournissez à la fois OBOConnectionName et OBOScopes, le Kit de développement logiciel (SDK) effectue automatiquement l’échange On-Behalf-Of pendant la connexion. Cela signifie que les appels suivants au jeton standard retournent directement le jeton OBO, sans qu’aucun code supplémentaire à l’exécution ne soit nécessaire.

  "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"
        ]
      }
    }
  },

Votre code d’assistant ressemblerait à ceci :

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
    }
}

En JavaScript, définissez une connexion au nom de dans la carte des connexions et référencez-la depuis le gestionnaire avec oboConnectionName et 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

Votre code d’assistant ressemblerait à ceci :

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
  }
}

En Python, définissez une connexion au nom de sous CONNECTIONS et référencez-la depuis le gestionnaire avec OBOCONNECTIONNAME et 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

Votre code d’assistant ressemblerait à ceci :

@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 au moment de l’exécution

Utilisez un échange à l’exécution lorsque vous ne pouvez pas corriger la ressource en aval, les étendues ou la connexion dans la configuration. Cette situation se produit, par exemple, lorsque les périmètres dépendent du client, du rôle utilisateur ou d’un indicateur de fonctionnalité. Dans ce modèle, vous configurez optionnellement la connexion au nom de, puis appelez la méthode d’échange avec les étendues que vous décidez au moment du tour. Vous recevez un jeton échangé que vous pouvez immédiatement appliquer.

Appelez ExchangeTurnTokenAsync avec les étendues que vous sélectionnez au tour.

  "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"
        ]
      }
    }
  },

Votre code d’assistant ressemblerait à ceci :

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
    }
}

Appelez authorization.exchangeToken avec le nom du gestionnaire et les étendues que vous sélectionnez au tour.

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

Votre code d’assistant ressemblerait à ceci :

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
  }
}

Appelez auth.exchange_token avec les étendues que vous sélectionnez au tour et le nom du gestionnaire.

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

Votre code d’assistant ressemblerait à ceci :

@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

Paramètres régionaux OAuth

Pour les régions hors États-Unis, mettez à jour le point de terminaison du service de jeton utilisé par votre assistant.

L’exemple suivant montre la configuration .NET. Ajoutez-le à appsettings.json :

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

Pour service-endpoint-url, utilisez la valeur appropriée du tableau suivant pour les bots de cloud public dont les données résident dans la région spécifiée.

URI Région
https://europe.api.botframework.com Europe
https://unitedstates.api.botframework.com États-Unis
https://india.api.botframework.com Inde