Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
4618b06
Initial plan
Copilot Jan 21, 2026
e4c09d2
Updated plan with new requirements
Copilot Jan 21, 2026
f63c338
Add Unauthenticated authentication provider with CLI support, tests, …
Copilot Jan 21, 2026
1a2f633
Fix typo in UnauthenticatedAuthenticationBuilderExtensions comment
Copilot Jan 21, 2026
b6d0fd5
Update src/Cli/Utils.cs
JerryNixon Jan 22, 2026
a441af8
Merge branch 'main' into copilot/add-unauthenticated-auth-provider
JerryNixon Jan 22, 2026
e3fb034
Address PR review comments: add Unauthenticated scheme mapping, use e…
Copilot Jan 22, 2026
00444b8
Merge branch 'main' into copilot/add-unauthenticated-auth-provider
RubenCerna2079 Jan 28, 2026
f0b25d0
Address PR review comments: rename folder, update docs, simplify warn…
Copilot Jan 28, 2026
ac245d6
Merge branch 'main' into copilot/add-unauthenticated-auth-provider
Aniruddh25 Feb 11, 2026
0330922
Merge branch 'main' into copilot/add-unauthenticated-auth-provider
JerryNixon Feb 13, 2026
17844ce
Change default authentication provider from AppService to Unauthentic…
Copilot Feb 13, 2026
8b364ca
Remove unused using directive from ValidateConfigTests.cs
Copilot Feb 21, 2026
015659f
Merge branch 'main' into copilot/add-unauthenticated-auth-provider
Aniruddh25 Feb 21, 2026
0d0b3f8
Use IsJwtConfiguredIdentityProvider() in Startup.cs for cleaner code
Copilot Feb 21, 2026
86dfde1
Merge branch 'main' into copilot/add-unauthenticated-auth-provider
Aniruddh25 Mar 4, 2026
a62d2fa
Fix: Revert AuthenticationOptions record default to AppService for ba…
Copilot Mar 5, 2026
9c68fb7
Change default authentication provider to Unauthenticated and update …
Copilot Mar 5, 2026
58e0328
Merge branch 'main' into copilot/add-unauthenticated-auth-provider
Aniruddh25 Mar 5, 2026
b0add00
Merge branch 'main' into copilot/add-unauthenticated-auth-provider
RubenCerna2079 Mar 6, 2026
5dfac7d
Merge branch 'main' into copilot/add-unauthenticated-auth-provider
Aniruddh25 Mar 7, 2026
afbe0be
Merge branch 'main' into copilot/add-unauthenticated-auth-provider
Aniruddh25 Mar 7, 2026
a8781ac
Update snapshot files to use Unauthenticated as default provider
Copilot Mar 8, 2026
bb114bf
Add --auth.provider AppService to config generator command files for …
Copilot Mar 8, 2026
d9b2ec5
Revert config-generator commands and update test configs to use Unaut…
Copilot Mar 8, 2026
684a9c6
Update Service.Tests snapshots to use Unauthenticated provider
Copilot Mar 8, 2026
62588b4
Merge branch 'main' into copilot/add-unauthenticated-auth-provider
JerryNixon Mar 10, 2026
1c13dd5
Fix snapshot tests to use Unauthenticated as default provider in test…
Copilot Mar 11, 2026
78e3476
Revert integration test config files to use AppService - Unauthentica…
Copilot Mar 11, 2026
98dcb85
Merge branch 'main' into copilot/add-unauthenticated-auth-provider
souvikghosh04 Mar 12, 2026
bd6fe6b
Fixing failing tests
souvikghosh04 Mar 12, 2026
630832a
Fix failing unit test for Unauthenticated with no audience
souvikghosh04 Mar 12, 2026
4d4f4b1
Merge branch 'main' into copilot/add-unauthenticated-auth-provider
souvikghosh04 Mar 12, 2026
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
4 changes: 4 additions & 0 deletions schemas/dab.draft.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions src/Cli.Tests/InitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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: []
}
33 changes: 33 additions & 0 deletions src/Cli.Tests/ValidateConfigTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Azure.DataApiBuilder.Config.ObjectModel;
Comment thread
Aniruddh25 marked this conversation as resolved.
Outdated
using Azure.DataApiBuilder.Core.Configurations;
using Azure.DataApiBuilder.Core.Models;
using Serilog;
Expand Down Expand Up @@ -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);
}

/// <summary>
/// Test that the Unauthenticated provider is correctly identified by the IsUnauthenticatedAuthenticationProvider method.
/// </summary>
[TestMethod]
public void TestIsUnauthenticatedAuthenticationProviderMethod()
Comment thread
JerryNixon marked this conversation as resolved.
Outdated
{
// 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());
}

/// <summary>
/// Test that Unauthenticated provider does not require JWT configuration.
/// </summary>
[TestMethod]
public void TestUnauthenticatedProviderDoesNotRequireJwt()
Comment thread
JerryNixon marked this conversation as resolved.
Outdated
{
AuthenticationOptions unauthenticatedOptions = new(Provider: "Unauthenticated");
Assert.IsFalse(unauthenticatedOptions.IsJwtConfiguredIdentityProvider());
}
}
22 changes: 22 additions & 0 deletions src/Cli/ConfigGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Comment thread
JerryNixon marked this conversation as resolved.
foreach (KeyValuePair<string, Entity> 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);
}
}
Comment thread
JerryNixon marked this conversation as resolved.
Outdated
}
}
Comment thread
JerryNixon marked this conversation as resolved.
Outdated
}
}
}

Expand Down
7 changes: 4 additions & 3 deletions src/Cli/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -516,19 +516,20 @@ public static bool ValidateAudienceAndIssuerForJwtProvider(
string? issuer)
{
if (Enum.TryParse<EasyAuthType>(authenticationProvider, ignoreCase: true, out _)
|| AuthenticationOptions.SIMULATOR_AUTHENTICATION == authenticationProvider)
|| AuthenticationOptions.SIMULATOR_AUTHENTICATION == authenticationProvider
Comment thread
JerryNixon marked this conversation as resolved.
Outdated
|| 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;
}
}
else
{
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;
}
}
Expand Down
10 changes: 9 additions & 1 deletion src/Config/ObjectModel/AuthenticationOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,17 @@ public record AuthenticationOptions(string Provider = nameof(EasyAuthType.AppSer
/// <returns>True when development mode should authenticate all requests.</returns>
public bool IsAuthenticationSimulatorEnabled() => Provider.Equals(SIMULATOR_AUTHENTICATION, StringComparison.OrdinalIgnoreCase);

public const string UNAUTHENTICATED_AUTHENTICATION = "Unauthenticated";

/// <summary>
/// Returns whether the configured Provider value matches the unauthenticated authentication type.
Comment thread
JerryNixon marked this conversation as resolved.
Outdated
/// </summary>
/// <returns>True when all operations run as anonymous.</returns>
Comment thread
JerryNixon marked this conversation as resolved.
Outdated
public bool IsUnauthenticatedAuthenticationProvider() => Provider.Equals(UNAUTHENTICATED_AUTHENTICATION, StringComparison.OrdinalIgnoreCase);

/// <summary>
/// A shorthand method to determine whether JWT is configured for the current authentication provider.
/// </summary>
/// <returns>True if the provider is enabled for JWT, otherwise false.</returns>
public bool IsJwtConfiguredIdentityProvider() => !IsEasyAuthAuthenticationProvider() && !IsAuthenticationSimulatorEnabled();
public bool IsJwtConfiguredIdentityProvider() => !IsEasyAuthAuthenticationProvider() && !IsAuthenticationSimulatorEnabled() && !IsUnauthenticatedAuthenticationProvider();
};
2 changes: 2 additions & 0 deletions src/Core/AuthenticationHelpers/SupportedAuthNProviders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.AspNetCore.Authentication;

namespace Azure.DataApiBuilder.Core.AuthenticationHelpers.UnauthenticatedAuthenticationHandler;

/// <summary>
/// Extension methods related to Unauthenticated authentication.
/// This class allows setting up Unauthenticated authentication in the startup class with
/// a single call to .AddAuthentication(scheme).AddUnauthenticatedAuthentication()
/// </summary>
public static class UnauthenticatedAuthenticationBuilderExtensions
{
/// <summary>
/// Add authentication with Unauthenticated provider.
/// </summary>
/// <param name="builder">Authentication builder.</param>
/// <returns>The builder, to chain commands.</returns>
public static AuthenticationBuilder AddUnauthenticatedAuthentication(this AuthenticationBuilder builder)
{
if (builder is null)
{
throw new System.ArgumentNullException(nameof(builder));
}

builder.AddScheme<AuthenticationSchemeOptions, UnauthenticatedAuthenticationHandler>(
authenticationScheme: UnauthenticatedAuthenticationDefaults.AUTHENTICATIONSCHEME,
displayName: UnauthenticatedAuthenticationDefaults.AUTHENTICATIONSCHEME,
configureOptions: null);

return builder;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace Azure.DataApiBuilder.Core.AuthenticationHelpers.UnauthenticatedAuthenticationHandler;

/// <summary>
/// Default values related to UnauthenticatedAuthentication handler.
/// </summary>
public static class UnauthenticatedAuthenticationDefaults
{
/// <summary>
/// The default value used for UnauthenticatedAuthenticationOptions.AuthenticationScheme.
/// </summary>
public const string AUTHENTICATIONSCHEME = "UnauthenticatedAuthentication";
}
Original file line number Diff line number Diff line change
@@ -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;
Comment thread
JerryNixon marked this conversation as resolved.
Outdated

/// <summary>
/// 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.
/// </summary>
public class UnauthenticatedAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
/// <summary>
/// Constructor for the UnauthenticatedAuthenticationHandler.
/// Note the parameters are required by the base class.
/// </summary>
/// <param name="options">Authentication options.</param>
/// <param name="logger">Logger factory.</param>
/// <param name="encoder">URL encoder.</param>
public UnauthenticatedAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder)
: base(options, logger, encoder)
{
}

/// <summary>
/// Returns an unauthenticated ClaimsPrincipal for all requests.
/// The ClaimsPrincipal has no identity and no claims, representing an anonymous user.
/// </summary>
/// <returns>An authentication result to ASP.NET Core library authentication mechanisms</returns>
protected override Task<AuthenticateResult> 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);
}
}
11 changes: 9 additions & 2 deletions src/Service/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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())
Comment thread
Aniruddh25 marked this conversation as resolved.
Outdated
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
Expand Down Expand Up @@ -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();
}
Comment thread
JerryNixon marked this conversation as resolved.
else if (mode == HostMode.Development && authOptions.IsAuthenticationSimulatorEnabled())
{
services.AddAuthentication(SimulatorAuthenticationDefaults.AUTHENTICATIONSCHEME)
Expand Down Expand Up @@ -850,7 +856,8 @@ private static void ConfigureAuthenticationV2(IServiceCollection services, Runti
services.AddAuthentication()
.AddEnvDetectedEasyAuth()
.AddJwtBearer()
.AddSimulatorAuthentication();
.AddSimulatorAuthentication()
.AddUnauthenticatedAuthentication();
}

/// <summary>
Expand Down
Loading