Scoping an App Registration to Administrative Units for Graph API

We frequently get requests for App Registrations with User.Read.All or Group.Read.All application permissions at work. Due to the nature of the firm I work for, we are unable to grant these as it lets an application read everything across the tenant. It would be great if one could scope these to specific groups or users.

I know of admin units but for the longest time thought it’s not possible to scope an App Registration to that.

First Attempt

If I create an admin unit with a single user, for instance:

And create an App Registration with the User.Read.All permission:

And then add its service principal to the admin unit with a role (I will go with Groups Admin here, which is way more rights than what I need):

Let’s connect with the App Registration credentials now. I see the User.Read.All scope. But there’s no mention of the admin unit scoping, but that’s ok I suppose.

When I do a Get-MgUser though, I am able to see everyone – not just the admin unit user.

Hmm. And… I am unable to see the groups even though I had given it the Groups Admin role.

So it looks like the role that’s granted gets ignored.

This is pretty much where I had left things in the past.

Second Attempt

Today, however, I was curious and wanted to try more.

This was based on the fact that I know Exchange service principals work off the role that’s granted in Entra ID, so Graph should be able to do the same… right? Moreover, based on working with Intune recently, I had learnt that if an admin has both an Intune RBAC role and an Entra ID role, Intune suppresses the Entra ID role. So maybe I shouldn’t mix and match permissions?

With that in mind, I removed the permission in the App Registration.

Disconnect, reconnect, confirm the permission is gone:

Can I get users now? Nope! And can I get groups? Nope.

Bummer.

Oh hang on, I didn’t scope any groups in here.  I should add a group to the admin unit and try.

Create a group. Add two users into it. Only one of them – Adele – is in the admin unit, the other isn’t.

Add this group to the admin unit.

Rinse and repeat… hmm, no permissions!

Damn.

Third Attempt

After some time I resumed my efforts. Sure, it can’t see all groups… but can I search for a specific group? The one in the admin unit.

Yup, I can. And weirdly, I can see other groups too. That’s not good. :-/

So I removed the scoped admin role I granted, and now I can’t see anything. That means the admin role was working, but it wasn’t scoping correctly for some reason…?

Hmm.

Add the scoped role back, and it can enumerate all the groups again.

It has some limitations though. As noted earlier, I can search for groups – even the ones in scope. It can see the properties and memberships of all groups (including those not in scope) but it can only make changes to the ones in scope.

Maybe the Groups Admin role is a bit much? It’s more than I need for this task, but I was using it to keep life easy and not create a custom role. But let’s do that now.

Scope that in the admin unit.

Rinse and repeat…

And I still can see other groups. Blimey.

At this point I started Googling on whether there’s some default setting that allows one to see all settings. Came across this document but that’s for users being able to read other users, and the recommendation is to not disable it.

And then I realized that even though I had removed my application from the scoped groups admin role, it was still there!

I had definitely removed it –

And I did it again now.

But the damn thing remains. 🤬

Finally after trying to remove it a few more times it went away.

So this means my tests about with the scoped custom role weren’t accurate.

Disconnect & reconnect Graph, let’s try again:

Woo hoo!

So the lesson learnt is: roles like “Groups Administrator” when scoped let the role holder see more info than what’s necessarily in scope. They won’t be able to make changes, but they definitely can see. Go with custom roles if you want to avoid that (which is a good advice in general anyways – least permissions and all that).

Success. But more stuff.

Here’s me enumerating the membership.

I get the Ids, but what about the UPNs?

Just add the ability to read users too into that custom role I suppose? And here’s where I ran into another issue (a minor one + some nuances to get it all sorted eventually).

Notice the existing permissions of the role.

I go ahead and add new permissions. Select the existing ones:

And users:

But when you go to the final page, only the users role is there.

So it sounds like you can’t mix and match permissions between different sets into a single custom role? I don’t know. Didn’t find any info on it, but at this point I was too tired to do a thorough search.

What I did instead, is create a new custom role just for users, with the single user permission of above. And add it to the admin unit.

That should work, right?

(Quick recap at the point – the admin unit contains one user – Adele – and one group – containing Adele & more users).

Let’s try to get membership:

Nice! When I can see the Id of the users not in the admin unit but in the group, I can’t see any of their info. That is very neat! :)

However, in this case what I want is the ability to have an App Registration that can enumerate the membership of a group – so it must be able to see everyone in it.

What can I do here?

The straight forward option is to populate the admin unit with members of the group. Of course, it would be silly to do that manually, so I must tie the admin unit to members pulled dynamically. That’s doable. But the catch is when I do this I can no longer also add the group to it.

Admin unit with the group:

I changed the membership type from assigned:

To dynamic user:

When you do that, the membership changes:

Wait a few minutes for the scope to update, and the group disappears.

And the users appear:

So: you can’t mix groups and dynamic users together.

What this means is I need two admin units. One for groups, to which I can add the group custom role. And one for users, to which I add the user custom role.

That’s all.

And now the App Registration can use Graph API to see exactly what it is allowed to see.

Tests

Enumerating all users or groups fails.

Can only see the group in the admin unit.

And enumerate its members, with full info.

Awesome!

Took a fair bit of trial and error and effort, but totally worth it! :)