July 17, 2026
I Built the Automation. Then Someone Asked Me to Explain the Permission Model.
That second part is what actually matters.

By Daniel Santiago
4 min read
When I finished my joiner/mover/leaver automation lab, I thought the hard part was done.
Three PowerShell scripts, Microsoft Graph API, full identity lifecycle — provisioning new hires, handling role changes, and offboarding departures. Audit logging on every operation. It ran. I pushed it to GitHub and felt good about it.
Then I started doing interview prep, and I asked myself a question I couldn't cleanly answer: " Walk me through your permission model and why you made those decisions."
I knew what permissions I had assigned. I could open the Entra portal and point at them. But I couldn't explain why I chose those specific permissions over the alternatives, what I had considered and decided against, or what the actual risk looked like if one of those service principals got compromised.
That's a problem. Because in an interview, "I followed least privilege" is not an answer. It's a starting point. The answer is being able to explain what least privilege actually meant for the specific thing you built.
So I went back and wrote the document I should have written while I was building.
Three Service Principals Instead of One
The automation runs under three separate service principals — one for joiner operations, one for mover operations, and one for leaver operations. Each is scoped to only what it needs for its specific lifecycle event. They don't share credentials. They can't do each other's jobs.
The easy path when you're building something like this is one service principal with broad permissions. One credential to manage, one app registration, less complexity. I almost did it that way.
The reason I didn't come down to the blast radius. If a single broad service principal gets compromised, an attacker gets all three capabilities at once — they can provision rogue accounts, modify existing users, and disable legitimate accounts. That's your entire user directory.
Separate service principals contain that. A compromised joiner SP can create rogue accounts, which is bad, but it can't touch existing users. A compromised SP can mass-disable accounts, which is a genuinely serious risk and worth alerting on, but it can't provision anything new or assign any privileges.
The incident scope is different. The recovery scope is different. It was worth the extra complexity.
The Permission I Pulled Out
Early in the build, I had Directory.ReadWrite.All in the permission list for one of the service principals. It's a permission that comes up fast when you're Googling how to manage users programmatically through Graph. It solves problems. You stop getting the 403 errors.
It also grants control over the entire directory, including privileged objects. I didn't need it — User.ReadWrite.All covers what the scripts actually do. But it was easier to find in the documentation because it's broader.
I removed it and documented why. Not just that I removed it because what it would have granted wasn't needed, what the risk landscape looked like with it included versus without it.
That distinction matters more than I expected. Anyone can say they followed least privilege. The real question is whether you can explain what that actually meant for your specific implementation.
Why the Leaver Script Doesn't Hard Delete Accounts
This one surprised me when I really thought it through.
The leaver script disables accounts, revokes active sessions, and removes group memberships. It does not hard-delete user objects. Soft delete, 30-day retention window, that's the enforced model.
Part of this is a permission decision. Hard deletion requires Directory.ReadWrite.All, which I'd already excluded. But even if I'd included that permission, I wouldn't have put hard delete in an automated script.
Automated hard delete means one execution error, a wrong UPN, a timing issue, a wrong trigger condition, and you have an unrecoverable account loss. Getting a hard-deleted user back means a ticket to Microsoft Support and potentially lost data depending on your retention configuration. That's not a risk that belongs in a script running on a schedule.
Soft delete with a 30-day window is recoverable and takes human error into account. If something goes wrong, you have time to catch it and fix it. Hard delete, if it's ever actually needed, should be a manual operation with documented approval behind it. Also, this maps back to what we do in practice at my company. Currently, we never delete personal files or user names; we just disable them.
I put that in the permission model document. I explained the tradeoff and what it would look like to do this differently in a production environment.
What Writing the Document Actually Did
It took almost as long as building the scripts. That felt wrong at first.
But it forced me to answer questions I had glossed over during the build. Why Application permissions instead of Delegated? Because automation has no interactive session — delegated permissions require a logged-in user, which defeats the purpose. Why no PIM integration for time-bound access? That's a real gap, and I documented it as a future state item rather than pretending it wasn't a gap.
Those are the questions a senior engineer asks in a code review. Building the document before someone else asked them meant I actually had to think them through.
It also changed what I can do in an interview. Now I can walk through the threat model for each service principal — what an attacker gets if each one is compromised and what they still can't do. I can talk about what the next layer of this would look like in production. I can explain why the decisions I made are the decisions I made.
That's the difference between following a tutorial and being able to defend what you built.
I'm early in my career and still learning all of this. If something here is wrong or could be explained better, tell me — I'd genuinely rather know.
The permission model doc and the scripts are in the repo: github.com/DanielSantiagoSec/m365-iam-lifecycle-automation