Skip to content

Commit f6fabd8

Browse files
authored
Merge pull request #130 from microsoft/users/mbarbour/AddAuthorizationSamples
Users/mbarbour/add authorization samples Need to follow up and fix Readme's
2 parents 571043e + 82d09eb commit f6fabd8

22 files changed

+1293
-0
lines changed

samples/basic/authorization/README.md

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
# User Authorization Samples
2+
3+
These Samples demonstrate 3 methods of acquiring User tokens from inside an agent. Each method has specific use cases where they can be applied.
4+
5+
| Type of Authorization Flow | Sample Name | Description
6+
|----------------------------|-------------|----------------------------------------------------
7+
| Automatic Sign In| AutoSignIn | This sample demonstrates the use of the AgentSDK's built in Automatic Sign in Flow. This Flow triggers anytime a message request is submitted ot the Agent.
8+
| Manual Sign In| ManualSignIn | This sample demonstrates the use of an on-demand login request. This request only logs in if the user or code requests it.
9+
| On Behalf of| OBOSignIn | This Sample utilizes the Automatic Sign In and Token Exchange.
10+
11+
# User Authorization Samples General Setup
12+
13+
>[!Warning]
14+
> It's important you follow these instructions prior to running the samples as this contains the common setup needed by all samples.
15+
16+
These Samples use the OAuth capabilities in [Azure Bot Service](https://docs.botframework.com), providing features to make it easier to develop a bot that authorizes users to various identity providers such as Entria ID (formally Azure Active Directory), GitHub, Uber, etc.
17+
18+
These samples are designed to work through Azure Bot Services and are not intended to work with the Bot Framework Emulator or Teams Test Tool at this time.
19+
## Prerequisites
20+
21+
Access and Software
22+
- [.Net](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) version 8.0
23+
- [dev tunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started?tabs=windows)
24+
- Access to create Registrations on Azure Bot Service
25+
- Access to create Entra ID Application Registrations
26+
- (Optional) Access to deploy M365\Teams Applications
27+
28+
### Setup Entra Applications
29+
30+
For these samples you will need to create two Entra ID Application Registrations.
31+
- Agent Application Identity
32+
- This identity will be used by your Agent and Azure Bot Services to Communicate with each other
33+
- User Application Identity
34+
- This is used as the broker application that will be used for end user authentication, API permissions and provide the token for the user to the Agent.
35+
36+
#### Create the Agent Application Identity
37+
38+
You if you have an exiting Bot Service Agent Registration that you want to reuse and you have the Application(client) ID, Tenant ID, and Client Secret, you can skip this step. Otherwise, follow the steps below to create a new Agent Application Registration.
39+
40+
For the purposes of local development we will use an application registration that is registered in the same tenant as the Azure Bot Service. This is not a requirement, but it does make local development easier. We will also
41+
1. Create a new Entra ID Application Registration
42+
1. Go to the [Azure Portal](https://portal.azure.com)
43+
1. Select **Microsoft Entra ID** from the Azure Services List
44+
1. Select **App registrations** in the left menu
45+
- Name: `AgentApp`
46+
- Supported Account Types: `Accounts in this organizational directory only (Single tenant)`
47+
- Click **Register**
48+
- Record the Agent Application ID (Client ID) and Directory (Tenant ID) for use below
49+
- Click **Authentication** in the left menu
50+
-
51+
52+
- ## Prerequisites
53+
54+
- [.Net](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) version 8.0
55+
- [dev tunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started?tabs=windows)
56+
57+
## Running this sample
58+
59+
1. [Create an Azure Bot](https://aka.ms/AgentsSDK-CreateBot)
60+
- Record the Application ID, the Tenant ID, and the Client Secret for use below
61+
62+
1. [Add OAuth to your bot](https://aka.ms/AgentsSDK-AddAuth)
63+
64+
1. Configuring the token connection in the Agent settings
65+
> The instructions for this sample are for a SingleTenant Azure Bot using ClientSecrets. The token connection configuration will vary if a different type of Azure Bot was configured. For more information see [DotNet MSAL Authentication provider](https://aka.ms/AgentsSDK-DotNetMSALAuth)
66+
67+
1. Open the `appsettings.json` file in the root of the sample project.
68+
69+
1. Find the section labeled `Connections`, it should appear similar to this:
70+
71+
```json
72+
"ConnectionName": "{{ConnectionName}}",
73+
74+
"TokenValidation": {
75+
"Audiences": [
76+
"{{ClientId}}" // this is the Client ID used for the Azure Bot
77+
],
78+
"TenantId": "{{TenantId}}"
79+
},
80+
81+
"Connections": {
82+
"ServiceConnection": {
83+
"Assembly": "Microsoft.Agents.Authentication.Msal",
84+
"Type": "MsalAuth",
85+
"Settings": {
86+
"AuthType": "ClientSecret", // this is the AuthType for the connection, valid values can be found in Microsoft.Agents.Authentication.Msal.Model.AuthTypes. The default is ClientSecret.
87+
"AuthorityEndpoint": "https://login.microsoftonline.com/{{TenantId}}",
88+
"ClientId": "{{ClientId}}", // this is the Client ID used for the connection.
89+
"ClientSecret": "00000000-0000-0000-0000-000000000000", // this is the Client Secret used for the connection.
90+
"Scopes": [
91+
"https://api.botframework.com/.default"
92+
]
93+
}
94+
}
95+
```
96+
97+
1. Replace all **{{ClientId}}** with the AppId of the bot.
98+
1. Replace all **{{TenantId}}** with the Tenant Id where your application is registered.
99+
1. Set the **ClientSecret** to the Secret that was created for your identity.
100+
101+
> Storing sensitive values in appsettings is not recommend. Follow [AspNet Configuration](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-9.0) for best practices.
102+
103+
1. Update `appsettings.json`
104+
105+
| Property | Value Description |
106+
|----------------------|-----------|
107+
| ConnectionName | Set the configured bot's OAuth connection name. |
108+
109+
1. Run `dev tunnels`. Please follow [Create and host a dev tunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started?tabs=windows) and host the tunnel with anonymous user access command as shown below:
110+
> NOTE: Go to your project directory and open the `./Properties/launchSettings.json` file. Check the port number and update it to match your DevTunnel port. If `./Properties/launchSettings.json`not fount Close and re-open the solution.launchSettings.json have been re-created.
111+
112+
```bash
113+
devtunnel host -p 3978 --allow-anonymous
114+
```
115+
116+
1. Update your Azure Bot ``Messaging endpoint`` with the tunnel Url: `{tunnel-url}/api/messages`
117+
118+
1. Run the bot from a terminal or from Visual Studio
119+
120+
1. Test via "Test in WebChat"" on your Azure Bot in the Azure Portal.
121+
122+
## Running this Agent in Teams
123+
124+
1. There are two version of the manifest provided. One for M365 Copilot and one for Teams.
125+
1. Copy the desired version to manifest.json
126+
1. Manually update the manifest.json
127+
- Edit the `manifest.json` contained in the `/appManifest` folder
128+
- Replace with your AppId (that was created above) *everywhere* you see the place holder string `<<AAD_APP_CLIENT_ID>>`
129+
- Replace `<<AGENT_DOMAIN>>` with your Agent url. For example, the tunnel host name.
130+
- Zip up the contents of the `/appManifest` folder to create a `manifest.zip`
131+
1. Upload the `manifest.zip` to Teams
132+
- Select **Developer Portal** in the Teams left sidebar
133+
- Select **Apps** (top row)
134+
- Select **Import app**, and select the manifest.zip
135+
136+
1. Select **Preview in Teams** in the upper right corner
137+
138+
## Interacting with the Agent
139+
140+
Type anything to sign-in, or `logout` to sign-out.
141+
142+
## Further reading
143+
To learn more about building Agents, see our [Microsoft 365 Agents SDK](https://github.com/microsoft/agents) repo.
144+
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.13.35828.75 d17.13
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoSignIn", "AutoSignIn\AutoSignIn.csproj", "{840081EB-DBFE-2E05-9275-2B4B342025E4}"
7+
EndProject
8+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ManualSignIn", "ManualSignIn\ManualSignIn.csproj", "{A7E45FDA-6714-F4B7-0195-5A2B12BA9A37}"
9+
EndProject
10+
Global
11+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
12+
Debug|Any CPU = Debug|Any CPU
13+
Release|Any CPU = Release|Any CPU
14+
EndGlobalSection
15+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
16+
{840081EB-DBFE-2E05-9275-2B4B342025E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17+
{840081EB-DBFE-2E05-9275-2B4B342025E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
18+
{840081EB-DBFE-2E05-9275-2B4B342025E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
19+
{840081EB-DBFE-2E05-9275-2B4B342025E4}.Release|Any CPU.Build.0 = Release|Any CPU
20+
{A7E45FDA-6714-F4B7-0195-5A2B12BA9A37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21+
{A7E45FDA-6714-F4B7-0195-5A2B12BA9A37}.Debug|Any CPU.Build.0 = Debug|Any CPU
22+
{A7E45FDA-6714-F4B7-0195-5A2B12BA9A37}.Release|Any CPU.ActiveCfg = Release|Any CPU
23+
{A7E45FDA-6714-F4B7-0195-5A2B12BA9A37}.Release|Any CPU.Build.0 = Release|Any CPU
24+
EndGlobalSection
25+
GlobalSection(SolutionProperties) = preSolution
26+
HideSolutionNode = FALSE
27+
EndGlobalSection
28+
GlobalSection(ExtensibilityGlobals) = postSolution
29+
SolutionGuid = {55274C53-CF36-4245-A6B2-6603A3066208}
30+
EndGlobalSection
31+
EndGlobal
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using Microsoft.Agents.Builder;
5+
using Microsoft.Agents.Builder.App;
6+
using Microsoft.Agents.Builder.State;
7+
using Microsoft.Agents.Builder.UserAuth;
8+
using Microsoft.Agents.Core.Models;
9+
using System.Net.Http;
10+
using System.Net.Http.Headers;
11+
using System.Text;
12+
using System.Text.Json.Nodes;
13+
using System.Threading;
14+
using System.Threading.Tasks;
15+
16+
17+
public class AuthAgent : AgentApplication
18+
{
19+
/// <summary>
20+
/// Default Sign In Name
21+
/// </summary>
22+
private string _defaultDisplayName = "Unknown User";
23+
/// <summary>
24+
/// Authorization Handler Name to use for queries
25+
/// </summary>
26+
private string _signInHandlerName = string.Empty;
27+
28+
/// <summary>
29+
/// Describes the agent registration for the Authorization Agent
30+
/// This agent will handle the sign-in and sign-out processes for a user.
31+
/// </summary>
32+
/// <param name="options">AgentApplication Configuration objects to configure and setup the Agent Application</param>
33+
public AuthAgent(AgentApplicationOptions options) : base(options)
34+
{
35+
/*
36+
During setup of the Agent Application, Register Event Handlers for the Agent.
37+
For this example we will register a welcome message for the user when they join the conversation, then configure sign-in and sign-out commands.
38+
Additionally, we will add events to handle notifications of sign-in success and failure, these notifications will report the local log instead of back to the calling agent. .
39+
40+
This handler should only register events and setup state as it can be called multiple times before agent events are invoked.
41+
*/
42+
43+
// this sets the default signin handler to the default handler name. This is used to identify the handler that will be used for the sign-in process later in this code.
44+
_signInHandlerName = UserAuthorization.DefaultHandlerName;
45+
46+
/*
47+
When a conversation update event is triggered.
48+
*/
49+
OnConversationUpdate(ConversationUpdateEvents.MembersAdded, WelcomeMessageAsync);
50+
51+
///*
52+
// Handles the user sending a Login or LogOut command using the specific keywords '-signout'
53+
//*/
54+
OnMessage("/signout", async (turnContext, turnState, cancellationToken) =>
55+
{
56+
// force a user signout to reset the user state
57+
// this is needed to reset the token in Azure Bot Services if needed.
58+
await UserAuthorization.SignOutUserAsync(turnContext, turnState, cancellationToken: cancellationToken);
59+
await turnContext.SendActivityAsync("You have signed out", cancellationToken: cancellationToken);
60+
}, rank: RouteRank.Last);
61+
62+
63+
/*
64+
The UserAuthorization Class provides methods and properties to manage and access user authentication tokens
65+
You can use this class to interact with the authentication process, including signing in and signing out users, accessing tokens, and handling authentication events.
66+
*/
67+
68+
// Register Events for SignIn and SignOut on the authentication class to track the status of the user, notify the user of the status of their authentication process, or log the status of the authentication process.
69+
UserAuthorization.OnUserSignInFailure(OnUserSignInFailure);
70+
71+
// Registers a general event handler that will pick up any message activity that is not covered by the previous events handlers.
72+
OnActivity(ActivityTypes.Message, OnMessageAsync, rank: RouteRank.Last);
73+
}
74+
75+
/// <summary>
76+
/// This method is called to handle the conversation update event when a new member is added to or removed from the conversation.
77+
/// </summary>
78+
/// <param name="turnContext"><see cref="ITurnContext"/></param>
79+
/// <param name="turnState"><see cref="ITurnState"/></param>
80+
/// <param name="cancellationToken"><see cref="CancellationToken"/></param>
81+
private async Task WelcomeMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken)
82+
{
83+
/*
84+
In this example, we will send a welcome message to the user when they join the conversation.
85+
We do this by iterating over the incoming activity members added to the conversation and checking if the member is not the agent itself.
86+
Then a greeting notice is provided to each new member of the conversation.
87+
*/
88+
foreach (ChannelAccount member in turnContext.Activity.MembersAdded)
89+
{
90+
if (member.Id != turnContext.Activity.Recipient.Id)
91+
{
92+
string displayName = await GetDisplayName(turnContext);
93+
StringBuilder sb = new StringBuilder();
94+
sb.AppendLine($"Welcome to the AutoSignIn Example, **{displayName}**!.");
95+
sb.AppendLine("This Agent automatically signs you in when you first connect.");
96+
sb.AppendLine("You can use the following commands to interact with the agent:");
97+
sb.AppendLine("/signout: Sign out of the agent and force it to reset the login flow on next message.");
98+
if (displayName.Equals(_defaultDisplayName))
99+
{
100+
sb.AppendLine("**WARNING: We were unable to get your display name with the current access token.. please use the /signout command before proceeding**");
101+
}
102+
sb.AppendLine("");
103+
sb.AppendLine("Type anything else to see the agent echo back your message.");
104+
await turnContext.SendActivityAsync(MessageFactory.Text(sb.ToString()), cancellationToken);
105+
sb.Clear();
106+
}
107+
}
108+
}
109+
110+
/// <summary>
111+
/// Handles general message loop.
112+
/// </summary>
113+
/// <param name="turnContext"><see cref="ITurnContext"/></param>
114+
/// <param name="turnState"><see cref="ITurnState"/></param>
115+
/// <param name="cancellationToken"><see cref="CancellationToken"/></param>
116+
/// <returns></returns>
117+
private async Task OnMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken)
118+
{
119+
/*
120+
When Auto Sign in is properly configured, the user will be automatically signed in when they first connect to the agent using the default handler chosen in the UserAuthorization configuration.
121+
IMPORTANT: The ReadMe associated with this sample, instructs you on confiuring the Azure Bot Service Registration with the scopes to allow you to read your own information from Graph. you must have completed that for this sample to work correctly.
122+
123+
If the sign in process is successful, the user will be signed in and the token will be available from the UserAuthorization.GetTurnToken(_signInHandlerName) function call.
124+
if the sign in was not successful, you will get a Null when you call the UserAuthorization.GetTurnToken(_signInHandlerName) function.
125+
*/
126+
127+
// Check for Access Token from the Authorization Sub System.
128+
if (string.IsNullOrEmpty(UserAuthorization.GetTurnToken(_signInHandlerName)))
129+
{
130+
// Failed to get access token here, and we will now bail out of this message loop.
131+
await turnContext.SendActivityAsync($"The auto sign in process failed and no access token is available", cancellationToken: cancellationToken);
132+
return;
133+
}
134+
135+
// We have the access token, now try to get your user name from graph.
136+
string displayName = await GetDisplayName(turnContext);
137+
if (displayName.Equals(_defaultDisplayName))
138+
{
139+
// Handle error response from Graph API
140+
await turnContext.SendActivityAsync($"Failed to get user information from Graph API \nDid you update the scope correctly in Azure bot Service?. If so type in /signout to force signout the current user", cancellationToken: cancellationToken);
141+
return;
142+
}
143+
144+
// Now Echo back what was said with your display name.
145+
await turnContext.SendActivityAsync($"**{displayName} said:** {turnContext.Activity.Text}", cancellationToken: cancellationToken);
146+
}
147+
148+
/// <summary>
149+
/// This method is called when the sign-in process fails with an error indicating why .
150+
/// </summary>
151+
/// <param name="turnContext"></param>
152+
/// <param name="turnState"></param>
153+
/// <param name="handlerName"></param>
154+
/// <param name="response"></param>
155+
/// <param name="initiatingActivity"></param>
156+
/// <param name="cancellationToken"></param>
157+
/// <returns></returns>
158+
private async Task OnUserSignInFailure(ITurnContext turnContext, ITurnState turnState, string handlerName, SignInResponse response, IActivity initiatingActivity, CancellationToken cancellationToken)
159+
{
160+
// Raise a notification to the user that the sign-in process failed.
161+
await turnContext.SendActivityAsync($"Manual Sign In: Failed to login to '{handlerName}': {response.Error.Message}", cancellationToken: cancellationToken);
162+
}
163+
164+
/// <summary>
165+
/// Gets the display name of the user from the Graph API using the access token.
166+
/// </summary>
167+
/// <param name="turnContext"><see cref="ITurnState"/></param>
168+
/// <returns></returns>
169+
private async Task<string> GetDisplayName(ITurnContext turnContext)
170+
{
171+
string displayName = _defaultDisplayName;
172+
string accessToken = UserAuthorization.GetTurnToken(_signInHandlerName);
173+
string graphApiUrl = $"https://graph.microsoft.com/v1.0/me";
174+
HttpClient client = new HttpClient();
175+
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
176+
HttpResponseMessage response = await client.GetAsync(graphApiUrl);
177+
if (response.IsSuccessStatusCode)
178+
{
179+
var content = await response.Content.ReadAsStringAsync();
180+
var graphResponse = JsonNode.Parse(content);
181+
displayName = graphResponse!["displayName"].GetValue<string>();
182+
}
183+
return displayName;
184+
}
185+
186+
187+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<LangVersion>latest</LangVersion>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<PackageReference Include="Microsoft.Agents.Authentication.Msal" Version="0.2.*-*" />
10+
<PackageReference Include="Microsoft.Agents.Hosting.AspNetCore" Version="0.2.*-*" />
11+
</ItemGroup>
12+
13+
</Project>

0 commit comments

Comments
 (0)