Skip to content

Add explicit unauthenticated request bypass#596

Open
heyitsaamir wants to merge 4 commits into
mainfrom
heyitsaamir-skip-auth-authorization
Open

Add explicit unauthenticated request bypass#596
heyitsaamir wants to merge 4 commits into
mainfrom
heyitsaamir-skip-auth-authorization

Conversation

@heyitsaamir

@heyitsaamir heyitsaamir commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Deprecate skipAuth in favor of DangerouslyAllowUnauthenticatedRequests while preserving compatibility.
  • Allow unauthenticated /api/messages requests without parsing a bearer token when the dangerous bypass is enabled in the AspNetCore plugin path. This currently prevents 2.0 from being used for agents playground.
  • Support DangerouslyAllowUnauthenticatedRequests via Teams config/env and the core TeamsBotApplication auth path, including AzureAd section support.

Behavior

  • When DangerouslyAllowUnauthenticatedRequests is enabled, unauthenticated requests are accepted without requiring or parsing the Authorization header.
  • When ClientId is missing and the dangerous bypass is not enabled, requests are rejected with 401 ProblemDetails containing only the generic Authentication not configured title/status.
  • The remediation guidance to configure ClientId or enable DangerouslyAllowUnauthenticatedRequests is logged server-side and is not returned in the HTTP response body.

Validation

  • dotnet test Tests/Microsoft.Teams.Plugins.AspNetCore.Tests/Microsoft.Teams.Plugins.AspNetCore.Tests.csproj --verbosity minimal
  • dotnet test core/test/Microsoft.Teams.Core.UnitTests/Microsoft.Teams.Core.UnitTests.csproj --verbosity minimal
  • dotnet build Microsoft.Teams.sln --verbosity minimal

Deprecate skipAuth in favor of DangerouslyAllowUnauthenticatedRequests and wire the setting through both AspNetCorePlugin and core TeamsBotApplication auth paths. Support configuration via Teams/AzureAd sections and keep backward compatibility for skipAuth callers.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 9, 2026 18:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 introduces an explicit “dangerous” authentication bypass switch (replacing the older skipAuth) and wires it through both the AspNetCore plugin path and the core TeamsBotApplication / AzureAd configuration path, so local-development scenarios can accept /api/messages requests without attempting bearer-token parsing.

Changes:

  • Deprecates skipAuth in favor of DangerouslyAllowUnauthenticatedRequests across AspNetCore hosting extension overloads (with compatibility preserved).
  • Updates AspNetCore plugin request handling to optionally skip token extraction and use an UnauthenticatedToken when the bypass is enabled.
  • Extends configuration support for the bypass flag via Teams and AzureAd sections (including fallback/override behavior) with added unit tests.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
Tests/Microsoft.Teams.Plugins.AspNetCore.Tests/Extensions/HostApplicationBuilderTests.cs Updates/extends tests around the new bypass flag and deprecated skipAuth path.
Tests/Microsoft.Teams.Plugins.AspNetCore.Tests/AspNetCorePluginTests.cs Adds a test covering bypass behavior when no Authorization header is present.
Libraries/Microsoft.Teams.Plugins/Microsoft.Teams.Plugins.AspNetCore/UnauthenticatedToken.cs Introduces an IToken implementation for bypassed (unauthenticated) requests.
Libraries/Microsoft.Teams.Plugins/Microsoft.Teams.Plugins.AspNetCore/Extensions/HostApplicationBuilder.cs Refactors hosting extension overloads and adds the new bypass option/config plumbing.
Libraries/Microsoft.Teams.Plugins/Microsoft.Teams.Plugins.AspNetCore/AspNetCorePluginOptions.cs Adds the DangerouslyAllowUnauthenticatedRequests option and an obsolete SkipAuth alias.
Libraries/Microsoft.Teams.Plugins/Microsoft.Teams.Plugins.AspNetCore/AspNetCorePlugin.cs Skips token parsing under bypass and supplies an unauthenticated token to downstream handlers.
Libraries/Microsoft.Teams.Extensions/Microsoft.Teams.Extensions.Configuration/Microsoft.Teams.Apps.Extensions/TeamsSettings.cs Adds TeamsSettings.DangerouslyAllowUnauthenticatedRequests configuration binding support.
core/test/Microsoft.Teams.Core.UnitTests/Hosting/JwtExtensionsTests.cs Adds coverage for bypass authentication behavior in the core hosting path.
core/test/Microsoft.Teams.Core.UnitTests/Hosting/BotConfigTests.cs Adds tests for config-section precedence and validation of the bypass flag.
core/src/Microsoft.Teams.Core/Hosting/JwtExtensions.cs Enables bypass authentication when the new flag is set (even if ClientId exists).
core/src/Microsoft.Teams.Core/Hosting/BotConfig.cs Adds the bypass flag to resolved bot config and supports Teams-section fallback parsing.

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

Comment on lines 53 to +61
var services = builder.Build().Services;
var authOptions = services.GetRequiredService<IAuthorizationPolicyProvider>();
var pluginOptions = services.GetRequiredService<AspNetCorePluginOptions>();

var policy = await authOptions.GetPolicyAsync(TeamsTokenAuthConstants.AuthorizationPolicy);

Assert.NotNull(policy);
Assert.True(policy.Requirements.OfType<AssertionRequirement>().Any());
Assert.False(pluginOptions.DangerouslyAllowUnauthenticatedRequests);
Comment on lines +74 to +83
var services = builder.Build().Services;
var authOptions = services.GetRequiredService<IAuthorizationPolicyProvider>();
var pluginOptions = services.GetRequiredService<AspNetCorePluginOptions>();

var policy = await authOptions.GetPolicyAsync(TeamsTokenAuthConstants.AuthorizationPolicy);

// Should allow all requests
Assert.NotNull(policy);
Assert.True(policy.Requirements.OfType<AssertionRequirement>().Any());
Assert.True(pluginOptions.DangerouslyAllowUnauthenticatedRequests);
Comment on lines +97 to +105
var services = builder.Build().Services;
var authOptions = services.GetRequiredService<IAuthorizationPolicyProvider>();
var pluginOptions = services.GetRequiredService<AspNetCorePluginOptions>();

var policy = await authOptions.GetPolicyAsync(TeamsTokenAuthConstants.AuthorizationPolicy);

Assert.NotNull(policy);
Assert.True(policy.Requirements.OfType<AssertionRequirement>().Any());
Assert.True(pluginOptions.DangerouslyAllowUnauthenticatedRequests);
Comment on lines 120 to +128
var services = builder.Build().Services;
var authOptions = services.GetRequiredService<IAuthorizationPolicyProvider>();
var pluginOptions = services.GetRequiredService<AspNetCorePluginOptions>();

var policy = await authOptions.GetPolicyAsync(TeamsTokenAuthConstants.AuthorizationPolicy);

// Should allow all requests
Assert.NotNull(policy);
Assert.True(policy.Requirements.OfType<AssertionRequirement>().Any());
Assert.True(pluginOptions.DangerouslyAllowUnauthenticatedRequests);
Comment on lines 185 to 189
var request = httpContext.Request;
var token = ExtractToken(request);
var options = httpContext.RequestServices?.GetService(typeof(AspNetCorePluginOptions)) as AspNetCorePluginOptions;
var dangerouslyAllowUnauthenticatedRequests = options?.DangerouslyAllowUnauthenticatedRequests == true;
var token = dangerouslyAllowUnauthenticatedRequests ? null : ExtractToken(request);
var activity = await ParseActivity(request).ConfigureAwait(false);
heyitsaamir and others added 3 commits July 9, 2026 11:11
Require the dangerous unauthenticated bypass flag before bypassing inbound bot authentication. When ClientId is absent without the flag, register an auth scheme that challenges with a 401 JSON error instead of accepting requests.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Return the missing-auth-configuration challenge through ASP.NET Core Results.Problem instead of writing JSON manually, and assert the ProblemDetails response shape.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Log remediation guidance server-side while returning only a generic ProblemDetails title to clients.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Comment on lines +146 to +152
if (bool.TryParse(value, out bool result))
{
return result;
}

throw new InvalidOperationException(
$"Configuration value '{section.Path}:{key}' is not a valid boolean: '{value}'.");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This behavior diverges from PY and TS, right? Do we want parity?

Comment on lines +1 to +22
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.Teams.Api.Auth;

namespace Microsoft.Teams.Plugins.AspNetCore;

internal sealed class UnauthenticatedToken(string? serviceUrl) : IToken
{
public string? AppId => null;
public string? AppDisplayName => null;
public string? TenantId => null;
public string ServiceUrl { get; } = string.IsNullOrEmpty(serviceUrl)
? "https://smba.trafficmanager.net/teams/"
: serviceUrl.EndsWith('/') ? serviceUrl : $"{serviceUrl}/";
public CallerType From => CallerType.Azure;
public string FromId => "urn:botframework:azure";
public DateTime? Expiration => null;
public bool IsExpired => false;
public IEnumerable<string> Scopes => [];
public override string ToString() => string.Empty;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This synthesizes trafficmanager when the activity has no service url, but TS and PY do not. Should all three have matching behavior here?

@MehakBindra MehakBindra left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we keep this change to 2.0 ?
EDIT - resolved offline


internal const string BotFrameworkSectionName = "BotFramework";

private const string TeamsSectionName = "Teams";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we keep only one section ? and the default remains AzureAD

return Task.FromResult(AuthenticateResult.Fail("Authentication not configured"));
}

protected override async Task HandleChallengeAsync(AuthenticationProperties properties)

@MehakBindra MehakBindra Jul 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This leaves us with no startup-time warning log and defers it to when a request is actually received .


internal const string BotFrameworkSectionName = "BotFramework";

private const string TeamsSectionName = "Teams";

@singhk97 singhk97 Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

we don't need a new section imo, we can simply support the key at the root (or default section)

if (string.IsNullOrWhiteSpace(clientId))
{
builder.AddBypassAuthentication(schemeName, logger);
builder.AddAuthenticationNotConfigured(schemeName);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

same logic should also be applied to this overload

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