ID プロバイダーのプロキシ

このドキュメントでは、OAuth2 プロトコルを使用するカスタムまたは高度な ID プロバイダーと対話するプロキシを作成する方法について説明します。

Bot Framework を使用すると、ユーザーは OAuth2 プロトコルを使用するさまざまな ID プロバイダーを使用してログインできます。 ただし、ID プロバイダーは、より高度な機能や代替サインイン オプションを提供することで、コア OAuth2 プロトコルから逸脱する可能性があります。 このような場合は、適切な 接続設定の構成 が見つからない場合があります。 考えられる解決策は、次の操作を行うことです。

  1. Bot Framework トークン サービスと、よりカスタマイズされた、または高度な ID プロバイダーの間にある OAuth2 プロバイダー プロキシを作成します。
  2. このプロキシを呼び出すように接続設定を構成し、このプロキシがカスタム ID プロバイダーまたは高度な ID プロバイダーを呼び出すようにします。 プロキシは、応答をマップまたは変換して、Bot Framework トークン サービスが期待する内容に準拠させることもできます。

OAuth2 プロキシ サービス

OAuth2 プロキシ サービスを構築するには、承認用とトークン取得用の 2 つの OAuth2 API を使用して REST サービスを実装する必要があります。 以下に、これらの各メソッドの C# の例と、カスタムまたは高度な ID プロバイダーを呼び出すためにこれらのメソッドで実行できる操作を示します。

API を承認する

authorize API は、呼び出し元を承認し、コード プロパティを生成し、リダイレクト URI にリダイレクトする HTTP GET です。

[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

トークン API は、Bot Framework トークン サービスによって呼び出される HTTP POST です。 Bot Framework トークン サービスは、 と client_secretclient_id要求の本文で送信します。 これらの値は、検証するか、カスタムまたは高度な ID プロバイダーに渡す必要があります。 この呼び出しに対する応答は、トークンの access_token と 有効期限の値を含む JSON オブジェクトです (他のすべての値は無視されます)。 ID プロバイダーが、代わりに返す またはその他の値を返 id_token す場合は、戻る前に応答の プロパティに access_token マップするだけで済みます。

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

プロキシ接続設定の構成

OAuth2 プロキシ サービスを実行したら、Azure AI Bot Service リソースに OAuth サービス プロバイダー接続設定を作成できます。 以下で説明する手順に従います。

  1. 接続設定に名前を付けます。
  2. 汎用 Oauth 2 サービス プロバイダーを選択します。
  3. 接続の [クライアント ID][クライアント シークレット ] を入力します。 これらの値は、高度な ID プロバイダーまたはカスタム ID プロバイダーによって提供される場合があります。または、使用している ID プロバイダーがクライアント ID とシークレットを使用しない場合は、プロキシのみに固有である可能性があります。
  4. 承認 URL の場合は、承認 REST API のアドレス (例: https://proxy.com/api/oauth/authorize) をコピーする必要があります。
  5. [トークンと更新 URL] には、トークン REST API のアドレス (例: https://proxy.com/api/oauth/token) をコピーする必要があります。 トークン交換 URL は AAD ベースのプロバイダーに対してのみ有効であるため、無視できます。
  6. 最後に、適切なスコープを追加します。

ASP.NET Web アプリの OAuthController

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