4.9 Building Sign-In Baselines

60 minutes · Module 4 · Free

Building Sign-In Baselines

Reactive investigation starts when an alert fires. Proactive monitoring starts before the alert — by building baselines of normal behaviour and detecting deviations. The queries in this subsection establish what “normal” looks like for your environment, so you can spot “abnormal” faster.

User-level baseline: normal IPs and locations

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
SigninLogs
| where TimeGenerated > ago(30d)
| where ResultType == 0
| extend Country = tostring(LocationDetails.countryOrRegion)
| extend City = tostring(LocationDetails.city)
| summarize
    SignInCount = count(),
    LastSeen = max(TimeGenerated),
    Apps = make_set(AppDisplayName)
    by UserPrincipalName, IPAddress, Country, City
| sort by UserPrincipalName, SignInCount desc

This creates a profile for each user: which IPs they use, from which cities and countries, how often, when last seen, and which apps. Store this as a Sentinel watchlist for VIP users — it becomes the reference against which you compare suspicious sign-ins.

Environment-level baseline: sign-in volume by hour

1
2
3
4
5
6
7
8
SigninLogs
| where TimeGenerated > ago(30d)
| where ResultType == 0
| summarize HourlyCount = count() by bin(TimeGenerated, 1h)
| summarize
    AvgHourly = avg(HourlyCount),
    StdDev = stdev(HourlyCount),
    MaxHourly = max(HourlyCount)

This gives you the average hourly sign-in volume and standard deviation. A spike that exceeds AvgHourly + (3 * StdDev) is statistically anomalous and worth investigating.

Application baseline: unusual app access

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let normalApps =
    SigninLogs
    | where TimeGenerated between (ago(30d) .. ago(1d))
    | where ResultType == 0
    | distinct UserPrincipalName, AppDisplayName;
SigninLogs
| where TimeGenerated > ago(1d)
| where ResultType == 0
| join kind=anti normalApps on UserPrincipalName, AppDisplayName
| summarize Count = count() by UserPrincipalName, AppDisplayName
| sort by Count desc

This finds users accessing applications they have never accessed before. A finance user suddenly accessing Azure Portal or a marketing user accessing Exchange Web Services for the first time could indicate compromise — the attacker is exploring what the stolen credentials can access.

Converting baselines to detections

Each baseline query can be promoted to a Sentinel analytics rule:

  1. Run the baseline query to establish normal
  2. Define the anomaly threshold (e.g. sign-ins from a new country, new IP, new application)
  3. Save as a scheduled analytics rule with appropriate severity and entity mapping
  4. Tune over time — adjust thresholds based on false positive rates
This is the bridge to Modules 5 and 10

Module 5 teaches Sentinel workspace configuration. Module 10 teaches building analytics rules. The baseline queries you write here become the scheduled detections you deploy there. Think of this subsection as writing the detection logic; Modules 5 and 10 teach you to operationalise it.

Module 4 — Final knowledge check

1. A user who normally signs in from 2 IPs in London suddenly has a successful sign-in from an IP in Brazil. What is the first query you run?

EmailEvents for the user
AADNonInteractiveUserSignInLogs for the Brazil IP — check if there is token replay activity from the same IP without a corresponding interactive sign-in
DeviceProcessEvents for the user's device

2. Your application baseline query shows a help desk user suddenly accessing Azure Key Vault for the first time. Why is this significant?

Azure Key Vault is a normal help desk tool
Key Vault access requires MFA
A help desk user has no legitimate reason to access Key Vault (which stores secrets, certificates, and encryption keys). This indicates either a compromised account or an insider threat exploring resources beyond their role.

3. You want to convert your "sign-in from new country" baseline into an automated detection. What is the next step?

Save the query as a scheduled analytics rule in Sentinel with entity mapping to the user account and IP address
Run the query manually every morning
Add it to a Sentinel workbook