Proxy dos provedores de identidade

Este documento explica como criar um proxy para interagir com provedores de identidade personalizados ou avançados que usam o protocolo OAuth2.

O Bot Framework permite que os usuários façam logon usando vários provedores de identidade que usam o protocolo OAuth2. No entanto, os provedores de identidade podem desviar-se do protocolo OAuth2 principal, oferecendo recursos mais avançados ou opções alternativas de entrada. Nesses casos, talvez você não encontre uma configuração de configuração de conexão apropriada que funcione para você. Uma possível solução é fazer o seguinte:

  1. Escreva um proxy de provedor OAuth2 que esteja entre o serviço de token do Bot Framework e o provedor de identidade mais personalizado ou avançado .
  2. Defina a configuração de conexão para chamar esse proxy e faça com que esse proxy faça as chamadas para o provedor de identidade personalizado ou avançado. O proxy também pode mapear ou transformar respostas para torná-las em conformidade com o que o serviço de token do Bot Framework espera.

Serviço proxy OAuth2

Para criar um Serviço de Proxy OAuth2, você precisa implementar um serviço REST com duas APIs OAuth2: uma para autorização e outra para recuperar um token. Abaixo, você encontrará um exemplo em C# de cada um desses métodos e o que você pode fazer nesses métodos para chamar um provedor de identidade personalizado ou avançado.

Autorizar API

A API de autorização é um HTTP GET que autoriza o chamador, gera uma propriedade de código e redireciona para o URI de redirecionamento.

[HttpGet("authorize")]
public ActionResult Authorize(
    string response_type, 
    string client_id, 
    string state, 
    string redirect_uri, 
    string scope = null)
{
    // validate parameters
    if (string.IsNullOrEmpty(state))
    {
        return BadRequest("Authorize request missing parameter 'state'");
    }

    if (string.IsNullOrEmpty(redirect_uri))
    {
        return BadRequest("Authorize request missing parameter 'redirect_uri'");
    }

    // redirect to an external identity provider, 
    // or for this sample, generate a code and token pair and redirect to the redirect_uri

    var code = Guid.NewGuid().ToString("n");
    var token = Guid.NewGuid().ToString("n");
    _tokens.AddOrUpdate(code, token, (c, t) => token);

    return Redirect($"{redirect_uri}?code={code}&state={state}");
}

API de Token

A API de Token é um HTTP POST chamado pelo serviço de token do Bot Framework. O serviço de token do Bot Framework enviará o client_id e client_secret no corpo da solicitação. Esses valores devem ser validados e/ou passados para o provedor de identidade personalizado ou avançado. A resposta a essa chamada é um objeto JSON que contém o access_token valor de expiração e do token (todos os outros valores são ignorados). Se o provedor de identidade retornar um id_token ou algum outro valor que você deseja retornar, basta mapeá-lo para a access_token propriedade de sua resposta antes de retornar.

[HttpPost("token")]
public async Task<ActionResult> Token()
{
    string body;

    using (var reader = new StreamReader(Request.Body))
    {
        body = await reader.ReadToEndAsync();
    }

    if (string.IsNullOrEmpty(body))
    {
        return BadRequest("Token request missing body");
    }

    var parameters = HttpUtility.ParseQueryString(body);
    string authorizationCode = parameters["code"];
    string grantType = parameters["grant_type"];
    string clientId = parameters["client_id"];
    string clientSecret = parameters["client_secret"];
    string redirectUri= parameters["redirect_uri"];

    // Validate any of these parameters here, or call out to an external identity provider with them

    if (_tokens.TryRemove(authorizationCode, out string token))
    {
        return Ok(new TokenResponse()
        {
            AccessToken = token,
            ExpiresIn = 3600,
            TokenType = "custom",
        });
    }
    else
    {
        return BadRequest("Token request body did not contain parameter 'code'");
    }
}

Configuração da configuração de conexão de proxy

Depois de executar o Serviço de Proxy OAuth2, você poderá criar uma Configuração de Conexão do Provedor de Serviços OAuth no recurso Serviço de Bot de IA do Azure. Siga as etapas descritas abaixo.

  1. Dê um nome à configuração de conexão.
  2. Selecione o provedor de serviços Genérico Oauth 2 .
  3. Insira uma ID do cliente e um segredo do cliente para a conexão. Esses valores podem ser fornecidos pelo provedor de identidade avançado ou personalizado, ou podem ser específicos apenas para o proxy se o provedor de identidade que você está usando não usar a ID e o segredo do cliente.
  4. Para a URL de Autorização, você deve copiar o endereço da API REST de autorização, por exemplo https://proxy.com/api/oauth/authorize.
  5. Para a URL de Token e Atualização, você deve copiar o endereço da API REST do token, por exemplo https://proxy.com/api/oauth/token. A URL do Token Exchange é válida apenas para provedores baseados em AAD e, portanto, pode ser ignorada.
  6. Por fim, adicione todos os escopos apropriados.

OAuthController para ASP.NET aplicativo Web

using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading.Tasks;
using System.Web;

namespace CustomOAuthProvider.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class OAuthController : ControllerBase
    {
        ConcurrentDictionary<string, string> _tokens;

        public OAuthController(ConcurrentDictionary<string, string> tokens)
        {
            _tokens = tokens;
        }

        [HttpGet("authorize")]
        public ActionResult Authorize(
            string response_type, 
            string client_id, 
            string state, 
            string redirect_uri, 
            string scope = null)
        {
            if (string.IsNullOrEmpty(state))
            {
                return BadRequest("Authorize request missing parameter 'state'");
            }

            if (string.IsNullOrEmpty(redirect_uri))
            {
                return BadRequest("Authorize request missing parameter 'redirect_uri'");
            }

            // reidrect to an external identity provider, 
            // or for this sample, generte a code and token pair and redirect to the redirect_uri

            var code = Guid.NewGuid().ToString("n");
            var token = Guid.NewGuid().ToString("n");
            _tokens.AddOrUpdate(code, token, (c, t) => token);

            return Redirect($"{redirect_uri}?code={code}&state={state}");
        }

        [HttpPost("token")]
        public async Task<ActionResult> Token()
        {
            string body;

            using (var reader = new StreamReader(Request.Body))
            {
                body = await reader.ReadToEndAsync();
            }

            if (string.IsNullOrEmpty(body))
            {
                return BadRequest("Token request missing body");
            }

            var parameters = HttpUtility.ParseQueryString(body);
            string authorizationCode = parameters["code"];
            string grantType = parameters["grant_type"];
            string clientId = parameters["client_id"];
            string clientSecret = parameters["client_secret"];
            string redirectUri= parameters["redirect_uri"];

            // Validate any of these parameters here, or call out to an external identity provider with them

            if (_tokens.TryRemove(authorizationCode, out string token))
            {
                return Ok(new TokenResponse()
                {
                    AccessToken = token,
                    ExpiresIn = 3600,
                    TokenType = "custom",
                });
            }
            else
            {
                return BadRequest("Token request body did not contain parameter 'code'");
            }
        }
    }

    public class TokenResponse
    {
        [JsonProperty("access_token")]
        public string AccessToken { get; set; }

        [JsonProperty("id_token")]
        public string IdToken { get; set; }

        [JsonProperty("token_type")]
        public string TokenType { get; set; }

        [JsonProperty("expires_in")]
        public int ExpiresIn { get; set; }

        [JsonProperty("refresh_token")]
        public string RefreshToken { get; set; }

        [JsonProperty("scope")]
        public string Scope { get; set; }
    }
}