diff --git a/src/Core/AuthenticationHelpers/AppServiceAuthenticationInformation.cs b/src/Core/AuthenticationHelpers/AppServiceAuthenticationInformation.cs
index bcd836c288..0c31391148 100644
--- a/src/Core/AuthenticationHelpers/AppServiceAuthenticationInformation.cs
+++ b/src/Core/AuthenticationHelpers/AppServiceAuthenticationInformation.cs
@@ -15,18 +15,22 @@ public static class AppServiceAuthenticationInfo
/// Environment variable key whose value represents whether AppService EasyAuth is enabled ("true" or "false").
///
public const string APPSERVICESAUTH_ENABLED_ENVVAR = "WEBSITE_AUTH_ENABLED";
- ///
- /// Environment variable key whose value represents Identity Provider such as "AzureActiveDirectory"
- ///
- public const string APPSERVICESAUTH_IDENTITYPROVIDER_ENVVAR = "WEBSITE_AUTH_DEFAULT_PROVIDER";
+
+ // ── AppService messages ──────────────────────────────────────────────────────────────────────
///
/// Error message used when AppService Authentication is configured in production mode in a non AppService Environment.
///
- public const string APPSERVICE_PROD_MISSING_ENV_CONFIG = "AppService environment not detected while runtime is in production mode.";
+ public const string APPSERVICE_PROD_MISSING_ENV_CONFIG =
+ "AppService environment not detected while runtime is in production mode. " +
+ "The X-MS-CLIENT-PRINCIPAL header is not cryptographically validated by DAB and can be trivially " +
+ "forged when the service is not hosted behind an Azure App Service EasyAuth proxy. " +
+ "Set host.mode to 'development' for local testing, or deploy behind Azure App Service.";
///
/// Warning message logged when AppService environment not detected (applicable to development mode).
///
- public const string APPSERVICE_DEV_MISSING_ENV_CONFIG = "AppService environment not detected, EasyAuth authentication may not behave as expected.";
+ public const string APPSERVICE_DEV_MISSING_ENV_CONFIG =
+ "AppService environment not detected. The X-MS-CLIENT-PRINCIPAL header is not cryptographically " +
+ "validated; EasyAuth authentication may not behave as expected outside an Azure App Service environment.";
///
/// Returns a best guess whether AppService is enabled in the environment by checking for
@@ -39,14 +43,12 @@ public static class AppServiceAuthenticationInfo
///
public static bool AreExpectedAppServiceEnvVarsPresent()
{
+ // WEBSITE_AUTH_ENABLED is the only variable that is reliably injected by the Azure platform
+ // whenever App Service Authentication (EasyAuth) is enabled, regardless of how many identity
+ // providers are configured. WEBSITE_AUTH_DEFAULT_PROVIDER is only set when a *single* provider
+ // is selected; multi-provider configurations leave it unset, so it must not be required here.
string? appServiceEnabled = Environment.GetEnvironmentVariable(APPSERVICESAUTH_ENABLED_ENVVAR);
- string? appServiceIdentityProvider = Environment.GetEnvironmentVariable(APPSERVICESAUTH_IDENTITYPROVIDER_ENVVAR);
-
- if (string.IsNullOrEmpty(appServiceEnabled) || string.IsNullOrEmpty(appServiceIdentityProvider))
- {
- return false;
- }
-
- return appServiceEnabled.Equals(value: "true", comparisonType: StringComparison.OrdinalIgnoreCase);
+ return appServiceEnabled is not null &&
+ appServiceEnabled.Equals(value: "true", comparisonType: StringComparison.OrdinalIgnoreCase);
}
}
diff --git a/src/Core/AuthenticationHelpers/StaticWebAppsAuthentication.cs b/src/Core/AuthenticationHelpers/StaticWebAppsAuthentication.cs
index bf123e596d..19fa7af3f3 100644
--- a/src/Core/AuthenticationHelpers/StaticWebAppsAuthentication.cs
+++ b/src/Core/AuthenticationHelpers/StaticWebAppsAuthentication.cs
@@ -33,6 +33,31 @@ public class StaticWebAppsClientPrincipal
public IEnumerable? UserRoles { get; set; }
}
+ ///
+ /// Environment variable key set by the Azure platform for every App Service and Static Web Apps
+ /// hosted site. Its presence is the best-effort signal that the runtime is executing behind an
+ /// Azure-managed proxy that injects and validates the X-MS-CLIENT-PRINCIPAL header.
+ ///
+ public const string WEBSITE_SITE_NAME_ENVVAR = "WEBSITE_SITE_NAME";
+
+ // ── StaticWebApps messages ───────────────────────────────────────────────────────────────────
+ ///
+ /// Error message used when StaticWebApps Authentication is configured in production mode
+ /// without a detectable Azure-hosted environment.
+ ///
+ public const string SWA_PROD_MISSING_ENV_CONFIG =
+ "StaticWebApps environment not detected while runtime is in production mode. " +
+ "The X-MS-CLIENT-PRINCIPAL header is not cryptographically validated by DAB and can be trivially " +
+ "forged when the service is not hosted behind an Azure Static Web Apps proxy. " +
+ "Set host.mode to 'development' for local testing, or deploy behind Azure Static Web Apps.";
+
+ ///
+ /// Warning message logged when StaticWebApps environment not detected (applicable to development mode).
+ ///
+ public const string SWA_DEV_MISSING_ENV_CONFIG =
+ "StaticWebApps environment not detected. The X-MS-CLIENT-PRINCIPAL header is not cryptographically " +
+ "validated; EasyAuth authentication may not behave as expected outside an Azure Static Web Apps environment.";
+
///
/// Base64 decodes and deserializes the x-ms-client-principal payload containing
/// SWA token metadata.
@@ -98,4 +123,14 @@ error is NotSupportedException ||
return identity;
}
+
+ ///
+ /// Returns a best-effort indication that the runtime is executing inside an Azure Static Web Apps
+ /// environment by checking for the presence of , which is
+ /// injected by the Azure platform for every SWA-hosted application.
+ ///
+ public static bool AreExpectedSWAEnvVarsPresent()
+ {
+ return !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(WEBSITE_SITE_NAME_ENVVAR));
+ }
}
diff --git a/src/Service.Tests/Caching/HealthEndpointCachingTests.cs b/src/Service.Tests/Caching/HealthEndpointCachingTests.cs
index 664e6070e6..2ca6661fa4 100644
--- a/src/Service.Tests/Caching/HealthEndpointCachingTests.cs
+++ b/src/Service.Tests/Caching/HealthEndpointCachingTests.cs
@@ -22,6 +22,12 @@ public class HealthEndpointCachingTests
{
private const string CUSTOM_CONFIG_FILENAME = "custom-config.json";
+ [TestInitialize]
+ public void SetupAuthProviderEnvironmentVariables()
+ {
+ TestHelper.SetAppServiceEnvironmentVariable();
+ }
+
[TestCleanup]
public void CleanupAfterEachTest()
{
diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs
index 247685793f..8764533c14 100644
--- a/src/Service.Tests/Configuration/ConfigurationTests.cs
+++ b/src/Service.Tests/Configuration/ConfigurationTests.cs
@@ -735,6 +735,13 @@ public void CleanupAfterEachTest()
TestHelper.UnsetAllDABEnvironmentVariables();
}
+ [TestInitialize]
+ public void SetupAuthProviderEnvironmentVariables()
+ {
+ TestHelper.SetAppServiceEnvironmentVariable();
+ TestHelper.SetStaticWebAppsEnvironmentVariable();
+ }
+
///
/// When updating config during runtime is possible, then For invalid config the Application continues to
/// accept request with status code of 503.
@@ -3934,23 +3941,24 @@ type Planet @model(name:""PlanetAlias"") {
///
/// HostMode in Runtime config - Development or Production.
/// EasyAuth auth type - AppService or StaticWebApps.
- /// Whether to set the AppService host environment variables.
+ /// Whether to set the AppService host environment variables.
+ /// Whether to set the Static Web Apps host environment variable.
/// Whether an error is expected.
[DataTestMethod]
[TestCategory(TestCategory.MSSQL)]
- [DataRow(HostMode.Development, EasyAuthType.AppService, false, false, DisplayName = "AppService Dev - No EnvVars - No Error")]
- [DataRow(HostMode.Development, EasyAuthType.AppService, true, false, DisplayName = "AppService Dev - EnvVars - No Error")]
- [DataRow(HostMode.Production, EasyAuthType.AppService, false, false, DisplayName = "AppService Prod - No EnvVars - Error")]
- [DataRow(HostMode.Production, EasyAuthType.AppService, true, false, DisplayName = "AppService Prod - EnvVars - Error")]
- [DataRow(HostMode.Development, EasyAuthType.StaticWebApps, false, false, DisplayName = "SWA Dev - No EnvVars - No Error")]
- [DataRow(HostMode.Development, EasyAuthType.StaticWebApps, true, false, DisplayName = "SWA Dev - EnvVars - No Error")]
- [DataRow(HostMode.Production, EasyAuthType.StaticWebApps, false, false, DisplayName = "SWA Prod - No EnvVars - No Error")]
- [DataRow(HostMode.Production, EasyAuthType.StaticWebApps, true, false, DisplayName = "SWA Prod - EnvVars - No Error")]
- public void TestProductionModeAppServiceEnvironmentCheck(HostMode hostMode, EasyAuthType authType, bool setEnvVars, bool expectError)
+ [DataRow(HostMode.Development, EasyAuthType.AppService, false, false, false, DisplayName = "AppService Dev - No EnvVars - No Error")]
+ [DataRow(HostMode.Development, EasyAuthType.AppService, true, false, false, DisplayName = "AppService Dev - EnvVars - No Error")]
+ [DataRow(HostMode.Production, EasyAuthType.AppService, false, false, true, DisplayName = "AppService Prod - No EnvVars - Error")]
+ [DataRow(HostMode.Production, EasyAuthType.AppService, true, false, false, DisplayName = "AppService Prod - EnvVars - No Error")]
+ [DataRow(HostMode.Development, EasyAuthType.StaticWebApps, false, false, false, DisplayName = "SWA Dev - No EnvVars - No Error")]
+ [DataRow(HostMode.Development, EasyAuthType.StaticWebApps, false, true, false, DisplayName = "SWA Dev - EnvVars - No Error")]
+ [DataRow(HostMode.Production, EasyAuthType.StaticWebApps, false, false, true, DisplayName = "SWA Prod - No EnvVars - Error")]
+ [DataRow(HostMode.Production, EasyAuthType.StaticWebApps, false, true, false, DisplayName = "SWA Prod - EnvVars - No Error")]
+ public void TestProductionModeAppServiceEnvironmentCheck(HostMode hostMode, EasyAuthType authType, bool setAppServiceEnvVars, bool setStaticWebAppsEnvVar, bool expectError)
{
// Clears or sets App Service Environment Variables based on test input.
- Environment.SetEnvironmentVariable(AppServiceAuthenticationInfo.APPSERVICESAUTH_ENABLED_ENVVAR, setEnvVars ? "true" : null);
- Environment.SetEnvironmentVariable(AppServiceAuthenticationInfo.APPSERVICESAUTH_IDENTITYPROVIDER_ENVVAR, setEnvVars ? "AzureActiveDirectory" : null);
+ Environment.SetEnvironmentVariable(AppServiceAuthenticationInfo.APPSERVICESAUTH_ENABLED_ENVVAR, setAppServiceEnvVars ? "true" : null);
+ Environment.SetEnvironmentVariable(StaticWebAppsAuthentication.WEBSITE_SITE_NAME_ENVVAR, setStaticWebAppsEnvVar ? "test-site-name" : null);
TestHelper.SetupDatabaseEnvironment(TestCategory.MSSQL);
FileSystem fileSystem = new();
@@ -3987,7 +3995,16 @@ public void TestProductionModeAppServiceEnvironmentCheck(HostMode hostMode, Easy
catch (DataApiBuilderException ex)
{
Assert.IsTrue(expectError, message: ex.Message);
- Assert.AreEqual(AppServiceAuthenticationInfo.APPSERVICE_PROD_MISSING_ENV_CONFIG, ex.Message);
+ Assert.AreEqual(
+ expected: authType == EasyAuthType.AppService
+ ? AppServiceAuthenticationInfo.APPSERVICE_PROD_MISSING_ENV_CONFIG
+ : StaticWebAppsAuthentication.SWA_PROD_MISSING_ENV_CONFIG,
+ actual: ex.Message);
+ }
+ finally
+ {
+ Environment.SetEnvironmentVariable(AppServiceAuthenticationInfo.APPSERVICESAUTH_ENABLED_ENVVAR, null);
+ Environment.SetEnvironmentVariable(StaticWebAppsAuthentication.WEBSITE_SITE_NAME_ENVVAR, null);
}
}
diff --git a/src/Service.Tests/Configuration/HealthEndpointRolesTests.cs b/src/Service.Tests/Configuration/HealthEndpointRolesTests.cs
index 99f3284a8a..227167da2b 100644
--- a/src/Service.Tests/Configuration/HealthEndpointRolesTests.cs
+++ b/src/Service.Tests/Configuration/HealthEndpointRolesTests.cs
@@ -22,6 +22,12 @@ public class HealthEndpointRolesTests
private const string CUSTOM_CONFIG_FILENAME = "custom-config.json";
+ [TestInitialize]
+ public void SetupAuthProviderEnvironmentVariables()
+ {
+ TestHelper.SetAppServiceEnvironmentVariable();
+ }
+
[TestCleanup]
public void CleanupAfterEachTest()
{
diff --git a/src/Service.Tests/Configuration/HealthEndpointTests.cs b/src/Service.Tests/Configuration/HealthEndpointTests.cs
index b1b21c4307..0ae704a920 100644
--- a/src/Service.Tests/Configuration/HealthEndpointTests.cs
+++ b/src/Service.Tests/Configuration/HealthEndpointTests.cs
@@ -36,6 +36,12 @@ public class HealthEndpointTests
private const string CUSTOM_CONFIG_FILENAME = "custom_config.json";
private const string BASE_DAB_URL = "http://localhost:5000";
+ [TestInitialize]
+ public void SetupAuthProviderEnvironmentVariables()
+ {
+ TestHelper.SetAppServiceEnvironmentVariable();
+ }
+
[TestCleanup]
public void CleanupAfterEachTest()
{
diff --git a/src/Service.Tests/SqlTests/SqlTestHelper.cs b/src/Service.Tests/SqlTests/SqlTestHelper.cs
index 32041c8fc8..5aa7cb1fe9 100644
--- a/src/Service.Tests/SqlTests/SqlTestHelper.cs
+++ b/src/Service.Tests/SqlTests/SqlTestHelper.cs
@@ -380,6 +380,7 @@ public static RuntimeConfig InitBasicRuntimeConfigWithNoEntity(
DatabaseType dbType = DatabaseType.MSSQL,
string testCategory = TestCategory.MSSQL)
{
+ TestHelper.SetAppServiceEnvironmentVariable();
DataSource dataSource = new(dbType, GetConnectionStringFromEnvironmentConfig(environment: testCategory), new());
Config.ObjectModel.AuthenticationOptions authenticationOptions = new(Provider: nameof(EasyAuthType.AppService), null);
diff --git a/src/Service.Tests/TestHelper.cs b/src/Service.Tests/TestHelper.cs
index bb83f6243f..21d1c10113 100644
--- a/src/Service.Tests/TestHelper.cs
+++ b/src/Service.Tests/TestHelper.cs
@@ -9,6 +9,7 @@
using System.Threading.Tasks;
using Azure.DataApiBuilder.Config;
using Azure.DataApiBuilder.Config.ObjectModel;
+using Azure.DataApiBuilder.Core.AuthenticationHelpers;
using Azure.DataApiBuilder.Core.Configurations;
using Azure.DataApiBuilder.Service.Tests.Configuration;
using Humanizer;
@@ -29,6 +30,18 @@ public static void UnsetAllDABEnvironmentVariables()
Environment.SetEnvironmentVariable(FileSystemRuntimeConfigLoader.RUNTIME_ENVIRONMENT_VAR_NAME, null);
Environment.SetEnvironmentVariable(FileSystemRuntimeConfigLoader.ASP_NET_CORE_ENVIRONMENT_VAR_NAME, null);
Environment.SetEnvironmentVariable(FileSystemRuntimeConfigLoader.RUNTIME_ENV_CONNECTION_STRING, null);
+ Environment.SetEnvironmentVariable(AppServiceAuthenticationInfo.APPSERVICESAUTH_ENABLED_ENVVAR, null);
+ Environment.SetEnvironmentVariable(StaticWebAppsAuthentication.WEBSITE_SITE_NAME_ENVVAR, null);
+ }
+
+ public static void SetAppServiceEnvironmentVariable()
+ {
+ Environment.SetEnvironmentVariable(AppServiceAuthenticationInfo.APPSERVICESAUTH_ENABLED_ENVVAR, "true");
+ }
+
+ public static void SetStaticWebAppsEnvironmentVariable()
+ {
+ Environment.SetEnvironmentVariable(StaticWebAppsAuthentication.WEBSITE_SITE_NAME_ENVVAR, "dab-service-tests");
}
///
diff --git a/src/Service/Startup.cs b/src/Service/Startup.cs
index 68364b530e..bcbaa235f8 100644
--- a/src/Service/Startup.cs
+++ b/src/Service/Startup.cs
@@ -1148,11 +1148,36 @@ private void ConfigureAuthentication(IServiceCollection services, RuntimeConfigP
EasyAuthType easyAuthType = EnumExtensions.Deserialize(runtimeConfig.Runtime.Host.Authentication.Provider);
bool isProductionMode = mode != HostMode.Development;
bool appServiceEnvironmentDetected = AppServiceAuthenticationInfo.AreExpectedAppServiceEnvVarsPresent();
+ bool swaEnvironmentDetected = StaticWebAppsAuthentication.AreExpectedSWAEnvVarsPresent();
if (easyAuthType == EasyAuthType.AppService && !appServiceEnvironmentDetected)
{
+ if (isProductionMode)
+ {
+ // SECURITY: In production the X-MS-CLIENT-PRINCIPAL header is blindly trusted.
+ // Refuse to start when there is no evidence of an App Service EasyAuth proxy.
+ throw new DataApiBuilderException(
+ message: AppServiceAuthenticationInfo.APPSERVICE_PROD_MISSING_ENV_CONFIG,
+ statusCode: System.Net.HttpStatusCode.ServiceUnavailable,
+ subStatusCode: DataApiBuilderException.SubStatusCodes.ConfigValidationError);
+ }
+
_logBuffer.BufferLog(LogLevel.Warning, AppServiceAuthenticationInfo.APPSERVICE_DEV_MISSING_ENV_CONFIG);
}
+ else if (easyAuthType == EasyAuthType.StaticWebApps && !swaEnvironmentDetected)
+ {
+ if (isProductionMode)
+ {
+ // SECURITY: In production the X-MS-CLIENT-PRINCIPAL header is blindly trusted.
+ // Refuse to start when there is no evidence of a Static Web Apps proxy.
+ throw new DataApiBuilderException(
+ message: StaticWebAppsAuthentication.SWA_PROD_MISSING_ENV_CONFIG,
+ statusCode: System.Net.HttpStatusCode.ServiceUnavailable,
+ subStatusCode: DataApiBuilderException.SubStatusCodes.ConfigValidationError);
+ }
+
+ _logBuffer.BufferLog(LogLevel.Warning, StaticWebAppsAuthentication.SWA_DEV_MISSING_ENV_CONFIG);
+ }
string defaultScheme = easyAuthType == EasyAuthType.AppService
? EasyAuthAuthenticationDefaults.APPSERVICEAUTHSCHEME