During a recent engagement I stumbled across an interesting observation: the Defender for Cloud Apps advanced hunting table CloudAppEvents was empty, even though the tenant had adopted Microsoft 365 collaboration and security tooling.
No events in the CloudAppEvents table The Microsoft 365 app connector was reported as healthy, with the initial connection dating back to 2022:
Healthy App Connector However, when opening the connector, none of the Microsoft 365 components were actually selected:
No components selected So either someone forgot to tick the boxes back in 2022, or Microsoft used different auto-provisioning defaults at the time.
This is also called out in the Microsoft docs 1:
In January 2026, the default values were added to support complete security coverage. If you configured your application before January 2026, make sure you select all of the default options and select Connect again to update your configuration.1
For maximum protection, we recommend selecting all Microsoft 365 components. Some threat detection and response functionalities don’t work unless all required components are properly selected.1
Interestingly, on another, more recently onboarded tenant where the connector had never been modified, all boxes were ticked except for file monitoring.
Enhanced Filtering for Connectors1 (aka skip listing) lets Exchange Online and Defender for Office 365 see the actual sender IP address instead of only the last hop. This is required so that MDO can verify message authentication attributes such as SPF, DMARC, and DKIM. Microsoft recommends it in its guides for third party mail flows2 3 to get the full value out of MDO.
Without enhanced filtering, MDO only sees your 3rd party gateway as the sender for incoming e-mails. As a result, you lose important sender metadata, which degrades your experience in Threat Explorer, Advanced Hunting, and the Tenant Allow/Block List.
The mail flow in the environment roughly looks like this, with the 3rd party gateway acting as the MX for inbound mail and as the smart host for outbound mail. Exchange Online is connected to an on-premises Exchange server via the hybrid connector:
flowchart LR Internet([Internet]) Gateway[3rd PartyMail GatewayMX record] EXO[Exchange OnlineDefender for Office 365] OnPrem[On-PremisesExchange Server] %% Inbound flow Internet --> Gateway Gateway -- "Partner connector+ Enhanced Filtering" --> EXO EXO -- "Hybrid connector" --> OnPrem %% Outbound flow OnPrem -. "Hybrid connector+ Enhanced Filtering" .-> EXO EXO -. "Partner connector" .-> Gateway Gateway .-> Internet classDef cloud fill:#e6f2ff,stroke:#0078d4,color:#000 classDef onprem fill:#fff4e6,stroke:#d97706,color:#000 classDef gw fill:#f3e8ff,stroke:#7c3aed,color:#000 class EXO cloud class OnPrem onprem class Gateway gw Following the Microsoft recommendation, enhanced filtering was enabled on both the 3rd party connector and the Exchange hybrid connectors.
While troubleshooting unexpected re-authentication prompts in Entra ID, I stumbled over a legacy setting I had almost forgotten existed. The environment was piloting a new set of Conditional Access policies, and some pilot users received daily re-authentication prompts in Microsoft 365 apps, although no Conditional Access policy was forcing this.
Entra ID Protection was the first suspect, but no risk detections were present for the affected users. The actual reason was hiding in plain sight in the Entra sign-in logs, in the SessionLifetimePolicies field.
Inspecting SessionLifetimePolicies # A quick KQL query against SigninLogs reveals every distinct expirationRequirement reason observed in the tenant:
Distinct expirationRequirement reasons surfaced from SigninLogs SigninLogs | mv-expand parse_json(SessionLifetimePolicies) | extend ExpirationRequirement = tostring(SessionLifetimePolicies.expirationRequirement) | extend ExpirationDetail = tostring(SessionLifetimePolicies.detail) | distinct ExpirationRequirement, ExpirationDetail The sessionLifetimePolicy resource is also documented as part of the Microsoft Graph API1.
Legacy MFA settings # The rememberMultifactorAuthenticationOnTrustedDevices value stood out and reminded me of the Azure AD days and the legacy MFA settings. Sure enough, the Remember multi-factor authentication on trusted devices setting was still enabled:
Why are my Intune devices no longer compliant? Rolling out new compliance policies, raising minimum OS versions, or adjusting other controls can all cause devices to drift out of compliance. Ultimately, this impacts resource access whenever Conditional Access enforces a compliant device.
If you forward the IntuneOperationalLogs to a Log Analytics workspace1, you can query, parse, and alert on non-compliance events with just a few lines of KQL:
Anonymized example of noncompliant devices IntuneOperationalLogs | where OperationName == "Compliance" | extend Properties = parse_json(Properties) | evaluate bag_unpack(Properties) | where AlertType == @"Managed Device Not Compliant" | extend UserPrincipalName = iif(UserDisplayName != "System account", strcat(UserName, '@', UPNSuffix), "System account") // Extract the reason | extend ReasonRaw = tostring(split(Description, '||')[0]) // Parse the compliance Policy ID | parse ReasonRaw with ReasonParsed:string "_IID_" PolicyIdRaw:string | extend Reason = coalesce(ReasonParsed, ReasonRaw) | extend PolicyId = coalesce(PolicyIdRaw, 'DefaultDeviceCompliancePolicy') | project-away *Raw | project-reorder TimeGenerated, UserPrincipalName, DeviceHostName, IntuneDeviceId ,Reason, PolicyId Note The Properties column is a serialized JSON string that holds all the non-compliance details. The bag_unpack plugin2 expands every property in the bag into its own column, which keeps the rest of the query much simpler.
Managing Microsoft Sentinel table retention and tiers is typically done through scripts or the portal, but neither approach fits well into an infrastructure-as-code workflow and it can be difficult to maintain tables at scale. While exploring the Log Analytics Bicep resource provider, I came across the Microsoft.OperationalInsights/workspaces/tables resource type1, which makes it possible to manage table tier and retention declaratively with Bicep: version-controlled, reviewable, and reproducible across environments.
For each table we typically want to control:
Table tier: Analytics or Data Lake (Auxiliary) Interactive retention: the hot, queryable period Total retention: the overall retention period of the table Typical Table Configuration Settings Managing the total retention per table is necessary whenever you want to keep logs longer than the 90-day default to satisfy logging or compliance requirements. Since there is no workspace-level default for total retention, it has to be configured table by table, a perfect fit for a declarative approach.
Identify tables with recent ingestion # To identify ‘active’ tables, both a KQL- and API-based approach exist.
KQL # The following KQL query lists tables that have received data in the last 90 days along with their current tier, which is helpful when deciding what to put under Bicep management: