エージェントを構成して OAuth を使用する

エージェントはOAuthを使ってユーザーにサインインし、認証情報を直接処理せずに下流リソース (たとえばMicrosoft Graph) のトークンを取得します。 Azure Bot Serviceはトークン交換を管理し、エージェントはターン中に生成されたユーザートークンを取得します。

概要

エージェントでOAuthを使用するには、次の3つのアクティビティが必要です。

  1. Azure Bot での OAuth の構成とアプリ登録: Azure Bot リソース上に 1 つ以上の OAuth 接続を作成します。各接続は、Microsoft Entra ID によるアプリ登録によってサポートされます。 フェデレーションID認証情報を使用したユーザー認証の追加は、最も一般的なアプローチです。 クライアントシークレットや証明書など、他の認証情報タイプもサポートされています。 オプションの全セットについては、Bot Service 認証の基本を参照してください。
  2. エージェントで対応する設定を構成する: Azure Bot 上の各 OAuth 接続は、エージェント構成内の 1 つの OAuth ハンドラーに対応付けられます。 「設定」を参照してください。 一般的なエージェント設定については、Microsoft 365 エージェント SDK とは何かをご覧ください。
  3. コード内でトークンを使用する: ターン中に、エージェントのユーザー認証APIを介してユーザートークンを取得するか、または代理(OBO)交換を実行します。 コードでのトークンの使用 (非 OBO) およびコードでのトークンの使用 (OBO) を参照してください。

この記事の続きを読む際には、以下の概念を念頭に置いてください。

  • Azure Bot は、複数の OAuth 接続を持つことができます。 たとえば、Microsoft Graph用の接続とGitHub用の接続です。 各接続はAzure Bot上で独立して構成されています。
  • Azure BotのOAuth接続とエージェント内のOAuthハンドラの間には1対1の関係があります。 ハンドラーの AzureBotOAuthConnectionName 設定は、使用するAzure Bot接続の名前を付けます。 2つの接続を使うには、2つのハンドラを定義します。
  • エージェントのユーザー認証APIは、コード内で呼び出すインターフェイスです。 .NET では、これは AgentApplication.UserAuthorization となります。たとえば、トークンを読み取るには GetTurnTokenAsync、OBO交換を実行するには ExchangeTurnTokenAsync を使用します。 同等のサーフェスは、JavaScriptではauthorization、Pythonではauthです。

動作中のサンプルについては、自動サインインおよびOBOサンプルを参照してください:

OAuth のサポート言語

エージェントSDKは.NET、JavaScript、Python向けのOAuthをサポートしています。 すべての言語で基本的な概念は同じです (OAuthハンドラ、ルートへのハンドラの接続、OBO交換)。 異なるのは設定形式とハンドラーAPI名のみです。

Language 構成する場所 API サーフェス
.NET appsettings.json (または Program.cs内のコード) AgentApplication.UserAuthorization
JavaScript .env 環境変数 AgentApplication.authorization
Python .env 環境変数 AgentApplication.auth

JavaScriptやPythonの場合、 .env キーは.NET appsettings.json 構造と同じ階層名を使用し、各レベルはダブルアンダースコア (__) で区切られています。 JavaScript キーは、テーブルに表示されるキャメルケースのリーフ名を保持します (例: azureBotOAuthConnectionName)。 Pythonキーは大文字(例: AZUREBOTOAUTHCONNECTIONNAME)で表記されています。

重要

グローバル自動サインイン (AutoSignIn) と DefaultHandlerName は.NETでのみサポートされています。 JavaScriptとPythonでは、ルートごとの構成に示すように、OAuthハンドラーを特定のルートにアタッチします。

設定

AgentApplication 内部のユーザー認証オブジェクトは、エージェントがユーザートークンを取得する方法を制御します。 少なくとも、各ハンドラーは使用するAzure Bot OAuth接続名を付けます。 以下の例は各言語における最小構造を示しています。 以下の表では、利用可能なその他の物件について説明し、OBOのセクションではOBOConnectionNameOBOScopesの設定について説明します。

.NET では、appsettings.jsonAgentApplication の下でユーザー認証を構成します。

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

JavaScriptでは、.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}}

Pythonでは、.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}}

UserAuthorization のプロパティ

次の表は、各着信活動に対して、ハンドラの選択方法およびトークンの取得方法を決定するトップレベルの UserAuthorization プロパティを一覧にしたものです。

プロパティ 必須 内容
DefaultHandlerName いいえ (非推奨) string .NET のみ。 AutoSignIn の評価結果が true であり、ルートごとの上書きが指定されていない場合に使用されるハンドラーの名前。
AutoSignIn いいえ ブールまたは代理人 .NET のみ。 true (既定) の場合、エージェントは受信するすべての活動に対してトークンの取得を試みます。 活動の種類をフィルターするには、実行時に Options.AutoSignIn を使用して上書きしてください。
Handlers はい (1 つ以上) オブジェクト (辞書) ハンドラー名とその構成のマッピング 各キーは一意である必要があります。

.NET では、自動サインインが適用されるアクティビティを制限するには、次のような述語を設定します: Options.AutoSignIn = (context, ct) => Task.FromResult(context.Activity.IsType(ActivityTypes.Message));。 JavaScriptとPythonはグローバル自動サインインをサポートしていません。 代わりに、ルートごとの例に示されているように、個々のルートに特定のハンドラ名を付与してサインインします。

設定のプロパティ

次の表は、個々の OAuth ハンドラに適用されるネストされた Settings オブジェクトについて説明したもので、サインインカードの表示、再試行の動作、タイムアウト、オプションの OBO 交換構成を制御します。

プロパティ 必須 説明
AzureBotOAuthConnectionName イエス string Azure Bot リソースで定義された OAuth 接続名。
OBOConnectionName いいえ (OBO のみ) string On‑Behalf‑Of トークンの交換を実行するために使用される、エージェント SDK 接続の名前。
OBOScopes いいえ (OBO のみ) string[] OBO 交換時に要求されるスコープ OBOConnectionName で省略した場合は、ExchangeTurnTokenAsync を手動で呼び出すことができます。
Title いいえ string カスタム サインイン カードのタイトル。 既定は サインイン です。
Text いいえ string サインインカードのボタンのテキスト。 既定値は、サインインしてくださいです。
InvalidSignInRetryMax いいえ int ユーザーが無効なコードを入力した場合に許容される最大再試行回数。 既定値は 2 です。
InvalidSignInRetryMessage いいえ string 無効なコード入力後の表示メッセージです。 既定値: 無効なサインインコードです。6桁のコードを入力してください。
Timeout いいえ int (ms) 進行中のサインイン試行が期限切れになるまでのミリ秒数。 既定は 900000 (15 分) です。

紙幣

AzureBotOAuthConnectionNameOBOConnectionNameOBOScopesTitle、およびTextは、3つの言語すべてに適用されます(OAuthの言語サポートで説明されている言語ごとのキーの大文字小文字を使用します)。 InvalidSignInRetryMaxInvalidSignInRetryMessageTimeout は.NETの設定です。

どの種類を使用すべきですか?

以下の表を使って、どのアプローチが状況に合っているかを判断してください。

選択肢 使用する場合
自動サインイン (.NET のみ) すべての受信活動に対して自動的にトークンを取得させたい場合、あるいは UserAuthorizationOptions.AutoSignIn に述語を指定して、フィルターによって絞り込まれたサブセット (たとえば、メッセージのみ、またはイベントを除くすべて) を対象にしたい場合。 .NETでのみサポートされています。
ルートごと トークンが必要なのは特定のルートハンドラーのみであり、それ以外のルートでは異なる OAuth 接続 (まり異なるトークン) を使用する必要があります。 このオプションはJavaScriptとPythonで唯一の選択肢です。 .NETでは、グローバル自動サインインと連携して動作します。 .NETで両方が有効な場合、そのターンはそれぞれのトークンにアクセスできます。

コード内でトークンを使用 (OBO なし)

このセクションでは、 On-Behalf-Of の交換を実行せずに、Azure Bot の OAuth 接続から直接返されるユーザートークンを取得し、使用する方法について説明します。 .NETでは、グローバル自動サインインやルートごとのハンドラを使用できます。 JavaScriptとPythonはルートごとのハンドラのみを使用します。 アクティビティ ハンドラー内で、トークン (.NET では GetTurnTokenAsync、JavaScript では authorization.getToken、Python では auth.get_token) をできるだけ遅く取得してください。そうすることで、SDK がトークンの有効期限が近い場合にトークンを更新できます。 以下の例は、両方のパターンを示しています。

自動サインイン (.NET のみ)

紙幣

グローバル自動サインインと DefaultHandlerName は.NETでのみ使用可能です。 JavaScriptとPythonの場合は、ルートごとの構成を使用してください。

グローバル自動サインインによって、ルートごとのハンドラーを指定する必要なく、すべての受信アクティビティに対してトークンを取得する場合は、この設定を使用してください。

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

エージェント コードは、次のような形になります:

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

        // use the token 
    }
}

ルートごとの構成

きめ細やかな制御を行いたい場合は、ルートごとの構成を使用してください。トークンを取得するのは、明示的に指定したルートのみです。 ルートごとの設定には、次の利点があります。

  • 不要なトークン取得を削減します。
  • 異なるOAuth接続(したがって異なるリソースやスコープ)をターゲットにする異なるルートを可能にします。
  • これにより、認証済みルートと認証なしルートを同じエージェント内で混在させることができます。

以下の例では、単一の graph ハンドラがメッセージ経路にのみ付加されます。

.NETではグローバル自動サインインが無効化され、graph ハンドラは autoSignInHandlersを使ってルートに紐づけられています。

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

エージェント コードは、次のような形になります:

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

JavaScriptでは、ルート登録の最後の引数としてハンドラ名の配列を渡します。 そのルートのみが 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

エージェント コードは、次のような形になります:

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

Pythonでは、auth_handlers をルートデコレーターに渡します。 そのルートのみが GRAPH ハンドラーのサインインをトリガーします。

AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__GRAPH__SETTINGS__AZUREBOTOAUTHCONNECTIONNAME=teams_sso

エージェント コードは、次のような形になります:

@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

ターン中にトークンを回収する

ターン中に必要なときにユーザートークンを取り戻します。 複数回呼び出すことができます。 利用直前に呼び出すことで、必要に応じて更新ロジックが透過的に処理されます。

Language 通話
.NET GetTurnTokenAsync(turnContext, handlerName)
JavaScript authorization.getToken(context, handlerName)
Python auth.get_token(context, handler_name)

コードでトークンを使用する (OBO)

On-Behalf-Of (OBO) は、ユーザーが最初にサインインした際に交換可能なトークンが返されることを前提としています。 そのためには、OAuth 接続のスコープの中に、ダウンストリーム API が公開しているスコープに対応するものが含まれている必要があります (たとえば、公開されているスコープが defaultScopes 場合、構成するスコープは api://botid-{{clientId}}/defaultScopes となる可能性があります)。 その後、Agents SDK は、OBOConnectionName で識別される構成済みの接続と OBOScopes のリストを使用して、Microsoft Authentication Library (MSAL) による認証交換を使用します。 OBOConnectionNameOBOScopes の両方が設定に含まれている場合、交換は自動的に行われ、標準のトークン呼び出し(GetTurnTokenAsync / getToken / get_token)を通じて最終トークンを取得します。 どちらかが未設定の場合は、実行時に ExchangeTurnTokenAsync を使用して明示的にトークン交換を実行でき (.NET では ExchangeTurnTokenAsync、JavaScript では authorization.exchangeToken、Python ではauth.exchange_token) 、接続やスコープリストを動的に解決できます。

構成における OBO

このパターンは、構成時に必要な下流のリソースとスコープが分かっている場合に使用します。 OBOConnectionNameOBOScopes の両方を指定すると、SDK はサインイン時に自動的に「On‑Behalf‑Of」の交換を行います。 これは、標準トークン取得関数へのその後の呼び出しで、追加のランタイムコードを必要とせずに、OBOトークンが直接返されることを意味します。

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

エージェント コードは、次のような形になります:

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

JavaScriptでは、接続マップにOBO接続を定義し、ハンドラーから oboConnectionNameoboScopesを使用して参照します。

# 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

エージェント コードは、次のような形になります:

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

Pythonでは、CONNECTIONSの下にOBO接続を定義し、ハンドラからOBOCONNECTIONNAMEOBOSCOPESを使用してそれを参照します。

# 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

エージェント コードは、次のような形になります:

@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 交換

構成で下流のリソース、スコープ、または接続を修正できない場合は、ランタイム交換を使用してください。 この状況は、たとえば、スコープがテナント、ユーザーロール、または機能フラグに依存する場合に発生します。 このモデルでは、オプションでOBO接続を構成し、ターン時に決めたスコープで交換メソッドを呼び出します。 交換済みトークンを受け取り、すぐに適用できます。

ターン時に決めたスコープで ExchangeTurnTokenAsync を呼び出します。

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

エージェント コードは、次のような形になります:

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

ハンドラー名と、ターン時に決定したスコープを authorization.exchangeToken に伝えてください。

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

エージェント コードは、次のような形になります:

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

ターンタイム時に決定したスコープとハンドラー名を添えて、auth.exchange_token に伝えてください。

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

エージェント コードは、次のような形になります:

@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

地域ごとの OAuth 設定

米国以外の地域については、エージェントが使用するトークン サービスのエンドポイントを更新してください。

次の例は、.NET 構成の例を示しています。 appsettings.json に追加します。

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

service-endpoint-url については、指定されたリージョンにデータ所在地を持つパブリック クラウドのボットの場合、以下の表から適切な値を使用してください。

URI Region
https://europe.api.botframework.com ヨーロッパ
https://unitedstates.api.botframework.com 米国
https://india.api.botframework.com インド