Configurar o agente para usar OAuth

Um agente usa OAuth para fazer login de usuários e obter tokens para recursos posteriores (como o Microsoft Graph) sem lidar com as próprias credenciais. O Serviço de Bot do Azure gerencia a troca do token, e o agente recupera o token de usuário resultante durante uma rodada.

Visão geral

O uso de OAuth em um agente envolve três atividades:

  1. Configurar OAuth no Azure Bot e no registro do aplicativo: crie uma ou mais conexões OAuth no recurso do Azure Bot, cada uma com o apoio de um registro de aplicativo do Microsoft Entra ID. Adicionar autorização do usuário usando credencial de identidade federada abrange a abordagem mais comum. Outros tipos de credencial, como segredos de cliente ou certificados, também são compatíveis. Para o conjunto de opções completo, consulte Noções básicas de autenticação do Serviço de Bot.
  2. Definir as configurações correspondentes no agente: cada conexão OAuth no Azure Bot é mapeada para um manipulador OAuth na configuração do agente. Consulte Configurações. Para configuração do agente geral, consulte O que é o SDK de Agentes do Microsoft 365.
  3. Usar os tokens em código: durante uma rodada, recupere o token de usuário, ou realize uma troca Em nome de (OBO), por meio da API de autorização do agente. Consulte Usar o token em código (não OBO) e Usar o token em código (OBO).

Tenha em mente os seguintes conceitos enquanto lê o restante deste artigo:

  • Um bot do Azure pode conter várias conexões OAuth. Por exemplo, uma conexão para o Microsoft Graph e outra para o GitHub. Cada conexão é configurada independentemente no Bot do Azure.
  • Existe uma relação 1:1 entre uma conexão OAuth no Bot do Azure e um manipulador OAuth no agente. A configuração AzureBotOAuthConnectionName do manipulador nomeia a conexão do Bot do Azure usada por ela. Para usar duas conexões, defina dois manipuladores.
  • A API de autorização do usuário do agente é a superfície que você chama no código. No .NET, é AgentApplication.UserAuthorization, por exemplo, GetTurnTokenAsync para ler um token e ExchangeTurnTokenAsync para realizar uma troca OBO. As superfícies equivalentes são authorization em JavaScript e auth em Python.

Para amostras em funcionamento, consulte as credenciais automáticas e as amostras OBO para:

Suporte a linguagens para OAuth

O SDK de Agentes dá suporte a OAuth para .NET, JavaScript e Python. Os conceitos básicos são os mesmos em todas as linguagens: manipuladores OAuth, anexação de manipuladores a rotas e troca OBO. Somente o formato da configuração e os nomes de API do manipulador diferem:

Linguagem Onde você configura Superfície da API
.NET appsettings.json (ou código em Program.cs) AgentApplication.UserAuthorization
JavaScript Variáveis de ambiente .env AgentApplication.authorization
Python Variáveis de ambiente .env AgentApplication.auth

Para JavaScript e Python, as chaves .env usam os mesmos nomes hierárquicos da estrutura appsettings.json .NET, com cada nível separado por um sublinhado duplo (__). As chaves JavaScript mantêm os nomes folha em maiúsculas e minúsculas mostrados nas tabelas (por exemplo, azureBotOAuthConnectionName). As chaves Python estão em maiúsculas (por exemplo, AZUREBOTOAUTHCONNECTIONNAME).

Importante

Credenciais automáticas globais (AutoSignIn) e DefaultHandlerName só são compatíveis em .NET. Em JavaScript e Python, você anexa manipuladores OAuth a rotas específicas, conforme mostrado em Configuração por rota.

Configurações

Um objeto de autorização do usuário dentro de AgentApplication controla como o agente adquire tokens de usuário. No mínimo, cada manipulador nomeia a conexão OAuth de Bot do Azure usada. Os exemplos a seguir mostram a estrutura mínima em cada linguagem. As tabelas a seguir descrevem o restante das propriedades disponíveis, e as seções OBO abrangem as configurações OBOConnectionName e OBOScopes.

Em .NET, configure a autorização do usuário em AgentApplication em appsettings.json:

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

Em JavaScript, configure a autorização do usuário com variáveis de ambiente no arquivo .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}}

Em Python, configure a autorização do usuário com variáveis de ambiente no arquivo .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}}

Propriedades de UserAuthorization

A tabela a seguir lista as propriedades de nível superior de UserAuthorization que determinam como os manipuladores são selecionados e como os tokens são adquiridos para cada atividade recebida.

Propriedade Obrigatório Tipo descrição
DefaultHandlerName Não (recomendado) string (cadeia de caracteres) Somente .NET. Nome do manipulador usado quando AutoSignIn for avaliado como verdadeiro e nenhuma substituição por rota for especificada.
AutoSignIn Não bool ou delegado Somente .NET. Quando verdadeiro (padrão), o agente tenta obter um token para cada atividade recebida. Substitua no runtime com Options.AutoSignIn para filtrar tipos de atividade.
Handlers Sim (pelo menos um) objeto (dicionário) Mapeamento do nome do manipulador para sua configuração. Cada chave precisa ser exclusiva.

Em .NET, para restringir a quais atividades as credenciais automáticas se aplicam, defina um predicado semelhante a: Options.AutoSignIn = (context, ct) => Task.FromResult(context.Activity.IsType(ActivityTypes.Message));. JavaScript e Python não dão suporte a credenciais automáticas globais. Em vez disso, defina as credenciais de escopo anexando nomes de manipulador específicos a rotas individuais, conforme mostrado nos exemplos por rota.

Propriedades de configurações

A tabela a seguir descreve o objeto aninhado Settings aplicado a um manipulador OAuth individual, controlando a apresentação de cartão de entrada, o comportamento de repetição, os tempos limite e a configuração opcional da troca do OBO.

Propriedade Obrigatório Tipo Descrição
AzureBotOAuthConnectionName Sim string (cadeia de caracteres) Nome da conexão OAuth definido no recurso do Bot do Azure.
OBOConnectionName Não (somente OBO) string (cadeia de caracteres) Nome de uma conexão do SDK de Agentes usada para executar uma troca de tokens em nome de outra parte.
OBOScopes Não (somente OBO) string[] Escopos solicitados durante a troca de OBO. Se omitido com OBOConnectionName, você poderá chamar ExchangeTurnTokenAsync manualmente.
Title Não string (cadeia de caracteres) Título do cartão de entrada personalizado. O padrão é Entrada.
Text Não string (cadeia de caracteres) Texto do botão do cartão de entrada. O padrão é Entre.
InvalidSignInRetryMax Não int Número máximo de tentativas permitidas quando o usuário insere um código inválido. O padrão é 2.
InvalidSignInRetryMessage Não string (cadeia de caracteres) Mensagem mostrada após uma entrada de código inválida. O padrão é Código de entrada inválido. Digite o código de 6 dígitos.
Timeout Não int (ms) Número de milissegundos antes de uma tentativa de entrada em andamento expirar. O padrão é 900000 (15 minutos).

Observação

AzureBotOAuthConnectionName, OBOConnectionName, OBOScopes, Title e Text se aplicam a todas as três linguagens (usando maiúsculas e minúsculas da chave por linguagem descritas em Suporte da linguagem para OAuth). InvalidSignInRetryMax, InvalidSignInRetryMessage e Timeout são configurações do .NET.

Qual tipo você deve usar?

Consulte a tabela a seguir para decidir qual abordagem é mais apropriada para o seu cenário.

Opção Use quando
Credenciais automáticas (somente .NET) Você deseja que toda atividade recebida adquira automaticamente um token, ou deseja um subconjunto filtrado (por exemplo, somente mensagens ou tudo, exceto eventos) fornecendo um predicado para UserAuthorizationOptions.AutoSignIn. Só compatível em .NET.
Por rota Somente manipuladores de rotas específicos precisam de tokens ou diferentes rotas devem usar diferentes conexões OAuth (e, portanto, diferentes tokens). Esta opção é a única em JavaScript e Python. No .NET, é aditivo com credenciais automáticas globais. Se ambos estiverem habilitados em .NET, a rodada terá acesso aos tokens de cada conexão.

Usar o token no código (não OBO)

Esta seção mostra como recuperar e usar o token de usuário retornado diretamente pela sua conexão OAuth do Bot do Azure, sem realizar uma troca OBO (On-Behalf-Of). Em .NET, você pode usar credenciais automáticas globais ou por manipuladores de rotas. JavaScript e Python usam apenas manipuladores de rotas. Dentro do manipulador de atividade, recupere o token (GetTurnTokenAsync em .NET, authorization.getToken em JavaScript, auth.get_token em Python) o mais tarde possível, de maneira que o SDK possa atualizar o token se ele estiver próximo do vencimento. Os exemplos a seguir ilustram ambos os padrões.

Credenciais automáticas (somente .NET)

Observação

Credenciais automáticas globais e DefaultHandlerName só estão disponíveis em .NET. Para JavaScript e Python, use Configuração por rota.

Use essa configuração quando a entrada automática global deve adquirir um token para cada atividade recebida sem precisar especificar manipuladores por rota.

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

O código do agente seria semelhante a este:

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

        // use the token 
    }
}

Configuração por rota

Use uma configuração por rota quando você quiser controle granular: somente as rotas que você definir explicitamente adquirem tokens. A configuração por rota tem as seguintes vantagens:

  • Isso reduz a recuperação de token desnecessária.
  • Ele permite que rotas diferentes segmentem conexões OAuth distintas (e, por isso, recursos ou escopos diferentes).
  • Ele permite a você misturar rotas autenticadas e não autenticadas dentro do mesmo agente.

No exemplo a seguir, um único manipulador graph só é anexado à rota da mensagem.

Em .NET, as credenciais automáticas globais são desabilitadas, e o manipulador graph é anexado à rota usando autoSignInHandlers.

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

O código do agente seria semelhante a este:

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

Em JavaScript, passe um array de nomes de manipulador como último argumento para o registro da rota. Somente essa rota dispara credenciais para o manipulador 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

O código do agente seria semelhante a este:

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

Em Python, passe auth_handlers para o decorador da rota. Somente essa rota dispara credenciais para o manipulador GRAPH.

AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__GRAPH__SETTINGS__AZUREBOTOAUTHCONNECTIONNAME=teams_sso

O código do agente seria semelhante a este:

@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

Recuperar o token durante uma rodada

Recupere o token do usuário sempre que você precisar durante uma rodada. Você pode chamá-lo várias vezes. Chame-o imediatamente antes do uso para que a lógica de atualização (se necessário) seja tratada de forma transparente.

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

Usar o token no código (não OBO)

OBO (On-Behalf-Of) depende da entrada inicial do usuário fornecer um token que possa ser trocado. Isso requer que os escopos da conexão OAuth incluam um que corresponda a um escopo exposto pela API downstream (por exemplo, se o escopo exposto for defaultScopes, o escopo configurado pode ser api://botid-{{clientId}}/defaultScopes). Em seguida, o SDK de Agentes executa uma troca da MSAL (Biblioteca de Autenticação da Microsoft) usando uma conexão configurada identificada por OBOConnectionName e a lista de OBOScopes. Quando OBOConnectionName e OBOScopes estão presentes na configuração, a troca ocorre automaticamente, e você obtém o token final por meio da chamada do token padrão (GetTurnTokenAsync / getToken / get_token). Se qualquer um não for encontrado, você poderá realizar explicitamente a troca em runtime (ExchangeTurnTokenAsync em .NET, authorization.exchangeToken em JavaScript, auth.exchange_token em Python), permitindo a você resolver dinamicamente a conexão ou a lista de escopo.

OBO na configuração

Use esse padrão quando souber o recurso downstream e os escopos necessários no momento da configuração. Quando você fornece ambos OBOConnectionName e OBOScopes, o SDK executa automaticamente a troca Em nome de durante a entrada. Isso significa que chamadas subsequentes para o token padrão getter retornam diretamente o token OBO, sem necessidade de código de runtime extra.

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

O código do agente seria semelhante a este:

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

Em JavaScript, defina uma conexão OBO no mapa de conexões e a referencie pelo manipulador com 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

O código do agente seria semelhante a este:

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

Em Python, defina uma conexão OBO em CONNECTIONS e a referencie pelo manipulador com 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

O código do agente seria semelhante a este:

@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

Troca de OBO no runtime

Use uma troca de runtime quando você não conseguir corrigir o recurso downstream, os escopos ou a conexão na configuração. Essa situação acontece, por exemplo, quando os escopos dependem do locatário, da função do usuário ou de um sinalizador de recurso. Neste modelo, você pode configurar a conexão OBO e, em seguida, chamar o método de troca com os escopos que você decidir no momento da rodada. Você recebe um token trocado que pode aplicar imediatamente.

Chame ExchangeTurnTokenAsync com os escopos que você decidir no momento da rodada.

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

O código do agente seria semelhante a este:

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

Chame authorization.exchangeToken com o nome e os escopos do manipulador que você decidir no momento da rodada.

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

O código do agente seria semelhante a este:

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

Chame auth.exchange_token com o nome e os escopos do manipulador que você decidir no momento da rodada.

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

O código do agente seria semelhante a este:

@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

Configurações de OAuth regionais

Para regiões fora dos Estados Unidos, atualize o ponto de extremidade do serviço de token que seu agente usa.

A imagem a seguir mostra a configuração do .NET. Adicione-a ao appsettings.json:

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

Para service-endpoint-url, use o valor apropriado da tabela a seguir para bots de nuvem pública com residência de dados na região especificada.

URI Região
https://europe.api.botframework.com Europa
https://unitedstates.api.botframework.com Estados Unidos
https://india.api.botframework.com Índia