I was building a Node.js web application for managing files in Azure Blob Storage, deployed to an Azure App Service (Linux). The application consisted of:
- A backend API built with Express.js and the Azure Storage SDK
- A frontend UI using Bootstrap and DataTables
- Azure App Service Authentication with Azure Entra ID
Everything worked fine in my local and staging environments.
The Problem
However, when I deployed to production with Azure Entra ID authentication enabled, I encountered a critical issue:
File uploads were failing with HTTP 403 (Forbidden) errors, even though listing and deleting files worked perfectly.
The root cause? Azure Entra ID authentication was intercepting all requests, including API calls, and blocking unauthenticated programmatic uploads. My goal was to:
- Protect the UI with Entra ID authentication (redirect users to login)
- Allow API routes (
/api/*) to be accessed without authentication for file operations - Keep the backend using Azure Storage Access Keys (not user tokens)
Solutions Explored
Solution 1: Custom Middleware Authentication (Failed)
My first attempt was to implement custom middleware in Express to handle authentication:
// Custom middleware approach (didn't work)
function requireAuthForUI(req, res, next) {
if (req.path.startsWith('/api')) {
return next(); // Skip auth for API
}
if (!req.headers['x-ms-client-principal']) {
return res.redirect('/.auth/login/aad');
}
next();
}
app.use(requireAuthForUI);
Problems encountered:
- Created redirect loops (HTTP 431)
-
/.auth/login/aadreturned "Route not found" because Azure Entra ID authentication wasn't active at the platform level - Custom middleware interfered with Azure Entra ID authentication's built-in routing
Lesson learned: Don't try to implement authentication logic in your Node.js app when using Azure Entra ID authentication. The platform must handle authentication before requests reach your application code.
Solution 2: Patching authsettingsV2 via Azure CLI (Failed)
I attempted to configure Easy Auth by directly patching the authsettingsV2 API using Azure CLI:
az rest --method PATCH \
--url "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.Web/sites/{appName}/config/authsettingsV2?api-version=2022-03-01" \
--body @auth.json
Problems encountered:
-
UnsupportedMediaTypeerrors due to JSON formatting issues -
InvalidRequestContentwhen trying different approaches - Complex JSON structure with nested properties made it error-prone
References:
Solution 3: File-Based Configuration (Success)
The winning solution was to use file-based Azure Entra ID authentication configuration by creating a .auth/config.json file in the repository.
Key advantages of this approach:
- Configuration as Code: The authentication configuration lives in your repository alongside your application code
- Version controlled: Track changes, rollback if needed, and review configurations through pull requests
-
Easy to extend: Need to add another API route (e.g.,
/admin-api)? Simply add it toexcludedPathsin the config file and redeploy - Environment consistency: Deploy the same configuration across dev, staging, and production
- No portal dependency: No need to manually configure authentication settings through the Azure Portal for each environment
The Implementation
Step 1: Create .auth/config.json
I created a configuration file that:
- Enables Azure Entra ID authentication at the platform level
- Redirects unauthenticated users to Entra ID login for the UI
-
Excludes
/apiand/api/*paths from authentication
{
"platform": {
"enabled": true
},
"globalValidation": {
"requireAuthentication": true,
"unauthenticatedClientAction": "RedirectToLoginPage",
"excludedPaths": [
"/api",
"/api/*"
]
},
"identityProviders": {
"azureActiveDirectory": {
"enabled": true,
"registration": {
"clientIdSettingName": "MICROSOFT_PROVIDER_CLIENT_ID",
"clientSecretSettingName": "MICROSOFT_PROVIDER_AUTHENTICATION_SECRET",
"openIdIssuer": "https://login.microsoftonline.com/{tenant-id}/v2.0"
}
}
},
"login": {
"tokenStore": {
"enabled": true
}
},
"httpSettings": {
"requireHttps": true,
"routes": {
"apiPrefix": "/.auth"
}
}
}
Key configuration points:
-
excludedPaths: Tells Azure Entra ID authentication to skip authentication for/apiroutes -
unauthenticatedClientAction: "RedirectToLoginPage": Protects the UI by redirecting to login -
openIdIssuer: Uselogin.microsoftonline.com(notsts.windows.net) for proper v2.0 endpoint validation - Settings references: Use app settings for sensitive values instead of hardcoding
Step 2: Configure the Configuration File Path
To enable file-based authentication, you need to tell Azure App Service where to find your configuration file. There are two main approaches:
Option A: Using App Settings (Recommended)
In Azure Portal (or via CLI), add the WEBSITE_AUTH_CONFIG_DIR application setting:
WEBSITE_AUTH_CONFIG_DIR=.auth
MICROSOFT_PROVIDER_CLIENT_ID=<your-app-registration-client-id>
MICROSOFT_PROVIDER_AUTHENTICATION_SECRET=<your-client-secret>
This tells Azure App Service to load authentication configuration from the .auth directory in your deployment package.
Option B: Using platform.configFilePath in ARM Template
Alternatively, you can set the configuration path directly in the App Service resource configuration using ARM Template or Azure Resource Explorer:
ARM Template Example:
{
"type": "Microsoft.Web/sites/config",
"apiVersion": "2022-03-01",
"name": "[concat(parameters('siteName'), '/authsettingsV2')]",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', parameters('siteName'))]"
],
"properties": {
"platform": {
"enabled": true,
"configFilePath": ".auth/config.json"
}
}
}
Azure Resource Explorer Steps:
- Navigate to Azure Resource Explorer
- Browse to:
subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.Web/sites/{app-name}/config/authsettingsV2 - Click Edit and add or update the
platformsection:
{
"properties": {
"platform": {
"enabled": true,
"configFilePath": ".auth/config.json"
}
}
}
- Click PUT to apply the changes
Key differences:
| Method | Scope | Best for |
|---|---|---|
WEBSITE_AUTH_CONFIG_DIR |
Points to a directory; App Service looks for config.json inside |
Simple scenarios, CI/CD pipelines |
platform.configFilePath |
Points to a specific file path | Fine-grained control, IaC deployments |
Important notes:
- The path is relative to your application root (
/home/site/wwwrooton Linux) - Don't forget to also set
MICROSOFT_PROVIDER_CLIENT_IDandMICROSOFT_PROVIDER_AUTHENTICATION_SECRETas app settings - Restart your App Service after making these changes
Step 3: Remove Custom Middleware
I removed all custom authentication middleware from server.js to avoid interference:
// Removed this:
// app.use(requireAuthForUI);
// Let Azure Entra ID authentication handle everything at platform level
app.use(express.static('public'));
app.use('/api', blobRoutes);
Step 4: Deploy and Verify
After deploying the changes:
- Restart the App Service to apply the new configuration
- Test the UI: Navigate to the root URL → should redirect to Entra ID login
-
Test the API:
POST /api/blobs→ should work without authentication (returns 201) -
Verify auth headers: After login, check
/.auth/meto see the authenticated user identity
Results
UI is protected: Unauthenticated users are redirected to Entra ID login
API routes are open: File uploads work without authentication
No custom code: Azure Entra ID authentication handles everything at the platform level
Token store enabled: Authenticated user identity available via /.auth/me
Key Takeaways
- Use file-based configuration when Azure Entra ID authentication portal options are limited or unavailable
- Let the platform handle authentication - don't implement custom middleware that competes with Azure Entra ID authentication
-
Use
excludedPathsto selectively bypass authentication for specific routes (like APIs) -
Use the correct issuer URL:
login.microsoftonline.com/{tenant}/v2.0for Entra ID v2.0 - Reference sensitive values via app settings instead of hardcoding in JSON
Documentation References
- Azure App Service Authentication Overview
- Configure Authentication File-Based Configuration
- Azure Entra ID Authentication Configuration Reference
- Entra ID OpenID Connect
Conclusion
File-based Azure Entra ID authentication configuration proved to be the most reliable solution for my use case. It provides fine-grained control over which routes require authentication while keeping the configuration declarative and version-controlled.
If you're facing similar challenges with Azure App Service authentication, I highly recommend exploring the file-based approach before diving into complex REST API patches or custom middleware solutions.
Have you encountered similar authentication challenges with Azure App Service? Share your experiences in the comments!
Top comments (0)