The Graph Client Nobody Owns

Most tenants administer their directory through a Microsoft-owned client nobody in the organisation owns, with a tenant-wide consent grant that has no holder. Here is why the client matters more than the scopes.


Almost every tenant I am handed has the same object sitting in Enterprise applications with a directory write scope consented against it, and almost nobody can tell me who decided that or when. The object is not theirs. It belongs to Microsoft, it arrived without being asked for, and the standing authority attached to it has no holder. This is about why the client matters more than the scopes, and what changes when the client is one you own.

The object under discussion

Microsoft Graph Command Line Tools is application 14d82eec-204b-4c2f-b7e8-296a70dab67e, registered in the Microsoft tenant 72f988bf-86f1-41af-91ab-2d7cd011db47. It is the default public client the Microsoft Graph PowerShell SDK authenticates as when nobody tells it otherwise. It is a multi-tenant application, which is the reason Connect-MgGraph works on a directory where no engineer has ever registered anything: the first person to run it consents, and consent is what instantiates the object.

The service principal in your own directory usually does not exist until the first person runs the cmdlet. At that moment it is created as a side effect of somebody’s afternoon. No owner, no description, no naming convention, no record of intent, and no ticket. The client through which a large share of directory administration will subsequently pass enters the estate unannounced, and the person who caused it to exist almost certainly did not know they had created a durable object.

That is the whole shape of the problem in one sentence, and everything that follows is a consequence of it.


Granting a delegated permission does not annotate the application. It creates a directory object, an oAuth2PermissionGrant, whose clientId is the service principal of the client, whose resourceId is the service principal of Microsoft Graph, and whose scope property is a single space-separated string of permission claim values. Tenant-wide admin consent writes that object with consentType set to AllPrincipals and principalId null.

Read that literally. The grant is not attached to the engineer who needed it. It is not attached to a team, a role, or a change record. It is attached to every principal in the directory, and it stays that way until somebody deletes it. The identity of the person whose problem justified the grant survives only in an audit log entry that ages out.

A tenant-wide grant on a shared client is a standing authority with no holder. Nobody was given it, so nobody can be asked to give it back.

The grant is also cumulative by construction. Because scope is one string on one object, every subsequent person who needs one more permission appends to the same value. There is no per-request record, no per-scope justification, and no natural expiry. In an estate of any age the string is a sediment layer: you can read the history of every incident that ever needed one more permission, and none of the removals, because there are none.

Nor is writing that object reserved to the most privileged role in the tenant. Granting tenant-wide admin consent is a documented Cloud Application Administrator operation. That is appropriate delegation for an application estate you designed. It is a different proposition when the client being consented is a shared Microsoft object that every account in the directory can authenticate through.


The intersection is real and it is not a safety net

Microsoft’s own guidance describes effective delegated permission as the intersection of what the application is permitted to do and what the signed-in user is permitted to do. This is accurate, and it is the single most over-quoted sentence in the subject. It gets used as reassurance: the scope cannot exceed the user, the user has no admin role, therefore the exposure is small.

The reassurance depends entirely on the second circle being small. In a default tenant it is not. A plain member user holding no directory role can enumerate every user and every group in the directory, read their properties, invite guests, register applications, and create security groups. Group creation is not gated by a role for the default user. It is gated by allowedToCreateSecurityGroups on defaultUserRolePermissions in the tenant authorization policy, and it ships permissive. Turning it off does not take the capability away from Groups Administrator or User Administrator. It takes it away from everyone else, which is the point, and it is a decision somebody has to have made.

So the reasoning that gets applied at review time, the one where an engineer concludes that a user without User Administrator cannot do much harm with a directory write scope, fails on the default configuration. Put a directory write scope in that user’s token and the group creation succeeds, because the constraint everyone was relying on was never a role check. It was a tenant setting nobody had looked at, defaulting the other way.

The intersection model is sound. The problem is that most architects have never enumerated the baseline the intersection is drawn against, and the baseline is generous. Anyone reasoning about delegated exposure without first reading their own default user permissions is guessing.


The control everyone reaches for is not available here

The instinct at this point is Conditional Access. Require a compliant device, or a phishing-resistant method, or a named location, for anyone using Graph PowerShell. It is the right instinct and it does not work, for a structural reason worth understanding rather than working around.

Conditional Access sets requirements for accessing a resource. It is not applied to a public client, because a public client is not a resource; policy is evaluated when that client requests a token for something. Microsoft Graph Command Line Tools is a public native client, so it does not appear as a selectable target and no policy attaches to it. Microsoft Graph itself is an umbrella resource and is likewise not individually targetable. Microsoft’s current position is that you read audience reporting in the sign-in logs to discover which underlying services a Graph token was actually issued for, and target those services instead. That is a real technique and it is worth knowing. It is not a substitute for targeting the client, because the audience set is a property of what the tool happened to call on that occasion, not of who was holding it. What you are left with is a policy over all resources, which applies to every token request in the tenant and cannot distinguish an engineer at a prompt from Outlook opening on a phone, or a policy over a service the client is under no obligation to keep calling.

This is the part that surprises people, and it is the part that reframes the problem. The lever most estates use to govern administrative access does not reach this client. Three things remain: whether the service principal requires an assignment before it will issue tokens, what the consent grant contains and who it names, and what roles the signing-in user holds. Every one of those is a property of an object. Two of those objects are Microsoft’s.


Ownership is the entire argument

Register your own client. A single-tenant public client application in your directory, named for what it is used for, with the redirect URIs the SDK needs, and connect to it with Connect-MgGraph -ClientId. Microsoft documents this as the supported way to isolate and limit the consent granted for Graph PowerShell use. It is not a workaround and it is not exotic. It is the difference between administering your directory through your own object and administering it through a borrowed one.

What that buys is not a new security feature. It is the return of every control you already know how to operate. The application has an owner and a name that states its purpose. The assignment requirement is a property you set and Microsoft has no reason to reset. Consent grants belong to a client only your named administrators can authenticate through, which means a tenant-wide grant is no longer tenant-wide in effect. The client has its own application ID in the sign-in logs, so Graph administration becomes a distinct, filterable stream rather than noise inside a first-party identity shared with unrelated tooling. And the object is deletable, which makes revocation an actual move rather than an aspiration.

Owning the client also makes the finer instrument worth using. A delegated grant can be written with consentType set to Principal and a principalId naming one user, which produces a grant that belongs to a person rather than to the directory. Almost nobody does this on the shared client, and reasonably so, because on a client nobody owns it is a fussy control with no lifecycle around it. On a client you own, with a group behind the assignment requirement and a review cadence over the membership, it becomes the normal way to hand out a high-privilege scope to the two people who need it.

The question was never which scopes to consent. It is whose object holds them.


What to do with the shared client afterwards

Standing up your own client does not remove the other one. You cannot delete an application registered in Microsoft’s tenant. The portal states the division of authority plainly, if you read the notice above the fields on the enterprise application’s Properties page: some of the displayed properties are not editable and are managed on the application registration in the application’s home tenant. Name, homepage and logo are greyed out. What is left editable is the local half, and the local half is where your control lives. Chief among it is the setting the portal calls Assignment required? and Graph calls appRoleAssignmentRequired. Create the service principal if it is not there yet, then set that property so only explicitly assigned principals can obtain tokens through it. That property is documented, it applies to sign-in and token issuance, and it is the closest thing to a switch that exists here. It also has a hole in it that you should know about before you rely on it: Microsoft documents that the assignment requirement does not apply to Global Administrators. The gate constrains routine administration. It does not constrain the accounts with the most reach, which is the opposite of the direction a control usually needs to point.

Be honest about what turning it on does. The default client is what the Graph PowerShell SDK and a large amount of community tooling authenticate as when nobody passes a client ID, and the full list of what sits behind that application ID in your estate is not something you can look up. You read it out of your own sign-in logs or you do not know it. Requiring assignment breaks every one of those for anyone not on the list. That is the intended effect, and it is also a change with a blast radius that deserves a date, a communication, and a period of watching the sign-in logs first to learn who is actually using it. Measuring before switching is not caution for its own sake. The list of people currently authenticating through that client is the only accurate inventory you will ever get of who has been administering your directory from a prompt.

Expect the object to be resilient. It is Microsoft’s application and your assignment requirement is a property on something you did not author, so if the service principal is ever deleted the next consent recreates it without the flag. Treat the gate as a control that needs monitoring rather than a door you closed once.


Who answers for it

Every access decision in a directory should have a holder. A role assignment has one. A group membership has one. A PIM activation has one, with a justification and a clock. A tenant-wide consent grant on a shared first-party client has none, and that is not an oversight in Microsoft’s model. It is the honest consequence of consenting on an object that is not yours to govern.

Revocation makes the point sharply. You can delete the grant, and the access it conferred stops being issued, but existing access tokens remain valid until they expire. So even the strongest action available to you is a decision that takes effect on somebody else’s schedule, against an object somebody else owns, affecting a population you never enumerated.

Move the client and all of that becomes ordinary again. The grant sits on an application with an owner, gated by a group with a review, visible as its own identity in the logs, and deletable on the day you decide. Nothing about the scopes changed. What changed is that there is now a name on the other end of the question.


The companion build sheet registers the client, gates it behind an assignment requirement, writes the consent grant at the audience you chose, validates the result end to end, and puts the shared client behind the same gate.


Build sheet: The Graph Client Nobody Owns: Build Sheet