Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
OAuth for a .NET agent is first provisioned on your Azure Bot resource and corresponding app registration. The agent runtime then acquires and refreshes user tokens for you through the AgentApplication auto sign-In capability. Auto sign-in can run globally (all activity types) or selectively so that only certain routes or activity types request a token.
You can register multiple OAuth handlers in configuration and assign them to specific routes, or declare a single default handler used everywhere. Each handler can optionally perform On-Behalf-Of (OBO) exchanges when the Azure side is configured for it. For a quick start, see the AutoSignIn sample, or retrofit the configuration shown here into an existing agent.
The following sections describe how to configure UserAuthorization, decide between global and per‑route approaches, and retrieve tokens (standard and OBO) during a turn. Regional guidance is also provided for non-US deployments.
The .NET agent is configured in appsettings, or via code in Program.cs. This document details using appsettings.
Settings
The UserAuthorization object inside AgentApplication controls how user tokens are acquired. The following JSON shows the hierarchical structure followed by tables that describe each property for UserAuthorization and for the nested Settings object.
"AgentApplication": {
"UserAuthorization": {
"DefaultHandlerName": "{{handler-name}}",
"AutoSignIn": true | false,
"Handlers": {
"{{handler-name}}": {
"Settings": {
"AzureBotOAuthConnectionName": "{{azure-bot-connection-name}}",
"OBOConnectionName": "{{connection-name}}",
"OBOScopes": [
"{{obo-scope}}"
],
"Title": "{{signin-card-title}}",
"Text": "{{signin-card-button-text}}",
"InvalidSignInRetryMax": {{int}},
"InvalidSignInRetryMessage": {{invalid-attempt-message}},
"Timeout": {{timeout-ms}}
}
}
}
}
}
UserAuthorization properties
The following table lists the top-level UserAuthorization properties that determine how handlers are selected and how tokens are acquired for each incoming activity.
| Property | Required | Type | Description |
|---|---|---|---|
DefaultHandlerName |
No (recommended) | string | Name of the handler used when AutoSignIn evaluates to true and no per‑route override is specified. |
AutoSignIn |
No | bool or delegate | When true (default), the agent attempts to acquire a token for every incoming activity; can be overridden at runtime with Options.AutoSignIn to filter activity types. |
Handlers |
Yes (at least one) | object (dictionary) | Mapping of handler name to its configuration. Each key must be unique. |
To restrict which activities auto sign-in applies to, set a predicate similar to: Options.AutoSignIn = (context, ct) => Task.FromResult(context.Activity.IsType(ActivityTypes.Message));.
Settings properties
The following table describes the nested Settings object applied to an individual OAuth handler, controlling sign-in card presentation, retry behavior, timeouts, and optional OBO exchange configuration.
| Property | Required | Type | Description |
|---|---|---|---|
AzureBotOAuthConnectionName |
Yes | string | OAuth connection name defined on the Azure Bot resource. |
OBOConnectionName |
No (OBO only) | string | Name of an Agents SDK connection used to perform an On‑Behalf‑Of token exchange. |
OBOScopes |
No (OBO only) | string[] | Scopes requested during OBO exchange; if omitted with OBOConnectionName you can call ExchangeTurnTokenAsync manually. |
Title |
No | string | Custom sign‑in card title; defaults to Sign In. |
Text |
No | string | Sign‑in card button text; defaults to Please sign in. |
InvalidSignInRetryMax |
No | int | Maximum number of retries allowed when user enters an invalid code; default is 2. |
InvalidSignInRetryMessage |
No | string | Message shown after an invalid code entry; defaults to Invalid sign in code. Please enter the 6-digit code. |
Timeout |
No | int (ms) | Number of milliseconds before an in‑progress sign‑in attempt expires. The default is 900000 (15 minutes). |
What type to use?
Use the following table to decide which approach fits your scenario.
| Choice | Use when |
|---|---|
| Auto sign-in | You want every incoming activity to automatically acquire a token, or you want a filtered subset (for example only messages or everything except events) by supplying a predicate to UserAuthorizationOptions.AutoSignIn. |
| Per-route | Only specific route handlers need tokens or different routes must use different OAuth connections (and therefore different tokens). This is additive with global auto sign-in. If both are enabled, the turn has access to tokens from each. |
Use the token in code (non-OBO)
This section shows how to retrieve and use the user token directly returned by your Azure Bot OAuth connection without performing an On‑Behalf‑Of exchange. Decide first whether you prefer global auto sign-in or per‑route handlers; then, inside your activity handler, call UserAuthorization.GetTurnTokenAsync as late as possible so that the SDK can refresh the token if it's near expiry. The following examples illustrate both patterns.
Auto sign in only configuration
Use this configuration when global auto sign-in should acquire a token for every incoming activity without needing to specify per‑route handlers.
"AgentApplication": {
"UserAuthorization": {
"DefaultHandlerName": "auto",
"Handlers": {
"auto": {
"Settings": {
"AzureBotOAuthConnectionName": "teams_sso",
}
}
}
}
},
Your agent code would look something like this:
public class MyAgent : AgentApplication
{
public MyAgent(AgentApplicationOptions options) : base(options)
{
OnActivity(ActivityTypes.Message, OnMessageAsync, rank: RouteRank.Last);
}
public async Task OnMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken)
{
var token = await UserAuthorization.GetTurnTokenAsync(turnContext);
// use the token
}
}
Per-route only configuration
Use a per-route configuration when you want fine-grained control: only the routes you explicitly mark acquire tokens. Per-route configuration reduces unnecessary token retrieval, allows different routes to target distinct OAuth connections (and therefore different resources or scopes), and lets you mix authenticated and unauthenticated routes within the same agent. In the following example, global auto sign-in is disabled and a single messageOauth handler is attached only to the message route.
"AgentApplication": {
"UserAuthorization": {
"AutoSignIn": false,
"Handlers": {
"messageOauth": {
"Settings": {
"AzureBotOAuthConnectionName": "teams_sso",
}
}
}
}
},
Your agent code would look something like this:
public class MyAgent : AgentApplication
{
public MyAgent(AgentApplicationOptions options) : base(options)
{
OnActivity(ActivityTypes.Message, OnMessageAsync, rank: RouteRank.Last, autoSignInHandlers: ["messageOauth"]);
}
public async Task OnMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken)
{
var token = await UserAuthorization.GetTurnTokenAsync(turnContext, "messageOauth");
// use the token
}
}
Use GetTurnTokenAsync
Call GetTurnTokenAsync whenever you need the user token during a turn. You can invoke it multiple times. Call it immediately before use so that refresh logic (if necessary) is handled transparently.
Use the token in code (OBO)
On-Behalf-Of (OBO) relies on the initial user sign-in returning an exchangeable token. That requires the OAuth connection's scopes to include one that corresponds to a scope exposed by the downstream API (for example if the exposed scope is defaultScopes, the configured scope might be api://botid-{{clientId}}/defaultScopes). The Agents SDK then performs a Microsoft Authentication Library (MSAL) exchange using a configured connection identified by OBOConnectionName and the list of OBOScopes. When both OBOConnectionName and OBOScopes are present in configuration the exchange occurs automatically, and you obtain the final token through GetTurnTokenAsync. If either is missing you can perform the exchange explicitly at runtime with ExchangeTurnTokenAsync, allowing you to resolve the connection or scope list dynamically.
OBO in config
Use this pattern when you know the downstream resource and scopes you need at configuration time. When you provide both OBOConnectionName and OBOScopes, the SDK automatically performs the On‑Behalf‑Of exchange during sign-in. This means subsequent calls to GetTurnTokenAsync return the OBO token directly with no extra runtime code needed.
"AgentApplication": {
"UserAuthorization": {
"DefaultHandlerName": "auto",
"Handlers": {
"auto": {
"Settings": {
"AzureBotOAuthConnectionName": "teams_sso",
"OBOConnectionName": "ServiceConnection",
"OBOScopes": [
"https://myservicescope.com/.default"
]
}
}
}
}
},
"Connections": {
"ServiceConnection": {
"Settings": {
"AuthType": "FederatedCredentials",
"AuthorityEndpoint": "https://login.microsoftonline.com/{{TenantId}}",
"ClientId": "{{ClientId}}",
"FederatedClientId": "{{ManagedIdentityClientId}}",
"Scopes": [
"https://api.botframework.com/.default"
]
}
}
},
Your agent code would look something like this:
public class MyAgent : AgentApplication
{
public MyAgent(AgentApplicationOptions options) : base(options)
{
OnActivity(ActivityTypes.Message, OnMessageAsync, rank: RouteRank.Last);
}
public async Task OnMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken)
{
var token = await UserAuthorization.GetTurnTokenAsync(turnContext);
// use the token
}
}
OBO Exchange at runtime
Use a runtime exchange when the downstream resource, scopes, or even the connection you must use can't be fixed in configuration—for example when scopes depend on tenant, user role, or a feature flag. In this model you configure (optionally) the OBOConnectionName, then call ExchangeTurnTokenAsync with the scopes you decide at turn time, receiving an exchanged token you can immediately apply.
"AgentApplication": {
"UserAuthorization": {
"DefaultHandlerName": "auto",
"Handlers": {
"auto": {
"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"
]
}
}
},
Your agent code would look something like this:
public class MyAgent : AgentApplication
{
public MyAgent(AgentApplicationOptions options) : base(options)
{
OnActivity(ActivityTypes.Message, OnMessageAsync, rank: RouteRank.Last);
}
public async Task OnMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken)
{
var scopes = GetScopes();
var exchangedToken = await UserAuthorization.ExchangeTurnTokenAsync(turnContext, exchangeScopes: scopes);
// use the token
}
}
Regional OAuth Settings
For non-US regions, you need to update the token service endpoint used by your agent.
Add the following to appsettings.json:
"RestChannelServiceClientFactory": {
"TokenServiceEndpoint": "{{service-endpoint-uri}}"
}
For service-endpoint-url, use the appropriate value from the following table for public-cloud bots with data residency in the specified region.
| URI | Region |
|---|---|
https://europe.api.botframework.com |
Europe |
https://unitedstates.api.botframework.com |
United States |
https://india.api.botframework.com |
India |