Entra ID Guest User Lifecycle Management — Automated, License-Free & Auditable

Entra ID Guest User Lifecycle Management — Automated, License-Free & Auditable

Managing guest users in Entra ID can quickly become a compliance headache. Stale accounts, forgotten access, and manual cleanup are a recipe for risk. But what if you could automate the entire lifecycle — from detection to deletion — without shelling out for premium identity governance licenses?

That’s exactly what this solution delivers: a lightweight, scalable guest user lifecycle management system built entirely with Azure Logic Apps, Managed Identity, and the Microsoft Graph API.

🧩 Architecture Overview

This setup uses two independent Logic Apps, each with a clear purpose:

  1. Mark inactive guests for removal
  2. Delete marked guests after a grace period

Each app runs on a weekly schedule, emits detailed JSON reports, and supports a report-only mode for safe testing.

💤 Logic App 1 — Mark Guests Eligible for Removal

This app identifies inactive guest users based on sign-in activity and marks them for removal.

⏱️ Schedule: Weekly (e.g., Sunday 01:00)
🔍 Evaluation:

  • Checks all members of a dynamic group (All Guests)
  • Flags users who never signed in or haven’t signed in for a configurable number of days

🏷️ Actions:

  • Sets extensionAttribute14 = "MarkedEligibleforRemoval"
  • Sets extensionAttribute15 = <UTC timestamp>
  • Disables the account if ReportOnly = false

📤 Output:

  • JSON report with evaluated, marked, and disabled users

⚙️ Parameters:

  • Threshold: Days since last sign-in (default: 180)
  • ReportOnly: true/false
  • BatchSize: Users per run (default: 20)

🧹 Logic App 2 — Delete Guests After Grace Period

This app deletes guest users who were previously marked and have exceeded the grace period.

⏱️ Schedule: Weekly (e.g., Sunday 03:00)
🔍 Evaluation:

  • Checks members of the Eligible For Removal group
  • Deletes users if:
    • extensionAttribute14 = "MarkedEligibleforRemoval"
    • extensionAttribute15 is older than the deletion threshold
    • accountEnabled = false

🗑️ Action:

  • Deletes user when ReportOnly = false

📤 Output:

  • JSON report with evaluated, deleted, and failed users

⚙️ Parameters:

  • DeletionThresholdDays: Days since marking (default: 30)
  • ReportOnly: true/false
  • BatchSize: Users per run (default: 20)

👥 Dynamic Group Setup

Two dynamic security groups help scope and monitor the lifecycle:

  • All Guests
    (user.userType -eq "Guest") and (user.extensionAttribute15 -ne "ExcludeFromLCM") and (user.accountEnabled -eq true)
  • Eligible For Removal
    (user.extensionAttribute14 -eq "MarkedEligibleforRemoval") and (user.accountEnabled -eq false)

💡 Exclude VIPs or service accounts by setting extensionAttribute15 = "ExcludeFromLCM"

🔐 Prerequisites

  • Azure subscription with Logic App deployment permissions
  • Entra ID tenant with guest users
  • Two free extension attributes (e.g., 14 & 15)
  • Microsoft Graph API permissions for:
    • Sign-in logs (AuditLog.Read.All)
    • User read/write (User.ReadWrite.All)
    • Group read (Group.Read.All)

🆔 Enable System-assigned Managed Identity for each Logic App

📦 Deployment & Templates

Ready to roll this out in your tenant?
The full solution — including templates, CLI scripts, and setup instructions — is available on Github.
Just import, configure, and automate.

🧪 Testing & Verification

Start with ReportOnly = true to validate logic and outputs.
Check group memberships and run outputs to confirm correct behavior.
Once verified, switch to ReportOnly = false to activate lifecycle actions.

📊 Auditable Reports

Each Logic App emits a structured JSON report for every run.
Example (Mark app):

{
  "totalEvaluated": 120,
  "totalMarked": 18,
  "totalDisabled": 18,
  "details": [
    {
      "userPrincipalName": "guest1@contoso.com",
      "reason": "No sign-in activity in the last 180 days"
    }
  ]
}

Example (Delete app):

{
  "totalEvaluated": 42,
  "totalDeleted": 12,
  "totalFailed": 0,
  "details": [
    {
      "userPrincipalName": "guest1@contoso.com",
      "markedOn": "2025-01-01T00:00:00Z",
      "action": "Deleted"
    }
  ]
}

🛡️ Safety Tips

  • Always start in report-only mode
  • Exclude critical guests with ExcludeFromLCM
  • Monitor Graph API throttling
  • Export reports to Storage or Log Analytics for auditing
  • Restrict Logic App modification permissions

🧰 Troubleshooting

No users marked?

  • Check group membership and sign-in data
  • Validate Graph permissions

🗂️ Users not deleted?

  • Confirm attribute values and timestamps
  • Ensure accountEnabled = false

🔑 Permission errors?

  • Re-run CLI scripts with correct Managed Identity ID
  • Verify Graph App Role assignments

✨ Final Thoughts

This solution brings clarity, control, and compliance to guest user lifecycle management — all without premium licensing. Whether you’re cleaning up stale accounts or enforcing tighter access hygiene, this setup gives you the tools to do it safely, scalably, and audibly.

Ready to automate your guest cleanup? You’re just two Logic Apps away.

7 responses to “Entra ID Guest User Lifecycle Management — Automated, License-Free & Auditable”

  1. Arron Avatar
    Arron

    Script is very nice, unfortunately it does not take non-interactive sign-ins in consideration. Could it be possible to include that?

    1. Daniel Fraubaum Avatar

      Thanks for your feedback!
      The script currently checks the signInActivity property on each user, prioritizing lastSuccessfulSignInDateTime (which includes both interactive and non-interactive successful sign-ins) and falls back to lastSignInDateTime (last interactive sign-in, successful or failed).
      So, non-interactive sign-ins are already considered if they were successful, as they update lastSuccessfulSignInDateTime. If you want to include failed non-interactive sign-ins or get more granular, you’d need to query the sign-in logs directly via Microsoft Graph and filter for specific event types.

  2. Oli Avatar
    Oli

    Hi Daniel. Thank you for creating this, it’s exactly what we are after. However I have had a few problems getting it up and running.

    For the first app I amended the guestSourceGroupId parameter in the json file on line 14, plus made sure the ReportOnly parameter was set to true on line 28, however after importing the file and creating the logic app, it seemed to completely ignore these settings and only went with the values on lines 58-76. So I had to amend them in the logic app designer. Is that expected?

    Second, my group of guest users contains over 800 members, but when I run the app (in report mode) it only marks 40-50 of these as eligible for removal (there should be much more). Drilling in to the run history, in the GetUserInfosBatch job, many of the requests are returning this:

    “body”: {
    “error”: {
    “code”: “UnknownError”,
    “message”: “Too Many Requests”

    Do you have any advice for this? Or would it just be a case of running the app regularly until enough users have been removed?

    1. Daniel Fraubaum Avatar

      Hi Oli, thanks for your feedback! You don’t need to adjust the json file, you can adjust the parameters in the logic app designer after importing it.
      I only have testet it with 500 Users so far … Normally yes, if it runs regulary the size of the group should get smaller and then without api limits …

      1. Oli Avatar
        Oli

        Looks like there is some code to check if a batch is being rate limited (Check GetUserInfosBatch Error) but it only checks the header of the batch, which returns status 200 even if all the requests inside it fail.

  3. Oli Avatar
    Oli

    Got this all in and working now, works very well (apart from the rate limiting issue mentioned above).

    Just going to post this here for anyone else like me who has never used logic apps before.

    To create the logic apps:
    – First create a new resource group in Azure for the apps.
    – In the resource group, click on Create, then search for “Template deployment (deploy using custom templates)”.
    – Click create, then ‘Build your own template in the editor’.
    – Copy in the JSON file from the Git, leave the parameter values as they are, except “location” – change this to your region. Save.
    – Check subscription, resource group and region are correct, then create.
    – Open the newly created app, then go to Development Tools – > Logic app designer.
    – Open Parameters and fill in correct guestSourceGroupId (and change other values if you want).

    To apply the Graph permissions:
    – In the logic app, go to Settings -> Identity and copy the object ID (turn system assigned managed identity on if its not).
    – Download the .sh script file from the Git, and edit the miPrincipalId field with the above ID.
    – In Azure, open the Cloud Shell. Change to Bash if its in Powershell.
    – Use “az account set –subscription ‘my-subscription-name’” to connect to the correct subscription.
    – Upload the .sh file using the manage files button.
    – Run “chmod +xr” on the script to grant execute permissions (e.g. chmod +xr Logic-EntraIDGuestLCM-DeleteGuestUsersAfterGracePeriod_AzureCLI_Permissions.sh)
    – Run the script (e.g. ./Logic-EntraIDGuestLCM-DeleteGuestUsersAfterGracePeriod_AzureCLI_Permissions.sh).

    1. Daniel Fraubaum Avatar

      Thanks Oli for this detailed instructions!

Leave a Reply

Your email address will not be published. Required fields are marked *