Protect App Service with Microsoft Defender
Microsoft Defender for App Service adds cloud-native threat detection and security recommendations for apps and APIs on Azure App Service. In this lab, you inspect the existing subscription setting, deliberately enable the paid plan, review recommendations and alerts without generating malicious traffic, and send App Service diagnostic logs to a Log Analytics workspace for investigation.
This is an advanced lab for cloud security engineers, platform engineers, and App Service operators.
Estimated time: 35 to 50 minutes.
Objectives
By the end of this lab, you will be able to:
- Explain the subscription scope and cost effect of Defender for App Service.
- Record the original Defender plan state and enable the
AppServicesStandard pricing tier only after approval. - Review App Service recommendations and security alerts safely.
- Route supported App Service logs and metrics to a Log Analytics workspace.
- Verify the pricing tier and diagnostic destination.
- Restore the original Defender pricing tier and remove lab resources.
Prerequisites
Before you begin, you will need an Azure subscription with Owner permissions and a GitHub account.
In addition, you will need the following tools installed on your local machine:
- Visual Studio Code with the following extensions:
- Azure CLI
- GitHub CLI
- Git
- Access to a nonproduction Azure subscription where subscription-wide Defender changes are approved
- Microsoft Defender for Cloud pricing information
- POSIX-compliant shell (bash, zsh, Azure Cloud Shell)
Setup Azure CLI
Start by logging into Azure by run the following command and follow the prompts:
az login --use-device-code
You can log into a different tenant by passing in the --tenant flag to specify your tenant domain or tenant ID.
Defender for App Service is enabled at the subscription scope. Turning the
AppServices plan to Standard can protect and bill for App Service plan
instances across that subscription, not only the app created in this lab.
Diagnostic ingestion and retention can add Azure Monitor charges.
Do not run the enable step in a shared or production subscription until the subscription owner and cost owner approve the change. Record the original pricing tier first. Deleting the lab resource group does not turn off the subscription plan.
Enabling or disabling a Defender plan requires subscription-scope permission
to write Microsoft.Security/pricings, commonly provided by Security Admin
or Owner. Creating the lab app and workspace needs resource-group
Contributor. Configuring the diagnostic setting also needs write access to the
app and permission to use the workspace. Separate these duties when one person
should not hold all permissions.
How protection and diagnostics fit together
Defender for App Service is integrated into the platform; you do not install an agent in the app. Defender analyzes platform signals and produces recommendations and alerts. App Service diagnostic settings are separate: they route HTTP, console, application, audit, and platform logs supported by your app to a destination you control for investigation.
Defender alerts are not guaranteed immediately after enablement, and a healthy lab subscription can have no alerts. Do not attack the app or paste exploit payloads to force an alert.
Provision resources
Setup Resource Group
In this workshop, we will set environment variables for the resource group name and location.
The following commands will set the environment variables for your current terminal session. If you close the current terminal session, you will need to set the environment variables again.
To keep the resource names unique, we will use a random number as a suffix for the resource names. This will also help you to avoid naming conflicts with other resources in your Azure subscription.
Run the following command to generate a random number.
RAND=$RANDOM
export RAND
echo "Random resource identifier will be: ${RAND}"
Set the location to a region of your choice. For example, eastus or westeurope.
export LOCATION=eastus
Create a resource group name using the random number.
export RG_NAME=myresourcegroup$RAND
You can list the regions that support availability zones with the following command:
az account list-locations \
--query "[?metadata.regionType=='Physical' && metadata.supportsAvailabilityZones==true].{Region:name}" \
--output table
Run the following command to create a resource group using the environment variables you just created.
az group create \
--name ${RG_NAME} \
--location ${LOCATION}
Setup Resources
This workshop uses a few supporting Azure resources alongside your App Service app. Depending on the lab, these may include:
- Azure App Service plan that defines the compute for your app
- Azure Container Registry for storing container images
- Azure Key Vault for secrets management
- Azure User-Assigned Managed Identity for accessing Azure services securely
- Azure Monitor / Application Insights for observability
Each lab will provide the exact commands needed to provision the resources it depends on.
Create a low-cost web app and a Log Analytics workspace. This lab uses a Linux B1 plan, about USD 13/month if left running. The workspace has usage-based ingestion charges; the small lab volume should be low, but it is not guaranteed to be free.
There is no azd path for enabling the Defender plan. The setting belongs to the
subscription security boundary, not an individual app environment, and hiding
that paid change inside azd up would make its scope and rollback less clear.
- Azure CLI (az)
- Azure portal
Set names, create the App Service plan, web app, and workspace, and capture their resource IDs:
export SUBSCRIPTION_ID=$(az account show --query id -o tsv)
export PLAN_NAME=plan-defender-$RAND
export APP_NAME=app-defender-$RAND
export LAW_NAME=law-defender-$RAND
export DIAGNOSTIC_SETTING=send-to-law
az appservice plan create \
--name "$PLAN_NAME" \
--resource-group "$RG_NAME" \
--sku B1 \
--is-linux
az webapp create \
--name "$APP_NAME" \
--resource-group "$RG_NAME" \
--plan "$PLAN_NAME" \
--runtime "NODE:22-lts"
az monitor log-analytics workspace create \
--resource-group "$RG_NAME" \
--workspace-name "$LAW_NAME" \
--location "$LOCATION"
export APP_ID=$(az webapp show \
--name "$APP_NAME" \
--resource-group "$RG_NAME" \
--query id -o tsv)
export WORKSPACE_ID=$(az monitor log-analytics workspace show \
--resource-group "$RG_NAME" \
--workspace-name "$LAW_NAME" \
--query id -o tsv)
-
In the Azure portal, create a Web App in the lab resource group.
-
Choose Code, Linux, a supported runtime, and a Basic B1 App Service plan.
-
Create a Log Analytics workspace in the same resource group and region.
-
Record the app and workspace names:
export APP_NAME="<your-app-name>"export LAW_NAME="<your-workspace-name>"export DIAGNOSTIC_SETTING=send-to-lawexport SUBSCRIPTION_ID=$(az account show --query id -o tsv)
Step 1: Inspect and approve the current Defender state
- Azure CLI (az)
- Azure portal
Read and save the current pricing tier before changing it. Also capture the
full response as an audit reference; this lab changes and restores only
pricingTier for AppServices:
export ORIGINAL_DEFENDER_TIER=$(az security pricing show \
--name AppServices \
--subscription "$SUBSCRIPTION_ID" \
--query pricingTier -o tsv)
az security pricing show \
--name AppServices \
--subscription "$SUBSCRIPTION_ID" \
-o json
printf "Original AppServices tier: %s\n" "$ORIGINAL_DEFENDER_TIER"
The value is normally Free or Standard. Save it in the current shell; you
will use it during cleanup.
- Search for and open Microsoft Defender for Cloud.
- Select Management > Environment settings.
- Select the lab subscription.
- Under Defender plans, record whether App Service is On or Off. Do not change it yet.
- Review the current Defender for Cloud cost estimate with the subscription owner.
Stop here until the authorized subscription owner confirms all three points:
- The selected subscription is the intended nonproduction subscription.
- Enabling Defender can affect and bill all App Service plan instances in it.
- The team has chosen whether to keep the plan enabled or restore its original pricing tier after the lab.
Step 2: Enable Defender for App Service
- Azure CLI (az)
- Azure portal
After approval, set only the App Service Defender plan to Standard:
az security pricing create \
--name AppServices \
--tier Standard \
--subscription "$SUBSCRIPTION_ID"
This command does not enable every Defender plan. It changes the AppServices
pricing resource for the subscription.
- Return to Microsoft Defender for Cloud > Environment settings and select the subscription.
- On Defender plans, turn App Service to On.
- Leave unrelated Defender plans unchanged.
- Select Save and confirm the approved cost impact.
Verify the result:
az security pricing show \
--name AppServices \
--subscription "$SUBSCRIPTION_ID" \
--query "{plan:name,tier:pricingTier,trialRemaining:freeTrialRemainingTime}" \
-o table
Expect AppServices and Standard. Do not assume a free trial is available.
Step 3: Review recommendations and alerts safely
Defender for Cloud continuously evaluates resources. Recommendations can take time to appear after a new app is created.
- Azure CLI (az)
- Azure portal
List assessments whose resource path contains this web app:
az security assessment list \
--subscription "$SUBSCRIPTION_ID" \
--query "[?contains(id, '/sites/$APP_NAME/')].{recommendation:displayName,status:status.code,id:name}" \
-o table
An empty table can mean the app has not been assessed yet. It is not proof that the app is secure.
List alerts in the lab resource group:
az security alert list \
--resource-group "$RG_NAME" \
--subscription "$SUBSCRIPTION_ID" \
--query "[].{name:alertDisplayName,severity:severity,status:status,time:timeGeneratedUtc}" \
-o table
No output is the expected safe result for a new app with normal traffic.
- In Microsoft Defender for Cloud, select Recommendations.
- Filter Resource type to App Service and, if available, filter by the lab resource group.
- Open a recommendation and review its affected resource, severity, rationale, remediation, and exemption workflow. Do not change unrelated resources.
- Select Security alerts and filter by the lab resource group.
- Record that no alert is expected for normal traffic. If an existing alert is present, follow your organization's incident process; do not dismiss it as part of this lab.
A recommendation reports a configuration or posture issue. An alert reports activity that Defender detected as potentially malicious. Enabling the paid plan does not make every recommendation healthy, and fixing recommendations does not guarantee that alerts will never occur.
Step 4: Send App Service diagnostics to Log Analytics
First inspect the categories supported by this app. Categories can differ by operating system, runtime, and App Service feature:
export APP_ID=$(az webapp show \
--name "$APP_NAME" \
--resource-group "$RG_NAME" \
--query id -o tsv)
export WORKSPACE_ID=$(az monitor log-analytics workspace show \
--resource-group "$RG_NAME" \
--workspace-name "$LAW_NAME" \
--query id -o tsv)
az monitor diagnostic-settings categories list \
--resource "$APP_ID" \
--query "value[].{name:name,type:categoryType,groups:categoryGroups}" \
-o table
- Azure CLI (az)
- Azure portal
Use the allLogs category group so Azure applies the app's currently supported
log categories. Use resource-specific tables to make queries clearer:
az monitor diagnostic-settings create \
--name "$DIAGNOSTIC_SETTING" \
--resource "$APP_ID" \
--workspace "$WORKSPACE_ID" \
--export-to-resource-specific true \
--logs '[{"categoryGroup":"allLogs","enabled":true}]' \
--metrics '[{"category":"AllMetrics","enabled":true}]'
- Open the web app and select Monitoring > Diagnostic settings.
- Select Add diagnostic setting and name it
send-to-law. - Select allLogs and AllMetrics.
- Select Send to Log Analytics workspace and choose the lab workspace.
- If offered, select Resource specific as the destination table mode.
- Select Save.
Generate only normal traffic:
APP_URL="https://$(az webapp show \
--name "$APP_NAME" \
--resource-group "$RG_NAME" \
--query defaultHostName -o tsv)"
for i in $(seq 1 10); do
curl --fail --silent "$APP_URL/" --output /dev/null
done
Diagnostic logs can take several minutes to reach the workspace.
Verify
-
Confirm the Defender pricing tier:
az security pricing show \--name AppServices \--subscription "$SUBSCRIPTION_ID" \--query pricingTier -o tsvExpected output after the approved enable step:
Standard -
Confirm the diagnostic setting points to the intended workspace:
az monitor diagnostic-settings show \--name "$DIAGNOSTIC_SETTING" \--resource "$APP_ID" \--query "{workspaceId:workspaceId,logs:logs[].{group:categoryGroup,enabled:enabled},metrics:metrics[].{category:category,enabled:enabled}}" \-o jsonVerify the
workspaceIdexactly matches$WORKSPACE_ID,allLogsis enabled, andAllMetricsis enabled. -
After ingestion, list tables that received records for the app:
WORKSPACE_CUSTOMER_ID=$(az monitor log-analytics workspace show \--resource-group "$RG_NAME" \--workspace-name "$LAW_NAME" \--query customerId -o tsv)az monitor log-analytics query \--workspace "$WORKSPACE_CUSTOMER_ID" \--analytics-query "search * | where _ResourceId =~ '$APP_ID' | summarize records=count() by \$table | order by records desc" \-o tableAt least one App Service resource-specific table should appear after normal requests and ingestion. Table availability depends on the categories the app emitted.
Restore the subscription pricing tier
Restore the original pricing tier unless the authorized owner explicitly
decided to keep Standard. This lab changes only pricingTier; it does not
change a subplan or extension configuration.
- Azure CLI (az)
- Azure portal
az security pricing create \
--name AppServices \
--tier "$ORIGINAL_DEFENDER_TIER" \
--subscription "$SUBSCRIPTION_ID"
az security pricing show \
--name AppServices \
--subscription "$SUBSCRIPTION_ID" \
--query pricingTier -o tsv
The final value must equal the value you recorded before the lab. If it was
already Standard, this command leaves the plan enabled.
- Return to Defender for Cloud > Environment settings > your subscription > Defender plans.
- Set App Service back to the On or Off state you recorded before the lab.
- Leave unrelated plans unchanged and select Save.
- Refresh the page and confirm the state persisted.
Cleanup
To clean up the resources created in this lab, run the following command to delete the resource group. If you want to use the resources again, you can skip this step.
az group delete \
--name ${RG_NAME} \
--yes \
--no-wait
This will delete the resource group and all its contents.
Deleting the resource group removes the app, workspace, and its diagnostic
setting. It does not change Microsoft.Security/pricings at subscription
scope, so complete Restore the subscription pricing tier first.
Wait for deletion and verify it:
while [ "$(az group exists --name "$RG_NAME")" = "true" ]; do
sleep 10
done
az group exists --name "$RG_NAME"
Expected output:
false
Summary
You enabled Defender for App Service only after reviewing its subscription-wide scope, permissions, and cost. You safely inspected recommendations and alerts, routed App Service diagnostics to Log Analytics, verified the paid tier and destination, and restored the original subscription setting before deleting the lab resources.
Troubleshooting
AuthorizationFailedonaz security pricing create. Ask an authorized administrator for a role that includesMicrosoft.Security/pricings/writeat subscription scope. Do not broaden your own access or use another subscription without approval.- The portal plan toggle changes back. Check Azure Policy, management-group settings, or automation that enforces Defender plans. Coordinate with the subscription security owner instead of fighting the control.
- No recommendations appear. New resources need time for assessment. Check that the resource provider is registered and return later. An empty result is not a security attestation.
- No alerts appear. That is normal for a new app receiving benign traffic. Do not simulate attacks. Validate the plan state and review the documented App Service alert types.
- The diagnostic setting rejects a category. Run the categories-list
command again. Prefer the supported
allLogscategory group or select only categories returned for that resource. - No records reach Log Analytics. Generate normal requests, wait for ingestion, confirm the diagnostic setting's workspace ID, and verify the app emitted the selected log categories. Diagnostic settings do not create application log messages that the app never writes.
- Charges continue after resource-group deletion. Recheck the
AppServicespricing tier at subscription scope and check for resource-level Defender settings on remaining App Service plans.