Was trying to find out if there’s a way of letting others in our firm create App Registrations and add permissions to them, but limit what permissions they can admin consent to. You know, maybe allow them to admin consent delegated permissions but not application permissions. Or even allow application permissions but not certain type of application permissions. Typically you need to be either a Global Admin or a Privileged Role Admin to be able to do this, and that gives out a lot more permissions than needed.
Looks like the ability to do this was released about a year ago. What you need to do is:
- Create app consent policies as detailed in this link (this is what defines what consents someone can do), and
- Add these consent policies to custom roles that you create as detailed in this link (this is what applies the app consent policies to users & groups who have that custom role).
All of this needs to be done via PowerShell, and some of these even require the AzureADPreview
module (even though it’s been a year since release of this feature).
So let’s get cracking!
First, install/ enable the preview module and connect to AzureAD.
1 2 3 |
Remove-Module AzureAD -ErrorAction SilentlyContinue Import-Module AzureADPreview Connect-AzureAD |
After that, if you want to see the existing app consent policies you can do so with the following cmdlet:
1 |
Get-AzureADMSPermissionGrantPolicy | ft Id, DisplayName, Description |
Here’s what the default output looks like:
Creating a new app consent policy
Creating a new app consent policy is via the New-AzureADMSPermissionGrantPolicy
cmdlet to create a policy and then the New-AzureADMSPermissionGrantConditionSet
to add permissions to that policy.
Let’s make an app consent policy that allows consent to delegated permissions only. This one’s very simple:
1 2 3 4 5 6 7 8 9 |
New-AzureADMSPermissionGrantPolicy ` -Id "mytenant-all-delegated-permissions" ` -DisplayName "All delegated permissions, for any client app" ` -Description "Permissions consentable by Application Administrators (Level 1)" New-AzureADMSPermissionGrantConditionSet ` -PolicyId "mytenant-all-delegated-permissions" ` -ConditionSetType "includes" ` -PermissionType "delegated" |
Note the Id (mytenant-all-delegated-permissions
). We need it later.
Let’s also make an app consent policy that allows consent to any application permissions; but not a few critical ones. For example: I don’t want someone to be able to consent to an application permission that lets them modify roles… that would be silly after all as anyone who can do this consent will be able to escalate themselves to the Global Admin role for instance. :)
The official document has an example like the one below where they exclude certain delegated permissions:
1 2 3 4 5 6 7 8 9 10 11 12 |
New-AzureADMSPermissionGrantConditionSet -PolicyId "test1" -ConditionSetType "excludes" -PermissionType "delegated" -Permissions @("8b590330-0eb2-45d0-baca-a00ecf7e7b87", "dac1c8fa-e6e4-47b8-a128-599660b8cd5c", "f6db0cc3-88cd-4c74-a374-3d8c7cc4c50b") -ResourceApplication "ec8d61c9-1cb2-4edb-afb0-bcda85645555" -PermissionClassification "low" -ClientApplicationsFromVerifiedPublisherOnly $true -ClientApplicationIds @("4a6c40ea-edc1-4202-8620-dd4060ee6583", "17a961bd-e743-4e6f-8097-d7e6612999a7") -ClientApplicationTenantIds @("17a961bd-e743-4e6f-8097-d7e6612999a8", "17a961bd-e743-4e6f-8097-d7e6612999a9", "17a961bd-e743-4e6f-8097-d7e6612999a0") -ClientApplicationPublisherIds @("verifiedpublishermpnid") Id : 0f81cce0-a766-4db6-a7e2-4e5f10f6abf8 PermissionType : delegated PermissionClassification : low ResourceApplication : ec8d61c9-1cb2-4edb-afb0-bcda85645555 Permissions : {8b590330-0eb2-45d0-baca-a00ecf7e7b87, dac1c8fa-e6e4-47b8-a128-599660b8cd5c, f6db0cc3-88cd-4c74-a374-3d8c7cc4c50b} ClientApplicationIds : {4a6c40ea-edc1-4202-8620-dd4060ee6583, 17a961bd-e743-4e6f-8097-d7e6612999a7} ClientApplicationTenantIds : {17a961bd-e743-4e6f-8097-d7e6612999a8, 17a961bd-e743-4e6f-8097-d7e6612999a9, 17a961bd-e743-4e6f-8097-d7e6612999a0} ClientApplicationPublisherIds : {verifiedpublishermpnid} ClientApplicationsFromVerifiedPublisherOnly : True |
How do you get those permission ids though? The document simply says -Permissions
are “The identifier of the resource application to scope consent operation down to. It could be @("All") or a list of permission ids.
” Not very helpful.
The app consent document is slightly more helpful.
Hmm, so it’s in the ServicePrincipal object of the API. I am interested in the Graph API so lets dig into that.
First I’ll search for the ServicePrincipal object and filter to any with the words “Graph” in it:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Get-AzureADServicePrincipal -All $true | ?{ $_.DisplayName -match "Graph" } ObjectId AppId DisplayName -------- ----- ----------- 2362f192-9721-4089-b2c9-6acf3e9ce553 d88a361a-d488-4271-a13f-a83df7dd99c2 IDML Graph Resolver Service and CAD 2f4d6758-e00b-4037-a933-8b5224f00489 765fe668-04e7-42ba-aec0-2c96f1d8b652 Exchange Office Graph Client for AAD - Noninteractive 327ba63b-334e-4004-bb30-20a607de4098 00000003-0000-0000-c000-000000000000 Microsoft Graph 57943d81-ce4c-4a80-ae3f-56ce03c6a8fd 6da466b6-1d13-4a2c-97bd-51a99e8d4d74 Exchange Office Graph Client for AAD - Interactive 68420e79-2754-4dbd-9819-7049a2820601 4bfd5d66-9285-44a1-bb14-14953e8cdf5e Audit GraphAPI Application 895b3d4b-d0b6-4102-8e01-d19ad243a7df 0bf30f3b-4a52-48df-9a82-234910c4a086 Microsoft Graph Change Tracking 9479fa7c-c94a-4c73-8599-92b762ac0029 ab3be6b7-f5df-413d-ac2d-abf1e3fd9c0b Microsoft Teams Graph Service 9b00ad24-77e1-4d53-be39-19e1bf08aa0d ba23cd2a-306c-48f2-9d62-d3ecd372dfe4 OfficeGraph bea6ee98-3321-47f5-aff4-4f4844c68c35 56c1da01-2129-48f7-9355-af6d59d42766 Graph Connector Service |
I think Microsoft Graph
is what I am looking for? Let’s expand that:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
Get-AzureADServicePrincipal -ObjectId 327ba63b-334e-4004-bb30-20a607de4098 | fl * DeletionTimestamp : ObjectId : 327ba63b-334e-4004-bb30-20a607de4098 ObjectType : ServicePrincipal AccountEnabled : true AddIns : {} AlternativeNames : {} AppDisplayName : Microsoft Graph AppId : 00000003-0000-0000-c000-000000000000 AppOwnerTenantId : f8cdef31-a31e-4b4a-93e4-5f571e91255a AppRoleAssignmentRequired : False AppRoles : {class AppRole { AllowedMemberTypes: System.Collections.Generic.List`1[System.String] Description: Allows the app to read online meeting artifacts in your organization, without a signed-in user. DisplayName: Read online meeting artifacts Id: df01ed3b-eb61-4eca-9965-6b3d789751b2 IsEnabled: True Value: OnlineMeetingArtifact.Read.All } , class AppRole { ... <snip> ... , class AppRole { AllowedMemberTypes: System.Collections.Generic.List`1[System.String] Description: Allows the app to manage workforce integrations to synchronize data from Microsoft Teams Shifts, without a signed-in user. DisplayName: Read and write workforce integrations Id: 202bf709-e8e6-478e-bcfd-5d63c50b68e3 IsEnabled: True Value: WorkforceIntegration.ReadWrite.All } ...} DisplayName : Microsoft Graph ErrorUrl : Homepage : KeyCredentials : {} LogoutUrl : Oauth2Permissions : {class OAuth2Permission { AdminConsentDescription: Allows the app to read online meeting artifacts on behalf of the signed-in user. AdminConsentDisplayName: Read user's online meeting artifacts Id: 110e5abb-a10c-4b59-8b55-9b4daa4ef743 IsEnabled: True Type: User UserConsentDescription: Allows the app to read online meeting artifacts on your behalf. UserConsentDisplayName: Read user's online meeting artifacts Value: OnlineMeetingArtifact.Read.All } , class OAuth2Permission { AdminConsentDescription: Allows the app to read and manage the active role-based access control (RBAC) assignments for your company's directory, on behalf of the signed-in user. This includes managing active directory role membership, and reading directory role templates, directory roles and active memberships. AdminConsentDisplayName: Read, update, and delete all active role assignments for your company's directory Id: 8c026be3-8e26-4774-9372-8d5d6f21daff IsEnabled: True Type: Admin UserConsentDescription: Allows the app to read and manage the active role-based access control (RBAC) assignments for your company's directory, on your behalf. This includes managing active directory role membership, and reading directory role templates, directory roles and active memberships. UserConsentDisplayName: Read, update, and delete all active role assignments for your company's directory Value: RoleAssignmentSchedule.ReadWrite.Directory } ... <snip> ... } ...} PasswordCredentials : {} PreferredTokenSigningKeyThumbprint : PublisherName : Microsoft Services ReplyUrls : {} SamlMetadataUrl : ServicePrincipalNames : {00000003-0000-0000-c000-000000000000/ags.windows.net, 00000003-0000-0000-c000-000000000000, https://canary.graph.microsoft.com, https://graph.microsoft.com...} ServicePrincipalType : Application Tags : {} |
Yup, jackpot! Let’s look at what AppRoles
are available as I am interested in application level permissions:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
(Get-AzureADServicePrincipal -ObjectId 327ba63b-334e-4004-bb30-20a607de4098).AppRoles | ft Value,DisplayName,Id Value DisplayName Id ----- ----------- -- OnlineMeetingArtifact.Read.All Read online meeting artifacts df01ed3b-eb61-4eca-9965-6b3d789751b2 AppCatalog.ReadWrite.All Read and write to all app catalogs dc149144-f292-421e-b185-5953f2e98d7f AppCatalog.Read.All Read all app catalogs e12dae10-5a57-4817-b79d-dfbec5348930 WorkforceIntegration.ReadWrite.All Read and write workforce integrations 202bf709-e8e6-478e-bcfd-5d63c50b68e3 Presence.ReadWrite.All Read and write presence information for all users 83cded22-8297-4ff6-a7fa-e97e9545a259 TeamworkTag.ReadWrite.All Read and write tags in Teams a3371ca5-911d-46d6-901c-42c8c7a937d8 TeamworkTag.Read.All Read tags in Teams b74fd6c4-4bde-488e-9695-eeb100e4907f WindowsUpdates.ReadWrite.All Read and write all Windows update deployment settings 7dd1be58-6e76-4401-bf8d-31d1e8180d5b ExternalConnection.ReadWrite.OwnedBy Read and write external connections f431331c-49a6-499f-be1c-62af19c34a9d |
Best to put them into a CSV so I can easily read in Excel:
1 |
(Get-AzureADServicePrincipal -ObjectId 327ba63b-334e-4004-bb30-20a607de4098).AppRoles | select Description,DisplayName,Value,Id | Export-Csv -NoTypeInformation appPermissions.csv |
Here’s a few I want to exclude (highlighted):
I know the -Permissions
parameter expects an array of the Ids so let’s create that. I put all the Ids I want to exclude into a text file (called excludedIds.txt
in the snippet below). Then I read them into an array:
1 |
$excludedIds = Get-Content .\excludedIds.txt |
Then I create the app consent policy. I learnt a bunch of things through trial and error here so I’ll summarize them below:
- I have to first include/ allow everything and then do an exclusion – i.e. I cannot create a policy having only exclusions as I have to define the set of policies that it is allowed in the first place and then exclude from that.
- When excluding I have to specify the resource application whose permissions I am excluding. In this case it’s the Graph API. However, I don’t use the
ObjectId
property of the Service Principal but theAppId
. - As a best practice let’s also specify the resource application when defining what is allowed/ included. Because if I leave that empty then it means all permissions of all APIs are allowed – and that’s not what we want here. I want to allow all permissions of the Graph API (because I selectively disable a few later), so if I don’t restrict the allowing also to just the Graph API it means any other APIs like (say) Exchange Online and PowerBI etc. are allowed. That means someone could potentially consent to an application level permission for the Exchange Online API to do a
full_access_as_app
as I am not disallowing anything to do with this API later on. Oops!
Here’s what I did:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# Create the policy New-AzureADMSPermissionGrantPolicy ` -Id "mytenant-all-application-permissions-except-a-few" ` -DisplayName "All application permissions, for any client app except a few selected ones" ` -Description "Permissions consentable by Application Administrators (Level 2)" # Create the permission grants allowing all application permissions for the Graph API New-AzureADMSPermissionGrantConditionSet ` -PolicyId "mytenant-all-application-permissions-except-a-few" ` -ConditionSetType "includes" ` -PermissionType "application" ` -ResourceApplication "327ba63b-334e-4004-bb30-20a607de4098" # Create the permission grants excluding the permissions in the array I previously created. I am casting it as an array again, but that's just me being OCD. The variable is already an array. New-AzureADMSPermissionGrantConditionSet ` -PolicyId "mytenant-all-application-permissions-except-a-few" ` -ConditionSetType "excludes" ` -PermissionType "application" ` -Permissions @($excludedIds) ` -ResourceApplication "327ba63b-334e-4004-bb30-20a607de4098" |
That’s it! So now I have two permission policies – one for all delegated permissions (for all APIs – just clarifying that); another for all except a few applications permissions for the Graph API.
Creating the custom role
The next step is to add the above permission policies to a custom role. This can’t be done via the Portal, however. You can create a custom role on the portal and assign it to users, but the specific bit of adding the above permission policies needs to be done via PowerShell.
There’s two permissions one can add to the custom role. Here’s the full list of permission policies related permissions, but the first two are what is of interest:
In my case I want the admins to be able to consent on behalf of themselves and/ or tenant. If you don’t want to do either of these then skip that permission when you do this.
My idea is to also have two custom roles. One for Level 1 Application admins – who can do only delegated permissions for themselves and/ or tenant – and another for Level 2 Application admins who can do delegated and application permissions (subject to exclusions as above) for themselves and/ or tenant.
If you want to create a new custom role and add the permissions you can do it thus:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
$displayName = "Application administrator (Level 2)" $description = "Can manage most aspects of application registrations." $templateId = (New-Guid).Guid # Set of permissions to grant $allowedResourceAction = @( "microsoft.directory/applications/create", "microsoft.directory/servicePrincipals/allProperties/read", "microsoft.directory/servicePrincipals/create", "microsoft.directory/servicePrincipals/managePermissionGrantsForSelf.mytenant-all-graph-app-permissions-except-a-few", "microsoft.directory/servicePrincipals/managePermissionGrantsForAll.mytenant-all-graph-app-permissions-except-a-few", "microsoft.directory/servicePrincipals/managePermissionGrantsForSelf.mytenant-all-delegated-permissions", "microsoft.directory/servicePrincipals/managePermissionGrantsForAll.mytenant-all-delegated-permissions" ) $rolePermissions = @{'allowedResourceActions'= $allowedResourceAction} # Create the custom role $customAdmin = New-AzureADMSRoleDefinition -RolePermissions $rolePermissions -DisplayName $displayName -Description $description -TemplateId $templateId -IsEnabled $true |
I’ll delve into the additional permissions above in a bit.
If you already have a custom role and only want to add these new permissions to it you can do it thus:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# Name of the custom role I want to modify $displayName = "Application administrator (Level 1)" # Find the custom role $customRole = Get-AzureADMSRoleDefinition | Where-Object{$_.DisplayName -eq $displayName } # Set of permissions to add $allowedResourceAction = @( "microsoft.directory/applications/managePermissionGrantsForSelf.mytenant-all-delegated-permissions", "microsoft.directory/applications/managePermissionGrantsForAll.mytenant-all-delegated-permissions" ) # Add our permissions to the existing list $allowedResourceAction += $customRole.RolePermissions.AllowedResourceActions $rolePermissions = @{'allowedResourceActions'= $allowedResourceAction} Set-AzureADMSRoleDefinition -Id $customRole.Id -RolePermissions $rolePermissions |
And that’s it really! Now add users to the appropriate role group and they can do what you have allowed.
Those additional permissions
I added a few extra permissions above so the user in that custom role can also create App Registrations. You can get of the permissions at this link. I wanted users to be able to 1) create app registrations (microsoft.directory/applications/create
), 2) create the associated service principal (microsoft.directory/servicePrincipals/create
), and 3) read all properties of service principals (microsoft.directory/servicePrincipals/allProperties/read
) – I found that without this the user couldn’t view any app registration or service principal, he/ she could only search for them.
I might in future also add microsoft.directory/applications/permissions/update
(or rather microsoft.directory/applications.myOrganization/permissions/update
) so the admins can create permissions for other apps. With the current permissions above a Level 1 or Level 2 admin can only add permissions to the apps they own/ create.
With the above permissions any Level 2 or Level 1 admin cannot create secrets and that is intentional. Again, I don’t want them to be able to add secret to an app registration that possibly has a lot of permissions and that way do malicious things. If you want to allow that though then microsoft.directory/applications/credentials/update
is what you must add. All this and more can be found at the permissions link above.
Examples
A few examples of this in action.
First up, a warning on something I encountered and wasted time on. Initially I was creating the Level 2 custom role above with only the application permissions. This was because I was figuring things out and wanted to keep things minimal; and also because I thought maybe I could add the Level 2 admins to the Level 1 admins role to grant them the delegated permissions consent. So when I logged in with an admin who was in the Level 2 custom role (and it had only the application permissions) and I created an app registration with an application permission that was in the allowed list, the admin consent button was grayed out:
I wasted a fair bit of time trying to understand if I did something wrong or whether my understanding of everything I wrote above was mistaken. Then I realized I was right all along, but because app registrations have a delegated permission added by default (the User.Read
above) the admin consent was grayed out because my Level 2 user didn’t have rights to admin cosent delegated permissions. Doh! So I removed the delegated permission and refreshed the page a couple of times and then the button was no longer grayed out and I could do an admin consent:
Keep this in mind and mention to the Level 1 and Level 2 admins. If a Level 1 admin adds both delegated and application permissions for instance, the admin consent button will be grayed out even though they have rights to consent to the delegated permissions. Their workflow shoud be to first add the delegated permissions, do the consent, then add the application permissions so someone else can do the consent for those (or get someone else to both add and consent the application permissions).
Back to examples, if I were to add a restricted API permission:
Then the admin consent is still visible but clicking it throws an error:
Excellent! This is just what we want.
One more thing
I wasn’t aware of this until I stumbled upon it a few weeks ago. You can add any identity to a custom role. That is to say if you have an app registration service principal or an Azure Automation identity or a Function App identity you can add these to the custom role and they too would have the rights associated with that role. This is useful if you want to create some sort of automation to create app registrations and do admin consents, but want this to be restricted – simply add it to one of the custom roles above.