Skip to content

Conversation

@aacebo
Copy link
Collaborator

@aacebo aacebo commented Oct 20, 2025

To support the new combined auth flow, developers can now optionally configure an accountLinkingUrl that when present, will be returned by the apps default verify state invoke handler to the server.

https://skype.visualstudio.com/SCC/_git/async_messaging_botapiservice/pullrequest/1264762?_a=files&path=/BotNotifications.Library/Services/SendBotInvokeHandler.cs

@singhk97
Copy link
Collaborator

What scenarios does this enable?

@aacebo
Copy link
Collaborator Author

aacebo commented Oct 20, 2025

What scenarios does this enable?

the new combined auth flow

Copy link
Collaborator

@heyitsaamir heyitsaamir left a comment

Choose a reason for hiding this comment

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

Would it be possible to include what the new flow will look like? Maybe just text or a simple mermaid diagram? The actual change seems small, but I'm not sure how exactly this enables the scenario

public IList<IRoute> Select(IActivity activity)
{
return _routes
.OrderBy(route => route.Type == RouteType.User ? 0 : 1)
Copy link
Collaborator

Choose a reason for hiding this comment

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

so user routes run before system? We should make sure this is in other languages too

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes it works that way in TS as well, this is to ensure that if you register a route over a system (default) route, your route should be the priority. If you defer by calling next it would then use the default implementation.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@rajan-chari rajan-chari force-pushed the aacebo/account-linking-url branch from 7ba99ef to 857a402 Compare January 9, 2026 16:34
Copilot AI review requested due to automatic review settings January 9, 2026 16:34
@rajan-chari
Copy link
Contributor

Merge Conflicts Resolved ✅

I've rebased this PR on the latest main branch and resolved all merge conflicts.

Changes Made

The conflicts were in ActivityClient.cs where both branches had modified the same methods:

  • Main branch: Added isTargeted parameter to support targeted activities
  • This PR: Added virtual keywords for extensibility

Resolution: Combined both changes - all methods now have both virtual keyword AND isTargeted parameter.

Testing

✅ Build: Success
✅ Tests: All 561 tests pass (including 2 new tests from this PR)
✅ No breaking changes

Core Functionality

This PR adds support for the combined auth flow by:

  1. Adding AccountLinkingUrl property to OAuthSettings
  2. Returning accountLinkingUrl in the response body when configured
  3. Comprehensive test coverage

@lilyydu @heyitsaamir - This PR is now ready for review and merge. All conflicts have been resolved and all tests pass.

Fixes #163

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds support for a combined authentication flow by introducing an optional AccountLinkingUrl configuration. When configured, this URL is returned in the response body of the verify state invoke handler, enabling clients to perform tab authentication and link NAA accounts to bot login accounts.

Key Changes:

  • Added AccountLinkingUrl property to OAuthSettings and TeamsSettings classes for configuration
  • Updated default verify state handler in AppRouting.cs to return accountLinkingUrl in the response when configured
  • Added comprehensive test coverage for the new feature with override capability

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
Tests/Microsoft.Teams.Apps.Tests/Activities/Invokes/SignIn/VerifyStateActivityTests.cs New test file with two test cases: one verifying the default AccountLinkingUrl is returned, another testing override capability
Libraries/Microsoft.Teams.Extensions/Microsoft.Teams.Extensions.Configuration/Microsoft.Teams.Apps.Extensions/TeamsSettings.cs Added AccountLinkingUrl property and Apply method logic to configure it; improved Empty property implementation; added XML documentation
Libraries/Microsoft.Teams.Common/Http/HttpCredentials.cs Added XML documentation for the IHttpCredentials interface
Libraries/Microsoft.Teams.Apps/Routing/Router.cs Modified route selection to prioritize User routes over System routes using OrderBy
Libraries/Microsoft.Teams.Apps/OAuthSettings.cs Added AccountLinkingUrl property and converted from primary constructor to standard class with XML documentation
Libraries/Microsoft.Teams.Apps/Contexts/Context.cs Added new Next(IContext) overload to allow passing a different context through the handler chain
Libraries/Microsoft.Teams.Apps/AppRouting.cs Updated verify state handler to return accountLinkingUrl in response body when configured
Libraries/Microsoft.Teams.Apps/AppOptions.cs Added comprehensive XML documentation for all properties
Libraries/Microsoft.Teams.Apps/AppBuilder.cs Added AddOAuth(OAuthSettings) overload and modified existing AddOAuth(string) to update rather than replace
Libraries/Microsoft.Teams.Api/Clients/UserTokenClient.cs Added parameterless constructor and made methods virtual for testability
Libraries/Microsoft.Teams.Api/Clients/UserClient.cs Made Token property virtual and added parameterless constructor
Libraries/Microsoft.Teams.Api/Clients/TeamClient.cs Made methods virtual for better testability
Libraries/Microsoft.Teams.Api/Clients/MemberClient.cs Made methods virtual for better testability
Libraries/Microsoft.Teams.Api/Clients/MeetingClient.cs Made methods virtual for better testability
Libraries/Microsoft.Teams.Api/Clients/ConversationClient.cs Changed Activities and Members from readonly fields to virtual properties
Libraries/Microsoft.Teams.Api/Clients/BotTokenClient.cs Changed constructor chain and made GetGraphAsync virtual
Libraries/Microsoft.Teams.Api/Clients/BotSignInClient.cs Made methods virtual for better testability
Libraries/Microsoft.Teams.Api/Clients/BotClient.cs Made SignIn property virtual
Libraries/Microsoft.Teams.Api/Clients/ActivityClient.cs Made all public methods virtual for better testability
Libraries/Microsoft.Teams.Api/Auth/TokenCredentials.cs Added XML documentation and renamed unused parameter from _client to _
Libraries/Microsoft.Teams.Api/Auth/ClientCredentials.cs Added XML documentation with reference link

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +18 to +21
public UserTokenClient() : base()
{

}
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

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

The parameterless constructor calls base() which doesn't exist in the abstract Client class. The base Client class only has constructors that accept parameters (CancellationToken, IHttpClient, etc.). This will cause a compilation error. Either remove this constructor or ensure the base class has a parameterless constructor.

Copilot uses AI. Check for mistakes.
…s.Configuration/Microsoft.Teams.Apps.Extensions/TeamsSettings.cs

Co-authored-by: Copilot <[email protected]>
Copilot AI review requested due to automatic review settings January 9, 2026 16:39
rajan-chari and others added 4 commits January 9, 2026 11:40
…s.Configuration/Microsoft.Teams.Apps.Extensions/TeamsSettings.cs

Co-authored-by: Copilot <[email protected]>
…s.Configuration/Microsoft.Teams.Apps.Extensions/TeamsSettings.cs

Co-authored-by: Copilot <[email protected]>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 9 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

public static readonly string GraphScope = "https://graph.microsoft.com/.default";

public BotTokenClient() : this(default)
public BotTokenClient() : base()
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

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

This parameterless constructor calls base(), but the parent Client class does not have a parameterless constructor. This will cause a compilation error. The previous code was calling base(default) which correctly called the Client(CancellationToken) constructor. Either revert this change to base(default) or add a parameterless constructor to the Client base class.

Suggested change
public BotTokenClient() : base()
public BotTokenClient() : base(default)

Copilot uses AI. Check for mistakes.
Comment on lines +116 to 124
/// Called to continue the chain of route handlers using the specified context instance.
/// Use this overload when you want to invoke the next handler with a different or wrapped
/// <see cref="IContext{TActivity}"/> than the current one; if not called, no other handlers
/// in the sequence will be executed.
/// </summary>
/// <param name="context">The context to pass to the next handler in the chain.</param>
public Task<object?> Next(IContext<TActivity> context);

/// <summary>
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

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

The new Next(IContext<TActivity> context) overload appears redundant. It converts the passed context to IActivity type and calls OnNext, which is the same as what Next() does with the current context. In the test at line 70, context.Next(context) is called, which is functionally equivalent to just calling context.Next(). Consider removing this overload unless there's a specific use case where a different context instance needs to be passed.

Suggested change
/// Called to continue the chain of route handlers using the specified context instance.
/// Use this overload when you want to invoke the next handler with a different or wrapped
/// <see cref="IContext{TActivity}"/> than the current one; if not called, no other handlers
/// in the sequence will be executed.
/// </summary>
/// <param name="context">The context to pass to the next handler in the chain.</param>
public Task<object?> Next(IContext<TActivity> context);
/// <summary>

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +15
public virtual ActivityClient Activities { get; }
public virtual MemberClient Members { get; }
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

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

The Activities and Members properties are changed to virtual with auto-property syntax, but they are initialized in the constructor. This means they cannot be overridden in derived classes since they're set in the base constructor. Consider making them virtual with explicit backing fields, or ensure this pattern is intentional for mocking purposes only.

Copilot uses AI. Check for mistakes.
namespace Microsoft.Teams.Common.Http;

/// <summary>
/// Http Credential resolver used to fetch some access token.
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

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

There's a trailing space after "token." in the documentation comment.

Copilot uses AI. Check for mistakes.
Comment on lines +18 to +22
public UserTokenClient() : base()
{

}

Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

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

This parameterless constructor calls base(), but the parent Client class does not have a parameterless constructor. This will cause a compilation error. Either add a parameterless constructor to the Client base class, or remove this constructor if it's not needed.

Suggested change
public UserTokenClient() : base()
{
}

Copilot uses AI. Check for mistakes.
Comment on lines +12 to 17
public UserClient() : base()
{
Token = new UserTokenClient(_http, _cancellationToken);
}

public UserClient(CancellationToken cancellationToken = default) : base(cancellationToken)
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

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

This parameterless constructor calls base(), but the parent Client class does not have a parameterless constructor. This will cause a compilation error. Either add a parameterless constructor to the Client base class, or remove this constructor if it's not needed.

Suggested change
public UserClient() : base()
{
Token = new UserTokenClient(_http, _cancellationToken);
}
public UserClient(CancellationToken cancellationToken = default) : base(cancellationToken)
public UserClient(CancellationToken cancellationToken = default) : base(cancellationToken)
{
Token = new UserTokenClient(_http, cancellationToken);
}
public UserClient(IHttpClient client, CancellationToken cancellationToken = default) : base(client, cancellationToken)

Copilot uses AI. Check for mistakes.
activity = Activity;
reference = Ref;
client = new IContext.Client(ToActivityType());
}
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

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

The new Next(IContext<TActivity> context) overload appears redundant. It converts the passed context to IActivity type and calls OnNext, which is the same as what Next() does with the current context. In the test at line 70, context.Next(context) is called, which is functionally equivalent to just calling context.Next(). Consider removing this overload unless there's a specific use case where a different context instance needs to be passed.

Copilot uses AI. Check for mistakes.
public string? ClientId { get; set; }

/// <summary>
/// The secret (i.e. password) for your application.
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

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

The abbreviation "ie" should be "i.e." with periods for proper Latin abbreviation format.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

Comment on lines 26 to 29
return _routes
.OrderBy(route => route.Type == RouteType.User ? 0 : 1)
.Where(route => route.Select(activity))
.ToList();
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

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

The OrderBy operation is applied on every Select call, which could impact performance if there are many routes. Since routes are added through the Register method, consider ordering routes once during registration rather than on every selection. This would avoid repeated sorting operations.

Copilot uses AI. Check for mistakes.
- Fix redundant JwtSecurityTokenHandler creation in tests (reuse existing tokenHandler variable)
- Fix inconsistent assertion method (use Assert.Equivalent for anonymous object comparison)

All 561 tests pass

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
@rajan-chari
Copy link
Contributor

Addressed Remaining Copilot Review Comments ✅

I've synced with the latest changes and addressed the remaining review feedback:

Fixes Applied

  1. Redundant token handler creation (lines 59 & 109 in VerifyStateActivityTests.cs)

    • Changed: tokenHandler.WriteToken(new JwtSecurityTokenHandler().CreateToken(tokenDescriptor))
    • To: tokenHandler.WriteToken(tokenHandler.CreateToken(tokenDescriptor))
    • Reuses the existing tokenHandler variable instead of creating a new instance
  2. Inconsistent assertion method (line 136 in VerifyStateActivityTests.cs)

    • Changed: Assert.Equal(res.Body, new { accountLinkingUrl = "test_linking_url" })
    • To: Assert.Equivalent(res.Body, new { accountLinkingUrl = "test_linking_url" })
    • Uses Assert.Equivalent for proper anonymous object value comparison

Already Fixed in Previous Commits

The following issues were already addressed in earlier commits:

  • ✅ Logic error in TeamsSettings.cs (AccountLinkingUrl condition)
  • ✅ Documentation spelling: "ie" → "i.e."
  • ✅ Documentation capitalization: "true" → "True"

Testing

✅ Build: Success
✅ Tests: All 561 tests pass

All Copilot review comments have been addressed. Ready for final review and merge.

Copy link
Contributor

Copilot AI commented Jan 9, 2026

@rajan-chari I've opened a new pull request, #263, to work on those changes. Once the pull request is ready, I'll request review from you.

@rajan-chari
Copy link
Contributor

@copilot open a new pull request to apply changes based on the comments in this thread

Copy link
Contributor

Copilot AI commented Jan 9, 2026

@rajan-chari I've opened a new pull request, #264, to work on those changes. Once the pull request is ready, I'll request review from you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants