From 4618b06e350a75336cfad56f589224929e4f8a7a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 21 Jan 2026 17:00:22 +0000
Subject: [PATCH 1/6] Initial plan
From e4c09d2080d3da6462a55a57d39326f6a6153eaf Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 21 Jan 2026 17:07:49 +0000
Subject: [PATCH 2/6] Updated plan with new requirements
Co-authored-by: JerryNixon <1749983+JerryNixon@users.noreply.github.com>
---
src/Config/ObjectModel/AuthenticationOptions.cs | 10 +++++++++-
.../AuthenticationHelpers/SupportedAuthNProviders.cs | 2 ++
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/src/Config/ObjectModel/AuthenticationOptions.cs b/src/Config/ObjectModel/AuthenticationOptions.cs
index a937168493..036f55876f 100644
--- a/src/Config/ObjectModel/AuthenticationOptions.cs
+++ b/src/Config/ObjectModel/AuthenticationOptions.cs
@@ -32,9 +32,17 @@ public record AuthenticationOptions(string Provider = nameof(EasyAuthType.AppSer
/// True when development mode should authenticate all requests.
public bool IsAuthenticationSimulatorEnabled() => Provider.Equals(SIMULATOR_AUTHENTICATION, StringComparison.OrdinalIgnoreCase);
+ public const string UNAUTHENTICATED_AUTHENTICATION = "Unauthenticated";
+
+ ///
+ /// Returns whether the configured Provider value matches the unauthenticated authentication type.
+ ///
+ /// True when all operations run as anonymous.
+ public bool IsUnauthenticatedAuthenticationProvider() => Provider.Equals(UNAUTHENTICATED_AUTHENTICATION, StringComparison.OrdinalIgnoreCase);
+
///
/// A shorthand method to determine whether JWT is configured for the current authentication provider.
///
/// True if the provider is enabled for JWT, otherwise false.
- public bool IsJwtConfiguredIdentityProvider() => !IsEasyAuthAuthenticationProvider() && !IsAuthenticationSimulatorEnabled();
+ public bool IsJwtConfiguredIdentityProvider() => !IsEasyAuthAuthenticationProvider() && !IsAuthenticationSimulatorEnabled() && !IsUnauthenticatedAuthenticationProvider();
};
diff --git a/src/Core/AuthenticationHelpers/SupportedAuthNProviders.cs b/src/Core/AuthenticationHelpers/SupportedAuthNProviders.cs
index 70a6809074..cc543ee28e 100644
--- a/src/Core/AuthenticationHelpers/SupportedAuthNProviders.cs
+++ b/src/Core/AuthenticationHelpers/SupportedAuthNProviders.cs
@@ -14,4 +14,6 @@ internal static class SupportedAuthNProviders
public const string SIMULATOR = "Simulator";
public const string STATIC_WEB_APPS = "StaticWebApps";
+
+ public const string UNAUTHENTICATED = "Unauthenticated";
}
From f63c338416d928e0316359c039438e8cb0c552b3 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 21 Jan 2026 17:19:23 +0000
Subject: [PATCH 3/6] Add Unauthenticated authentication provider with CLI
support, tests, and JSON schema
Co-authored-by: JerryNixon <1749983+JerryNixon@users.noreply.github.com>
---
global.json | 2 +-
schemas/dab.draft.schema.json | 4 ++
src/Cli.Tests/InitTests.cs | 1 +
...ionProviders_47836da0dfbdc458.verified.txt | 50 +++++++++++++++++++
src/Cli.Tests/ValidateConfigTests.cs | 33 ++++++++++++
src/Cli/ConfigGenerator.cs | 22 ++++++++
src/Cli/Utils.cs | 7 +--
...nticatedAuthenticationBuilderExtensions.cs | 34 +++++++++++++
.../UnauthenticatedAuthenticationDefaults.cs | 15 ++++++
.../UnauthenticatedAuthenticationHandler.cs | 49 ++++++++++++++++++
src/Service/Startup.cs | 11 +++-
11 files changed, 222 insertions(+), 6 deletions(-)
create mode 100644 src/Cli.Tests/Snapshots/InitTests.EnsureCorrectConfigGenerationWithDifferentAuthenticationProviders_47836da0dfbdc458.verified.txt
create mode 100644 src/Core/AuthenticationHelpers/UnauthenticatedAuthenticationHandler/UnauthenticatedAuthenticationBuilderExtensions.cs
create mode 100644 src/Core/AuthenticationHelpers/UnauthenticatedAuthenticationHandler/UnauthenticatedAuthenticationDefaults.cs
create mode 100644 src/Core/AuthenticationHelpers/UnauthenticatedAuthenticationHandler/UnauthenticatedAuthenticationHandler.cs
diff --git a/global.json b/global.json
index 1bdb496ef0..285b9d3725 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "8.0.417",
+ "version": "8.0.416",
"rollForward": "latestFeature"
}
}
diff --git a/schemas/dab.draft.schema.json b/schemas/dab.draft.schema.json
index b684cc28ac..b441b57303 100644
--- a/schemas/dab.draft.schema.json
+++ b/schemas/dab.draft.schema.json
@@ -368,6 +368,10 @@
{
"const": "Custom",
"description": "Custom authentication provider defined by the user. Use the JWT property to configure the custom provider."
+ },
+ {
+ "const": "Unauthenticated",
+ "description": "Unauthenticated provider where all operations run as anonymous. Use when Data API builder is behind an app gateway or APIM where authentication is handled externally."
}
],
"default": "AppService"
diff --git a/src/Cli.Tests/InitTests.cs b/src/Cli.Tests/InitTests.cs
index 051bfdf7a7..96ba1ad66b 100644
--- a/src/Cli.Tests/InitTests.cs
+++ b/src/Cli.Tests/InitTests.cs
@@ -301,6 +301,7 @@ public void EnsureFailureOnReInitializingExistingConfig()
[DataRow("StaticWebApps", null, null, DisplayName = "StaticWebApps with no audience and no issuer specified.")]
[DataRow("AppService", null, null, DisplayName = "AppService with no audience and no issuer specified.")]
[DataRow("Simulator", null, null, DisplayName = "Simulator with no audience and no issuer specified.")]
+ [DataRow("Unauthenticated", null, null, DisplayName = "Unauthenticated with no audience and no issuer specified.")]
[DataRow("AzureAD", "aud-xxx", "issuer-xxx", DisplayName = "AzureAD with both audience and issuer specified.")]
[DataRow("EntraID", "aud-xxx", "issuer-xxx", DisplayName = "EntraID with both audience and issuer specified.")]
public Task EnsureCorrectConfigGenerationWithDifferentAuthenticationProviders(
diff --git a/src/Cli.Tests/Snapshots/InitTests.EnsureCorrectConfigGenerationWithDifferentAuthenticationProviders_47836da0dfbdc458.verified.txt b/src/Cli.Tests/Snapshots/InitTests.EnsureCorrectConfigGenerationWithDifferentAuthenticationProviders_47836da0dfbdc458.verified.txt
new file mode 100644
index 0000000000..55843cf207
--- /dev/null
+++ b/src/Cli.Tests/Snapshots/InitTests.EnsureCorrectConfigGenerationWithDifferentAuthenticationProviders_47836da0dfbdc458.verified.txt
@@ -0,0 +1,50 @@
+{
+ DataSource: {
+ DatabaseType: MSSQL,
+ Options: {
+ set-session-context: false
+ }
+ },
+ Runtime: {
+ Rest: {
+ Enabled: true,
+ Path: /api,
+ RequestBodyStrict: true
+ },
+ GraphQL: {
+ Enabled: true,
+ Path: /graphql,
+ AllowIntrospection: true
+ },
+ Mcp: {
+ Enabled: true,
+ Path: /mcp,
+ DmlTools: {
+ AllToolsEnabled: true,
+ DescribeEntities: true,
+ CreateRecord: true,
+ ReadRecords: true,
+ UpdateRecord: true,
+ DeleteRecord: true,
+ ExecuteEntity: true,
+ UserProvidedAllTools: false,
+ UserProvidedDescribeEntities: false,
+ UserProvidedCreateRecord: false,
+ UserProvidedReadRecords: false,
+ UserProvidedUpdateRecord: false,
+ UserProvidedDeleteRecord: false,
+ UserProvidedExecuteEntity: false
+ }
+ },
+ Host: {
+ Cors: {
+ AllowCredentials: false
+ },
+ Authentication: {
+ Provider: Unauthenticated
+ },
+ Mode: Production
+ }
+ },
+ Entities: []
+}
diff --git a/src/Cli.Tests/ValidateConfigTests.cs b/src/Cli.Tests/ValidateConfigTests.cs
index e40a32e291..a92ac07c2b 100644
--- a/src/Cli.Tests/ValidateConfigTests.cs
+++ b/src/Cli.Tests/ValidateConfigTests.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using Azure.DataApiBuilder.Config.ObjectModel;
using Azure.DataApiBuilder.Core.Configurations;
using Azure.DataApiBuilder.Core.Models;
using Serilog;
@@ -359,4 +360,36 @@ private async Task ValidatePropertyOptionsFails(ConfigureOptions options)
JsonSchemaValidationResult result = await validator.ValidateConfigSchema(config, TEST_RUNTIME_CONFIG_FILE, mockLoggerFactory.Object);
Assert.IsFalse(result.IsValid);
}
+
+ ///
+ /// Test that the Unauthenticated provider is correctly identified by the IsUnauthenticatedAuthenticationProvider method.
+ ///
+ [TestMethod]
+ public void TestIsUnauthenticatedAuthenticationProviderMethod()
+ {
+ // Test with Unauthenticated provider
+ AuthenticationOptions unauthenticatedOptions = new(Provider: "Unauthenticated");
+ Assert.IsTrue(unauthenticatedOptions.IsUnauthenticatedAuthenticationProvider());
+
+ // Test case-insensitivity
+ AuthenticationOptions unauthenticatedOptionsLower = new(Provider: "unauthenticated");
+ Assert.IsTrue(unauthenticatedOptionsLower.IsUnauthenticatedAuthenticationProvider());
+
+ // Test that other providers are not identified as Unauthenticated
+ AuthenticationOptions appServiceOptions = new(Provider: "AppService");
+ Assert.IsFalse(appServiceOptions.IsUnauthenticatedAuthenticationProvider());
+
+ AuthenticationOptions simulatorOptions = new(Provider: "Simulator");
+ Assert.IsFalse(simulatorOptions.IsUnauthenticatedAuthenticationProvider());
+ }
+
+ ///
+ /// Test that Unauthenticated provider does not require JWT configuration.
+ ///
+ [TestMethod]
+ public void TestUnauthenticatedProviderDoesNotRequireJwt()
+ {
+ AuthenticationOptions unauthenticatedOptions = new(Provider: "Unauthenticated");
+ Assert.IsFalse(unauthenticatedOptions.IsJwtConfiguredIdentityProvider());
+ }
}
diff --git a/src/Cli/ConfigGenerator.cs b/src/Cli/ConfigGenerator.cs
index 648edc1950..e0e36a1851 100644
--- a/src/Cli/ConfigGenerator.cs
+++ b/src/Cli/ConfigGenerator.cs
@@ -2444,6 +2444,28 @@ public static bool IsConfigValid(ValidateOptions options, FileSystemRuntimeConfi
}
}
}
+
+ // Warn if Unauthenticated provider is used with authenticated or custom roles
+ if (config.Runtime?.Host?.Authentication?.IsUnauthenticatedAuthenticationProvider() == true)
+ {
+ foreach (KeyValuePair entity in config.Entities)
+ {
+ if (entity.Value.Permissions is not null)
+ {
+ foreach (EntityPermission permission in entity.Value.Permissions)
+ {
+ if (!permission.Role.Equals("anonymous", StringComparison.OrdinalIgnoreCase))
+ {
+ _logger.LogWarning(
+ "Entity '{EntityName}' has permission configured for role '{Role}' but authentication provider is 'Unauthenticated'. " +
+ "All requests will be treated as anonymous.",
+ entity.Key,
+ permission.Role);
+ }
+ }
+ }
+ }
+ }
}
}
diff --git a/src/Cli/Utils.cs b/src/Cli/Utils.cs
index 48edd4411c..28d61e18b8 100644
--- a/src/Cli/Utils.cs
+++ b/src/Cli/Utils.cs
@@ -516,11 +516,12 @@ public static bool ValidateAudienceAndIssuerForJwtProvider(
string? issuer)
{
if (Enum.TryParse(authenticationProvider, ignoreCase: true, out _)
- || AuthenticationOptions.SIMULATOR_AUTHENTICATION == authenticationProvider)
+ || AuthenticationOptions.SIMULATOR_AUTHENTICATION == authenticationProvider
+ || AuthenticationOptions.UNAUTHENTICATED_AUTHENTICATION.Equals(authenticationProvider, StringComparison.OrdinalIgnoreCase))
{
if (!(string.IsNullOrWhiteSpace(audience)) || !(string.IsNullOrWhiteSpace(issuer)))
{
- _logger.LogWarning("Audience and Issuer can't be set for EasyAuth or Simulator authentication.");
+ _logger.LogWarning("Audience and Issuer can't be set for EasyAuth, Simulator, or Unauthenticated authentication.");
return true;
}
}
@@ -528,7 +529,7 @@ public static bool ValidateAudienceAndIssuerForJwtProvider(
{
if (string.IsNullOrWhiteSpace(audience) || string.IsNullOrWhiteSpace(issuer))
{
- _logger.LogError($"Authentication providers other than EasyAuth and Simulator require both Audience and Issuer.");
+ _logger.LogError($"Authentication providers other than EasyAuth, Simulator, and Unauthenticated require both Audience and Issuer.");
return false;
}
}
diff --git a/src/Core/AuthenticationHelpers/UnauthenticatedAuthenticationHandler/UnauthenticatedAuthenticationBuilderExtensions.cs b/src/Core/AuthenticationHelpers/UnauthenticatedAuthenticationHandler/UnauthenticatedAuthenticationBuilderExtensions.cs
new file mode 100644
index 0000000000..acb69d1755
--- /dev/null
+++ b/src/Core/AuthenticationHelpers/UnauthenticatedAuthenticationHandler/UnauthenticatedAuthenticationBuilderExtensions.cs
@@ -0,0 +1,34 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.AspNetCore.Authentication;
+
+namespace Azure.DataApiBuilder.Core.AuthenticationHelpers.UnauthenticatedAuthenticationHandler;
+
+///
+/// Extension methods related to Unauthenticated authentication.
+/// This class allows setting up Unauthenticated authentication in the startup class with
+/// a single call to .AddAuthentiction(scheme).AddUnauthenticatedAuthentication()
+///
+public static class UnauthenticatedAuthenticationBuilderExtensions
+{
+ ///
+ /// Add authentication with Unauthenticated provider.
+ ///
+ /// Authentication builder.
+ /// The builder, to chain commands.
+ public static AuthenticationBuilder AddUnauthenticatedAuthentication(this AuthenticationBuilder builder)
+ {
+ if (builder is null)
+ {
+ throw new System.ArgumentNullException(nameof(builder));
+ }
+
+ builder.AddScheme(
+ authenticationScheme: UnauthenticatedAuthenticationDefaults.AUTHENTICATIONSCHEME,
+ displayName: UnauthenticatedAuthenticationDefaults.AUTHENTICATIONSCHEME,
+ configureOptions: null);
+
+ return builder;
+ }
+}
diff --git a/src/Core/AuthenticationHelpers/UnauthenticatedAuthenticationHandler/UnauthenticatedAuthenticationDefaults.cs b/src/Core/AuthenticationHelpers/UnauthenticatedAuthenticationHandler/UnauthenticatedAuthenticationDefaults.cs
new file mode 100644
index 0000000000..03f8222ecd
--- /dev/null
+++ b/src/Core/AuthenticationHelpers/UnauthenticatedAuthenticationHandler/UnauthenticatedAuthenticationDefaults.cs
@@ -0,0 +1,15 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace Azure.DataApiBuilder.Core.AuthenticationHelpers.UnauthenticatedAuthenticationHandler;
+
+///
+/// Default values related to UnauthenticatedAuthentication handler.
+///
+public static class UnauthenticatedAuthenticationDefaults
+{
+ ///
+ /// The default value used for UnauthenticatedAuthenticationOptions.AuthenticationScheme.
+ ///
+ public const string AUTHENTICATIONSCHEME = "UnauthenticatedAuthentication";
+}
diff --git a/src/Core/AuthenticationHelpers/UnauthenticatedAuthenticationHandler/UnauthenticatedAuthenticationHandler.cs b/src/Core/AuthenticationHelpers/UnauthenticatedAuthenticationHandler/UnauthenticatedAuthenticationHandler.cs
new file mode 100644
index 0000000000..552e44b228
--- /dev/null
+++ b/src/Core/AuthenticationHelpers/UnauthenticatedAuthenticationHandler/UnauthenticatedAuthenticationHandler.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Security.Claims;
+using System.Text.Encodings.Web;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+namespace Azure.DataApiBuilder.Core.AuthenticationHelpers.UnauthenticatedAuthenticationHandler;
+
+///
+/// This class is used to best integrate with ASP.NET Core AuthenticationHandler base class.
+/// When "Unauthenticated" is configured, this handler authenticates the user as anonymous,
+/// without reading any HTTP authentication headers.
+///
+public class UnauthenticatedAuthenticationHandler : AuthenticationHandler
+{
+ ///
+ /// Constructor for the UnauthenticatedAuthenticationHandler.
+ /// Note the parameters are required by the base class.
+ ///
+ /// Authentication options.
+ /// Logger factory.
+ /// URL encoder.
+ public UnauthenticatedAuthenticationHandler(
+ IOptionsMonitor options,
+ ILoggerFactory logger,
+ UrlEncoder encoder)
+ : base(options, logger, encoder)
+ {
+ }
+
+ ///
+ /// Returns an unauthenticated ClaimsPrincipal for all requests.
+ /// The ClaimsPrincipal has no identity and no claims, representing an anonymous user.
+ ///
+ /// An authentication result to ASP.NET Core library authentication mechanisms
+ protected override Task HandleAuthenticateAsync()
+ {
+ // ClaimsIdentity without authenticationType means the user is not authenticated (anonymous)
+ ClaimsIdentity identity = new();
+ ClaimsPrincipal claimsPrincipal = new(identity);
+
+ AuthenticationTicket ticket = new(claimsPrincipal, UnauthenticatedAuthenticationDefaults.AUTHENTICATIONSCHEME);
+ AuthenticateResult success = AuthenticateResult.Success(ticket);
+ return Task.FromResult(success);
+ }
+}
diff --git a/src/Service/Startup.cs b/src/Service/Startup.cs
index 333bf57234..e61154fc90 100644
--- a/src/Service/Startup.cs
+++ b/src/Service/Startup.cs
@@ -13,6 +13,7 @@
using Azure.DataApiBuilder.Config.Utilities;
using Azure.DataApiBuilder.Core.AuthenticationHelpers;
using Azure.DataApiBuilder.Core.AuthenticationHelpers.AuthenticationSimulator;
+using Azure.DataApiBuilder.Core.AuthenticationHelpers.UnauthenticatedAuthenticationHandler;
using Azure.DataApiBuilder.Core.Authorization;
using Azure.DataApiBuilder.Core.Configurations;
using Azure.DataApiBuilder.Core.Models;
@@ -772,7 +773,7 @@ private void ConfigureAuthentication(IServiceCollection services, RuntimeConfigP
{
AuthenticationOptions authOptions = runtimeConfig.Runtime.Host.Authentication;
HostMode mode = runtimeConfig.Runtime.Host.Mode;
- if (!authOptions.IsAuthenticationSimulatorEnabled() && !authOptions.IsEasyAuthAuthenticationProvider())
+ if (!authOptions.IsAuthenticationSimulatorEnabled() && !authOptions.IsEasyAuthAuthenticationProvider() && !authOptions.IsUnauthenticatedAuthenticationProvider())
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
@@ -809,6 +810,11 @@ private void ConfigureAuthentication(IServiceCollection services, RuntimeConfigP
_logger.LogInformation("Registered EasyAuth scheme: {Scheme}", defaultScheme);
}
+ else if (authOptions.IsUnauthenticatedAuthenticationProvider())
+ {
+ services.AddAuthentication(UnauthenticatedAuthenticationDefaults.AUTHENTICATIONSCHEME)
+ .AddUnauthenticatedAuthentication();
+ }
else if (mode == HostMode.Development && authOptions.IsAuthenticationSimulatorEnabled())
{
services.AddAuthentication(SimulatorAuthenticationDefaults.AUTHENTICATIONSCHEME)
@@ -850,7 +856,8 @@ private static void ConfigureAuthenticationV2(IServiceCollection services, Runti
services.AddAuthentication()
.AddEnvDetectedEasyAuth()
.AddJwtBearer()
- .AddSimulatorAuthentication();
+ .AddSimulatorAuthentication()
+ .AddUnauthenticatedAuthentication();
}
///
From 1a2f6337c54fffcb091340956cdafce3489d30d5 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 21 Jan 2026 17:24:47 +0000
Subject: [PATCH 4/6] Fix typo in
UnauthenticatedAuthenticationBuilderExtensions comment
Co-authored-by: JerryNixon <1749983+JerryNixon@users.noreply.github.com>
---
global.json | 2 +-
.../UnauthenticatedAuthenticationBuilderExtensions.cs | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/global.json b/global.json
index 285b9d3725..1bdb496ef0 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "8.0.416",
+ "version": "8.0.417",
"rollForward": "latestFeature"
}
}
diff --git a/src/Core/AuthenticationHelpers/UnauthenticatedAuthenticationHandler/UnauthenticatedAuthenticationBuilderExtensions.cs b/src/Core/AuthenticationHelpers/UnauthenticatedAuthenticationHandler/UnauthenticatedAuthenticationBuilderExtensions.cs
index acb69d1755..6cf6b79e94 100644
--- a/src/Core/AuthenticationHelpers/UnauthenticatedAuthenticationHandler/UnauthenticatedAuthenticationBuilderExtensions.cs
+++ b/src/Core/AuthenticationHelpers/UnauthenticatedAuthenticationHandler/UnauthenticatedAuthenticationBuilderExtensions.cs
@@ -8,7 +8,7 @@ namespace Azure.DataApiBuilder.Core.AuthenticationHelpers.UnauthenticatedAuthent
///
/// Extension methods related to Unauthenticated authentication.
/// This class allows setting up Unauthenticated authentication in the startup class with
-/// a single call to .AddAuthentiction(scheme).AddUnauthenticatedAuthentication()
+/// a single call to .AddAuthentication(scheme).AddUnauthenticatedAuthentication()
///
public static class UnauthenticatedAuthenticationBuilderExtensions
{
From b6d0fd504a6ac1505eefcbc3efc8becd16730f85 Mon Sep 17 00:00:00 2001
From: Jerry Nixon <1749983+JerryNixon@users.noreply.github.com>
Date: Thu, 22 Jan 2026 09:39:03 -0700
Subject: [PATCH 5/6] Update src/Cli/Utils.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
src/Cli/Utils.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Cli/Utils.cs b/src/Cli/Utils.cs
index 28d61e18b8..c1ff7f2a99 100644
--- a/src/Cli/Utils.cs
+++ b/src/Cli/Utils.cs
@@ -516,7 +516,7 @@ public static bool ValidateAudienceAndIssuerForJwtProvider(
string? issuer)
{
if (Enum.TryParse(authenticationProvider, ignoreCase: true, out _)
- || AuthenticationOptions.SIMULATOR_AUTHENTICATION == authenticationProvider
+ || AuthenticationOptions.SIMULATOR_AUTHENTICATION.Equals(authenticationProvider, StringComparison.OrdinalIgnoreCase)
|| AuthenticationOptions.UNAUTHENTICATED_AUTHENTICATION.Equals(authenticationProvider, StringComparison.OrdinalIgnoreCase))
{
if (!(string.IsNullOrWhiteSpace(audience)) || !(string.IsNullOrWhiteSpace(issuer)))
From e3fb034c39a2a8a11ba6fadf00ef36eeec504ba0 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 22 Jan 2026 16:44:08 +0000
Subject: [PATCH 6/6] Address PR review comments: add Unauthenticated scheme
mapping, use explicit Where() filtering, add unit tests
Co-authored-by: JerryNixon <1749983+JerryNixon@users.noreply.github.com>
---
src/Cli.Tests/ValidateConfigTests.cs | 22 +++++++++++++++++++
src/Cli/ConfigGenerator.cs | 20 ++++++-----------
...lientRoleHeaderAuthenticationMiddleware.cs | 5 +++++
3 files changed, 34 insertions(+), 13 deletions(-)
diff --git a/src/Cli.Tests/ValidateConfigTests.cs b/src/Cli.Tests/ValidateConfigTests.cs
index a92ac07c2b..03cac6de82 100644
--- a/src/Cli.Tests/ValidateConfigTests.cs
+++ b/src/Cli.Tests/ValidateConfigTests.cs
@@ -392,4 +392,26 @@ public void TestUnauthenticatedProviderDoesNotRequireJwt()
AuthenticationOptions unauthenticatedOptions = new(Provider: "Unauthenticated");
Assert.IsFalse(unauthenticatedOptions.IsJwtConfiguredIdentityProvider());
}
+
+ ///
+ /// Test that entities with non-anonymous roles are correctly identified when
+ /// Unauthenticated provider is configured. This validates the core detection logic
+ /// used by IsConfigValid to emit warnings.
+ ///
+ [DataTestMethod]
+ [DataRow("authenticated", true, DisplayName = "Authenticated role should be flagged as non-anonymous")]
+ [DataRow("customRole", true, DisplayName = "Custom role should be flagged as non-anonymous")]
+ [DataRow("anonymous", false, DisplayName = "Anonymous role should not be flagged")]
+ [DataRow("Anonymous", false, DisplayName = "Anonymous role (case-insensitive) should not be flagged")]
+ public void TestUnauthenticatedProviderNonAnonymousRoleDetection(string role, bool shouldWarn)
+ {
+ // Arrange: Create an entity permission with the specified role
+ EntityPermission permission = new(Role: role, Actions: new EntityAction[] { new(Action: EntityActionOperation.Read, Fields: null, Policy: null) });
+
+ // Act: Check if the role is non-anonymous (the logic used in IsConfigValid)
+ bool isNonAnonymous = !permission.Role.Equals("anonymous", StringComparison.OrdinalIgnoreCase);
+
+ // Assert: Verify the detection logic works correctly
+ Assert.AreEqual(shouldWarn, isNonAnonymous, $"Role '{role}' detection mismatch");
+ }
}
diff --git a/src/Cli/ConfigGenerator.cs b/src/Cli/ConfigGenerator.cs
index e0e36a1851..ec9b0cd3d3 100644
--- a/src/Cli/ConfigGenerator.cs
+++ b/src/Cli/ConfigGenerator.cs
@@ -2448,21 +2448,15 @@ public static bool IsConfigValid(ValidateOptions options, FileSystemRuntimeConfi
// Warn if Unauthenticated provider is used with authenticated or custom roles
if (config.Runtime?.Host?.Authentication?.IsUnauthenticatedAuthenticationProvider() == true)
{
- foreach (KeyValuePair entity in config.Entities)
+ foreach (KeyValuePair entity in config.Entities.Where(e => e.Value.Permissions is not null))
{
- if (entity.Value.Permissions is not null)
+ foreach (EntityPermission permission in entity.Value.Permissions!.Where(p => !p.Role.Equals("anonymous", StringComparison.OrdinalIgnoreCase)))
{
- foreach (EntityPermission permission in entity.Value.Permissions)
- {
- if (!permission.Role.Equals("anonymous", StringComparison.OrdinalIgnoreCase))
- {
- _logger.LogWarning(
- "Entity '{EntityName}' has permission configured for role '{Role}' but authentication provider is 'Unauthenticated'. " +
- "All requests will be treated as anonymous.",
- entity.Key,
- permission.Role);
- }
- }
+ _logger.LogWarning(
+ "Entity '{EntityName}' has permission configured for role '{Role}' but authentication provider is 'Unauthenticated'. " +
+ "All requests will be treated as anonymous.",
+ entity.Key,
+ permission.Role);
}
}
}
diff --git a/src/Core/AuthenticationHelpers/ClientRoleHeaderAuthenticationMiddleware.cs b/src/Core/AuthenticationHelpers/ClientRoleHeaderAuthenticationMiddleware.cs
index c83de9ed3a..513e81a137 100644
--- a/src/Core/AuthenticationHelpers/ClientRoleHeaderAuthenticationMiddleware.cs
+++ b/src/Core/AuthenticationHelpers/ClientRoleHeaderAuthenticationMiddleware.cs
@@ -4,6 +4,7 @@
using System.Security.Claims;
using Azure.DataApiBuilder.Config.ObjectModel;
using Azure.DataApiBuilder.Core.AuthenticationHelpers.AuthenticationSimulator;
+using Azure.DataApiBuilder.Core.AuthenticationHelpers.UnauthenticatedAuthenticationHandler;
using Azure.DataApiBuilder.Core.Authorization;
using Azure.DataApiBuilder.Core.Configurations;
using Azure.DataApiBuilder.Core.Models;
@@ -192,6 +193,10 @@ private static string ResolveConfiguredAuthNScheme(string? configuredProviderNam
{
return SimulatorAuthenticationDefaults.AUTHENTICATIONSCHEME;
}
+ else if (string.Equals(configuredProviderName, SupportedAuthNProviders.UNAUTHENTICATED, StringComparison.OrdinalIgnoreCase))
+ {
+ return UnauthenticatedAuthenticationDefaults.AUTHENTICATIONSCHEME;
+ }
else if (string.Equals(configuredProviderName, SupportedAuthNProviders.AZURE_AD, StringComparison.OrdinalIgnoreCase) ||
string.Equals(configuredProviderName, SupportedAuthNProviders.ENTRA_ID, StringComparison.OrdinalIgnoreCase))
{