Share via


Configure your .NET Agent to use OAuth

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