The Session Token Never Expired
The user had not entered their password in months.
No MFA prompt. No reauthentication. No interruption. Every morning the laptop opened straight into Outlook, Teams, SharePoint, OneDrive and internal systems.
The session simply continued. Day after day. Week after week. Month after month.
Eventually, the user could not remember the password at all. Because they never needed to.
This Agent Foskett briefing looks at one of the most overlooked identity risks in modern Microsoft environments: persistent sessions, refresh tokens, remembered devices and authentication that quietly stays alive far longer than most organisations realise.
Briefing summary
Persistent Microsoft 365 sessions are often treated as convenience features, but long-lived authentication can quietly become a security blind spot. GEMXIT investigates how session persistence, remembered devices and weak sign-in policies create risk in real business environments.
What happened
The problem with “Stay signed in”
First hunt: long-lived sign-in activity
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
SigninLogs | where TimeGenerated > ago(30d) | summarize FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated), Apps = make_set(AppDisplayName), IPs = make_set(IPAddress) by UserPrincipalName, DeviceDetail, ClientAppUsed | extend SessionDurationDays = datetime_diff("day", LastSeen, FirstSeen) | order by SessionDurationDays desc
Second hunt: refresh tokens and persistent access indicators
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
SigninLogs | where TimeGenerated > ago(14d) | project TimeGenerated, UserPrincipalName, AppDisplayName, ClientAppUsed, IPAddress, ConditionalAccessStatus, AuthenticationRequirement, AuthenticationProcessingDetails, DeviceDetail | order by TimeGenerated desc
Third hunt: unmanaged devices with persistent access
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
SigninLogs | where TimeGenerated > ago(30d) | extend DeviceName = tostring(DeviceDetail.displayName), TrustType = tostring(DeviceDetail.trustType), Browser = tostring(DeviceDetail.browser), OperatingSystem = tostring(DeviceDetail.operatingSystem) | where isempty(TrustType) or TrustType !has_any ("Azure AD joined", "Hybrid Azure AD joined") | project TimeGenerated, UserPrincipalName, AppDisplayName, IPAddress, DeviceName, TrustType, Browser, OperatingSystem | order by TimeGenerated desc
Where this becomes dangerous
What businesses commonly say
What should organisations do?
How GEMXIT approaches session security reviews
Final thought
Continue the investigation with the MFA Session Hijacking briefing, When Nothing Looks Wrong, Identity and Access Security, Zero Trust and GEMXIT Security Review.
Develop IT. Protect IT. GEMXIT PTY LTD | GEMXIT UK LTD