Identitetsproviderproxy

Det här dokumentet beskriver hur du skapar en proxy för att interagera med anpassade eller avancerade identitetsprovidrar som använder OAuth2-protokollet.

Med Bot Framework kan användare logga in med hjälp av olika identitetsprovidrar som använder OAuth2-protokollet. Identitetsprovidrar kan dock avvika från OAuth2-kärnprotokollet genom att erbjuda mer avancerade funktioner eller alternativa inloggningsalternativ. I dessa fall kanske du inte hittar någon lämplig konfiguration för anslutningsinställningar som fungerar för dig. En möjlig lösning är att göra följande:

  1. Skriv en OAuth2-providerproxy som finns mellan Bot Framework-tokentjänsten och den mer anpassade eller avancerade identitetsprovidern .
  2. Konfigurera anslutningsinställningen för att anropa den här proxyn och låt proxyn göra anropen till den anpassade eller avancerade identitetsprovidern. Proxyn kan också mappa eller transformera svar så att de överensstämmer med vad Bot Framework-tokentjänsten förväntar sig.

OAuth2 Proxy Service

Om du vill skapa en OAuth2-proxytjänst måste du implementera en REST-tjänst med två OAuth2-API:er: en för auktorisering och en för att hämta en token. Nedan hittar du ett C#-exempel på var och en av dessa metoder och vad du kan göra i dessa metoder för att anropa en anpassad eller avancerad identitetsprovider.

Auktorisera API

Auktorisera API:et är en HTTP GET som auktoriserar anroparen, genererar en kodegenskap och omdirigerar till omdirigerings-URI:n.

[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}");
}

Token-API

Token-API:et är en HTTP POST som anropas av Bot Framework-tokentjänsten. Bot Framework-tokentjänsten skickar client_id och client_secret i begärans brödtext. Dessa värden ska verifieras och/eller skickas till den anpassade eller avancerade identitetsprovidern. Svaret på det här anropet är ett JSON-objekt som innehåller access_token och förfallovärdet för token (alla andra värden ignoreras). Om din identitetsprovider returnerar ett id_token eller något annat värde som du vill returnera i stället behöver du bara mappa det till egenskapen för access_token ditt svar innan du returnerar.

[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'");
    }
}

Konfiguration av proxyanslutningsinställning

När du har din OAuth2-proxytjänst igång kan du skapa en anslutningsinställning för OAuth-tjänstprovidern på din Azure AI-Bot Service resurs. Följ stegen som beskrivs nedan.

  1. Ge anslutningsinställningen ett namn.
  2. Välj den allmänna Oauth 2-tjänstleverantören .
  3. Ange ett klient-ID och en klienthemlighet för anslutningen. Dessa värden kan tillhandahållas av din avancerade eller anpassade identitetsprovider, eller så kan de vara specifika bara för proxyn om identitetsprovidern du använder inte använder klient-ID och hemlighet.
  4. För auktoriserings-URL:en bör du kopiera adressen till rest-API:et för auktorisering, till exempel https://proxy.com/api/oauth/authorize.
  5. För token- och uppdaterings-URL:en bör du kopiera adressen till din token REST API, till exempel https://proxy.com/api/oauth/token. Token Exchange-URL:en är endast giltig för AAD-baserade providers och kan därför ignoreras.
  6. Lägg slutligen till eventuella omfång som är lämpliga.

OAuthController för ASP.NET webbapp

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