Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 10 additions & 32 deletions Libraries/Microsoft.Teams.Api/Auth/ClientCredentials.cs
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replaced to use M.I.W that already handles different credential types

Original file line number Diff line number Diff line change
@@ -1,46 +1,24 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.Identity.Abstractions;
using Microsoft.Teams.Common.Http;

namespace Microsoft.Teams.Api.Auth;

public class ClientCredentials : IHttpCredentials
public class ClientCredentials(IAuthorizationHeaderProvider authorizationHeaderProvider) : IHttpCredentials
{
public string ClientId { get; set; }
public string ClientSecret { get; set; }
public string? TenantId { get; set; }

public ClientCredentials(string clientId, string clientSecret)
{
ClientId = clientId;
ClientSecret = clientSecret;
}

public ClientCredentials(string clientId, string clientSecret, string? tenantId)
{
ClientId = clientId;
ClientSecret = clientSecret;
TenantId = tenantId;
}

public async Task<ITokenResponse> Resolve(IHttpClient client, string[] scopes, CancellationToken cancellationToken = default)
{
var tenantId = TenantId ?? "botframework.com";
var request = HttpRequest.Post(
$"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token"
);

request.Headers.Add("Content-Type", ["application/x-www-form-urlencoded"]);
request.Body = new Dictionary<string, string>()
AuthorizationHeaderProviderOptions options = new();
options.AcquireTokenOptions = new AcquireTokenOptions()
{
{ "grant_type", "client_credentials" },
{ "client_id", ClientId },
{ "client_secret", ClientSecret },
{ "scope", string.Join(",", scopes) }
};
Comment on lines +13 to 16
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AcquireTokenOptions is instantiated but left empty. Consider either:

  1. Removing these lines if no options are needed: AuthorizationHeaderProviderOptions options = new();
  2. Adding a comment explaining why an empty options object is required
  3. Passing null if the API supports it

This will improve code clarity and reduce unnecessary object allocation.

Copilot uses AI. Check for mistakes.

var res = await client.SendAsync<TokenResponse>(request, cancellationToken);
return res.Body;
var tokenResult = await authorizationHeaderProvider.CreateAuthorizationHeaderForAppAsync(scopes[0], options, cancellationToken);
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only the first scope (scopes[0]) is being used, but the method accepts an array. This could lead to unexpected behavior if callers pass multiple scopes. Consider either:

  1. Joining all scopes with a space delimiter (standard OAuth2 practice): string.Join(" ", scopes)
  2. Changing the parameter type to string scope if only one scope is supported
  3. Documenting that only the first scope is used
Suggested change
var tokenResult = await authorizationHeaderProvider.CreateAuthorizationHeaderForAppAsync(scopes[0], options, cancellationToken);
var tokenResult = await authorizationHeaderProvider.CreateAuthorizationHeaderForAppAsync(string.Join(" ", scopes), options, cancellationToken);

Copilot uses AI. Check for mistakes.
return new TokenResponse
{
AccessToken = tokenResult.Substring("Bearer ".Length),
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Substring call will throw an ArgumentOutOfRangeException if tokenResult is shorter than "Bearer " (7 characters) or doesn't start with "Bearer ". Consider using a safer approach like:

AccessToken = tokenResult.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase) 
    ? tokenResult.Substring("Bearer ".Length) 
    : tokenResult,
Suggested change
AccessToken = tokenResult.Substring("Bearer ".Length),
AccessToken = tokenResult.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)
? tokenResult.Substring("Bearer ".Length)
: tokenResult,

Copilot uses AI. Check for mistakes.
TokenType = "Bearer",
};
}
}
2 changes: 2 additions & 0 deletions Libraries/Microsoft.Teams.Api/Clients/ActivityClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
using Microsoft.Teams.Api.Activities;
using Microsoft.Teams.Common.Http;

using IHttpClientFactory = Microsoft.Teams.Common.Http.IHttpClientFactory;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could not find a better way to avoid namespace conflicts


namespace Microsoft.Teams.Api.Clients;

public class ActivityClient : Client
Expand Down
1 change: 1 addition & 0 deletions Libraries/Microsoft.Teams.Api/Clients/ApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using Microsoft.Teams.Common.Http;
using IHttpClientFactory = Microsoft.Teams.Common.Http.IHttpClientFactory;

namespace Microsoft.Teams.Api.Clients;

Expand Down
1 change: 1 addition & 0 deletions Libraries/Microsoft.Teams.Api/Clients/BotClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using Microsoft.Teams.Common.Http;

using IHttpClientFactory = Microsoft.Teams.Common.Http.IHttpClientFactory;
namespace Microsoft.Teams.Api.Clients;

public class BotClient : Client
Expand Down
1 change: 1 addition & 0 deletions Libraries/Microsoft.Teams.Api/Clients/BotSignInClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using Microsoft.Teams.Common.Http;

using IHttpClientFactory = Microsoft.Teams.Common.Http.IHttpClientFactory;
namespace Microsoft.Teams.Api.Clients;

public class BotSignInClient : Client
Expand Down
1 change: 1 addition & 0 deletions Libraries/Microsoft.Teams.Api/Clients/BotTokenClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using Microsoft.Teams.Common.Http;

using IHttpClientFactory = Microsoft.Teams.Common.Http.IHttpClientFactory;
namespace Microsoft.Teams.Api.Clients;

public class BotTokenClient : Client
Expand Down
1 change: 1 addition & 0 deletions Libraries/Microsoft.Teams.Api/Clients/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using Microsoft.Teams.Common.Http;

using IHttpClientFactory = Microsoft.Teams.Common.Http.IHttpClientFactory;
namespace Microsoft.Teams.Api.Clients;

public abstract class Client
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.Teams.Api.Activities;
using Microsoft.Teams.Common.Http;

using IHttpClientFactory = Microsoft.Teams.Common.Http.IHttpClientFactory;
namespace Microsoft.Teams.Api.Clients;

public class ConversationClient : Client
Expand Down
1 change: 1 addition & 0 deletions Libraries/Microsoft.Teams.Api/Clients/MeetingClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.Teams.Api.Meetings;
using Microsoft.Teams.Common.Http;

using IHttpClientFactory = Microsoft.Teams.Common.Http.IHttpClientFactory;
namespace Microsoft.Teams.Api.Clients;

public class MeetingClient : Client
Expand Down
1 change: 1 addition & 0 deletions Libraries/Microsoft.Teams.Api/Clients/MemberClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using Microsoft.Teams.Common.Http;

using IHttpClientFactory = Microsoft.Teams.Common.Http.IHttpClientFactory;
namespace Microsoft.Teams.Api.Clients;

public class MemberClient : Client
Expand Down
1 change: 1 addition & 0 deletions Libraries/Microsoft.Teams.Api/Clients/TeamClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using Microsoft.Teams.Common.Http;

using IHttpClientFactory = Microsoft.Teams.Common.Http.IHttpClientFactory;
namespace Microsoft.Teams.Api.Clients;

public class TeamClient : Client
Expand Down
1 change: 1 addition & 0 deletions Libraries/Microsoft.Teams.Api/Clients/UserClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using Microsoft.Teams.Common.Http;

using IHttpClientFactory = Microsoft.Teams.Common.Http.IHttpClientFactory;
namespace Microsoft.Teams.Api.Clients;

public class UserClient : Client
Expand Down
1 change: 1 addition & 0 deletions Libraries/Microsoft.Teams.Api/Clients/UserTokenClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

using Microsoft.Teams.Common.Http;

using IHttpClientFactory = Microsoft.Teams.Common.Http.IHttpClientFactory;
namespace Microsoft.Teams.Api.Clients;

public class UserTokenClient : Client
Expand Down
5 changes: 3 additions & 2 deletions Libraries/Microsoft.Teams.Api/Microsoft.Teams.Api.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!-- Copyright (c) Microsoft Corporation. All rights reserved.-->
<!-- Copyright (c) Microsoft Corporation. All rights reserved.-->
<!-- Licensed under the MIT License.-->

<Project Sdk="Microsoft.NET.Sdk">
Expand All @@ -23,7 +23,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.5.0" />
<PackageReference Include="Microsoft.Identity.Web.AgentIdentities" Version="4.0.1" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.14.0" />
<PackageReference Include="System.Text.Json" Version="9.0.0" />
</ItemGroup>

Expand Down
7 changes: 5 additions & 2 deletions Libraries/Microsoft.Teams.Apps/App.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,14 @@ internal string UserAgent
}
}

public App(AppOptions? options = null)
internal App() : this(null!, null)
{ }

public App(IHttpCredentials credentials, AppOptions? options = null)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to register our credentials in the DI, so we can get receive the IAuthorizationHeaderProvider

{
Logger = options?.Logger ?? new ConsoleLogger();
Storage = options?.Storage ?? new LocalStorage<object>();
Credentials = options?.Credentials;
Credentials = credentials;
Plugins = options?.Plugins ?? [];
OAuth = options?.OAuth ?? new OAuthSettings();
Provider = options?.Provider;
Expand Down
12 changes: 11 additions & 1 deletion Libraries/Microsoft.Teams.Apps/AppBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Teams.Apps.Plugins;
using Microsoft.Teams.Common.Http;

namespace Microsoft.Teams.Apps;

public partial class AppBuilder
{
private readonly IServiceProvider _serviceProvider;
protected AppOptions _options;

public AppBuilder(IServiceProvider serviceProvider)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is an idea to consolidate our AppBuilder with the service provider.. not perfect, but it works :)

{
_serviceProvider = serviceProvider;
_options = serviceProvider.GetService(typeof(AppOptions)) as AppOptions ?? throw new InvalidOperationException("AppOptions not found in DI container");
}

public AppBuilder(AppOptions? options = null)
{
_serviceProvider = null!;
_options = options ?? new AppOptions();
}

Expand Down Expand Up @@ -100,6 +110,6 @@ public AppBuilder AddOAuth(string defaultConnectionName)

public App Build()
{
return new App(_options);
return new App(_serviceProvider.GetService<IHttpCredentials>()!, _options);
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Build() method will throw a NullReferenceException when called on an AppBuilder instance created with the parameterless constructor (line 21-25), since _serviceProvider is set to null!. Consider adding a null check and appropriate error handling:

public App Build()
{
    var credentials = _serviceProvider?.GetService<IHttpCredentials>();
    return new App(credentials!, _options);
}

Or throw a more descriptive exception if _serviceProvider is null.

Suggested change
return new App(_serviceProvider.GetService<IHttpCredentials>()!, _options);
IHttpCredentials? credentials = null;
if (_serviceProvider != null)
{
credentials = _serviceProvider.GetService<IHttpCredentials>();
}
if (credentials == null)
{
credentials = _options.Credentials;
}
if (credentials == null)
{
throw new InvalidOperationException("No IHttpCredentials available. Provide credentials via AddCredentials or ensure they are registered in the service provider.");
}
return new App(credentials, _options);

Copilot uses AI. Check for mistakes.
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.Teams.Api.Auth;
// using Microsoft.Teams.Api.Auth;

namespace Microsoft.Teams.Apps.Extensions;

Expand All @@ -20,10 +20,10 @@ public AppOptions Apply(AppOptions? options = null)
{
options ??= new AppOptions();

if (ClientId is not null && ClientSecret is not null && !Empty)
{
options.Credentials = new ClientCredentials(ClientId, ClientSecret, TenantId);
}
//if (ClientId is not null && ClientSecret is not null && !Empty)
//{
// options.Credentials = new ClientCredentials(ClientId, ClientSecret, TenantId);
//}

return options;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Teams.Api.Auth;
// using Microsoft.Teams.Api.Auth;
using Microsoft.Teams.Apps.Plugins;
using Microsoft.Teams.Common.Logging;
using Microsoft.Teams.Extensions.Logging;
Expand Down Expand Up @@ -32,22 +32,22 @@ public static IHostApplicationBuilder AddTeamsCore(this IHostApplicationBuilder
var loggingSettings = builder.Configuration.GetTeamsLogging();

// client credentials
if (options.Credentials is null && settings.ClientId is not null && settings.ClientSecret is not null && !settings.Empty)
{
options.Credentials = new ClientCredentials(
settings.ClientId,
settings.ClientSecret,
settings.TenantId
);
}
//if (options.Credentials is null && settings.ClientId is not null && settings.ClientSecret is not null && !settings.Empty)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Credentials are now managed by M.I.W, I leave this as commented just to see the diff, if we al agree I can delete the commented code

//{
// options.Credentials = new ClientCredentials(
// settings.ClientId,
// settings.ClientSecret,
// settings.TenantId
// );
//}

options.Logger ??= new ConsoleLogger(loggingSettings);
var app = new App(options);

//var app = new App(options);
builder.Services.AddSingleton<App>();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the key part.. to properly register our app in DI.

builder.Services.AddSingleton(settings);
builder.Services.AddSingleton(loggingSettings);
builder.Logging.AddTeams(app.Logger);
builder.Services.AddTeams(app);
//builder.Logging.AddTeams(app.Logger);
//builder.Services.AddTeams(app);
return builder;
}

Expand All @@ -57,14 +57,14 @@ public static IHostApplicationBuilder AddTeamsCore(this IHostApplicationBuilder
var loggingSettings = builder.Configuration.GetTeamsLogging();

// client credentials
if (settings.ClientId is not null && settings.ClientSecret is not null && !settings.Empty)
{
appBuilder = appBuilder.AddCredentials(new ClientCredentials(
settings.ClientId,
settings.ClientSecret,
settings.TenantId
));
}
//if (settings.ClientId is not null && settings.ClientSecret is not null && !settings.Empty)
//{
// appBuilder = appBuilder.AddCredentials(new ClientCredentials(
// settings.ClientId,
// settings.ClientSecret,
// settings.TenantId
// ));
//}

var app = appBuilder.Build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,20 @@ public static IServiceCollection AddTeams(this IServiceCollection collection)
return collection;
}

public static IServiceCollection AddTeams(this IServiceCollection collection, AppOptions options)
{
var app = new App(options);
var log = new TeamsLogger(app.Logger);

collection.AddSingleton(app.Logger);
collection.AddSingleton(app.Storage);
collection.AddSingleton<ILoggerFactory>(_ => new LoggerFactory([new TeamsLoggerProvider(log)]));
collection.AddSingleton<ILogger>(log);
collection.AddSingleton(app);
collection.AddHostedService<TeamsService>();
collection.AddSingleton<IContext.Accessor>();
return collection;
}
//public static IServiceCollection AddTeams(this IServiceCollection collection, AppOptions options)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this method was not called anywhere

//{
// var app = new App(options);
// var log = new TeamsLogger(app.Logger);

// collection.AddSingleton(app.Logger);
// collection.AddSingleton(app.Storage);
// collection.AddSingleton<ILoggerFactory>(_ => new LoggerFactory([new TeamsLoggerProvider(log)]));
// collection.AddSingleton<ILogger>(log);
// collection.AddSingleton(app);
// collection.AddHostedService<TeamsService>();
// collection.AddSingleton<IContext.Accessor>();
// return collection;
//}

public static IServiceCollection AddTeams(this IServiceCollection collection, AppBuilder builder)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="9.0.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.8.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.14.0" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.Teams.Apps;
using Microsoft.Teams.Apps.Annotations;
using Microsoft.Teams.Apps.Plugins;
using Microsoft.Teams.Common.Http;

namespace Microsoft.Teams.Plugins.AspNetCore.Extensions;

Expand All @@ -22,7 +23,7 @@ public static partial class ApplicationBuilderExtensions
public static App UseTeams(this IApplicationBuilder builder, bool routing = true)
{
var assembly = Assembly.GetEntryAssembly() ?? Assembly.GetCallingAssembly();
var app = builder.ApplicationServices.GetService<App>() ?? new App(builder.ApplicationServices.GetService<AppOptions>());
var app = builder.ApplicationServices.GetService<App>() ?? new App(builder.ApplicationServices.GetService<IHttpCredentials>()!,builder.ApplicationServices.GetService<AppOptions>());
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after comma in constructor call. Should be:

var app = builder.ApplicationServices.GetService<App>() ?? new App(builder.ApplicationServices.GetService<IHttpCredentials>()!, builder.ApplicationServices.GetService<AppOptions>());
Suggested change
var app = builder.ApplicationServices.GetService<App>() ?? new App(builder.ApplicationServices.GetService<IHttpCredentials>()!,builder.ApplicationServices.GetService<AppOptions>());
var app = builder.ApplicationServices.GetService<App>() ?? new App(builder.ApplicationServices.GetService<IHttpCredentials>()!, builder.ApplicationServices.GetService<AppOptions>());

Copilot uses AI. Check for mistakes.
var plugins = builder.ApplicationServices.GetServices<IPlugin>();
var types = assembly.GetTypes();

Expand Down
Loading