Management Policies for Apps and Service Principals at Tenant and App Level
Many Microsoft 365 tenants have experienced a proliferation of apps. Tenant processes registered apps to authenticate with Entra ID before running scripts. Third-party solutions introduce enterprise apps into the mix. It’s easy for a tenant to accrue thousands of apps, each with a different purpose, permissions, and authentication method. Exerting control over apps is essential for tenant security. Attackers have been known to use apps as a vector to gain privileged access to tenants before exfiltrating data or destroying information.
Credentials are an essential part of the authentication process. Apps can use multiple types of credentials to authenticate. The most common methods are app secrets and X.509 certificates. Unlike user accounts, apps support multiple credentials of the same type. For instance, an app can be configured with several app secrets, any of which can be used to authenticate.
App Credentials and Lifetimes
Credentials created in the Entra admin center have limited lifetimes (if the 730-day maximum for an app secret is considered limited), but credentials created programmatically can have much longer lifetimes. A common attacker tactic is to plant an app in a compromised tenant with a long-lasting credential. The attacker might then wait for months or even years before moving from passive to active intervention in the tenant.
For example, this code creates a payload to instruct Entra ID to create a new app secret for an application with an extended lifetime. When run, the app secret is created and can be used to authenticate with the Graph by running the Connect-MgGraph cmdlet with the ClientSecretCredential parameter. The parameter contains a PowerShell credentials object built from the application identifier and a secure string created from the app secret.
$App = Get-MgApplication -Filter "DisplayName eq 'Test2'" $AppParams = @{} $Password = @{} $Password.Add("DisplayName", "My App Password") $Password.Add("enddateTime", "2099-01-01:T00:00:00Z") $AppParams.Add("passwordCredential", $Password) $AppPasswordDetails = Add-MgApplicationPassword -ApplicationId $App.Id -BodyParameter $AppParams $AppPasswordDetails.SecretText AuO8Q~RLWmZlpzmzeMxMGGJx-blVmi9~2w35EaAc $ClientSecretString = ConvertTo-SecureString -String $AppPasswordDetails.SecretText -AsPlainText -Force $ClientSecret = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $App.AppId, $ClientSecretString Connect-MgGraph -TenantId o365maestro.onmicrosoft.com -ClientSecretCredential $ClientSecret
I don’t recommend using app secrets for anything other than initial testing. Once an app moves to the later stages of testing in preparation for production, its authentication should be switched to an X.509 certificate. Certificates can also be added to apps programmatically using the Update-MgApplication cmdlet. Certificates added in this manner can have extended lifetimes.
App Management Policies
It’s obvious it is common sense to keep an eye on app credentials either by reviewing this data periodically by running a bespoke report or by using Entra Identity Score. Knowing what apps exist in the tenant and how they interact with Entra ID is a fundamental step to taking control over what otherwise could be a mess. The next step is to impose controls over how apps authenticate, and that’s where App management policies come in.
App management policies aren’t available in the Entra admin center and must be configured through the Graph, including with Microsoft Graph PowerShell SDK cmdlets. These policies used to require an Entra ID Workload ID license, but are now covered by Entra ID free. Microsoft appears to have changed the licensing requirement in late 2024, perhaps as a response to the burgeoning number of apps in customer tenants and the need for tenants to exert more control over those apps.
App management policies come in two forms:
- A tenant-wide default policy that is inactive by default. Policy settings control whether app secrets are allowed or not, and if allowed, for which apps (determined by their creation date) and for how long app secrets can last. Separate settings govern registered apps and service principals. This discussion covers registered apps, but the same principles apply to management of service principals.
- Custom app management policies that can be created to permit specific apps to use settings different than the tenant-wide policy. If an app comes within the scope of a custom app management policy, Entra ID applies the settings in the app management policy.
Settings can be enabled or disabled individually for granular control over the restrictions applied to how apps use app secrets and certificates.
Blocking App Secrets for Registered Apps
As an example of app management in action, let’s update the default policy to enable the policy and to restrict the creation of new app secrets (passwords) for any app created since the beginning of 2025. Existing app secrets will continue to work, but new app secrets cannot be created for any app that comes within the scope of the newly-enabled default policy.
Two restrictions are created here for Entra ID to apply to both registered apps and enterprise apps (service principals). The first restricts new app secrets, the second stops the creation of custom app secrets by applications. Only the first restriction is necessary, but I’ve included the second to show how to add multiple restrictions to the policy. Before running any of these commands, make sure that the signed-in account has consent for the delegated Policy.ReadWrite.ApplicationConfiguration permission and holds at least the Security administrator role.
$PasswordCredentials1 = @{} $PasswordCredentials1.Add("restrictForAppsCreatedAfterDateTime", [System.DateTime]::Parse("2025-01-01T00:00:00Z")) $PasswordCredentials1.Add("restrictionType", "passwordAddition") $PasswordCredentials1.Add("maxLifetime", $null) $PasswordCredentials1.Add(“state”, “enabled”) $PasswordCredentials2 = @{} $PasswordCredentials2.Add("restrictionType", "customPasswordAddition") $PasswordCredentials2.Add("maxLifetime", $null) $PasswordCredentials2.Add("restrictForAppsCreatedAfterDateTime", [System.DateTime]::Parse("2025-01-01T00:00:00Z")) $PasswordCredentials2.Add(“state”, “enabled”) [array]$PasswordCredentials = $PasswordCredentials1, $PasswordCredentials2 $ApplicationCredentials = @{} $ApplicationCredentials.Add("passwordCredentials", $PasswordCredentials) $ApplicationPolicyParameters = @{} $ApplicationPolicyParameters.Add("isEnabled", $True) $ApplicationPolicyParameters.Add("applicationRestrictions", $ApplicationCredentials) $ApplicationPolicyParameters.Add("ServicePrincipalRestrictions", $ApplicationCredentials)
After creating the hash tables and the array, the input payload for the policy looks like this:
$ApplicationPolicyParameters Name Value ---- ----- ServicePrincipalRestrictions {[passwordCredentials, System.Object[]]} isEnabled True applicationRestrictions {[passwordCredentials, System.Object[]]}
After assembling the payload, the default app management policy is updated by running the Update-MgPolicyDefaultApplicationPolicy cmdlet:
Update-MgPolicyDefaultAppManagementPolicy -BodyParameter $ApplicationPolicyParameters
To see the restrictions for registered apps enforced by the default policy, use the Get-MgPolicyDefaultApplicationManagementPolicy cmdlet to fetch the settings and examine the details:
$Policy = Get-MgPolicyDefaultAppManagementPolicy $Policy.applicationRestrictions.PasswordCredentials RestrictForAppsCreatedAfterDateTime RestrictionType State ----------------------------------- --------------- ----- 01/01/2025 00:00:00 passwordAddition enabled 01/01/2025 00:00:00 customPasswordAddition enabled
The new policy is effective immediately. To see the effect, go to the Entra admin center and create a new registered app. Now, try to create an app secret. You should see a banner saying “Client secrets are blocked by a tenant-wide policy. Contact your tenant administrator for more information.” (Figure 1).

Any attempt to add an app secret programmatically will also fail with a CredentialTypeNotAllowedAsPerAppPolicy error:
Add-MgApplicationPassword_Add: Line | 6 | $AppPasswordDetails = Add-MgApplicationPassword -ApplicationId $App. … | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | Credential type not allowed as per assigned policy 'efb5d603-232f-478d-a58d-364b4787ab86'. Status: 400 (BadRequest) ErrorCode: CredentialTypeNotAllowedAsPerAppPolicy
Because the policy only applies to apps created after January 1, 2025, if you select an app created earlier, you can create a new app secret.
Creating a Custom App Management Policy
Entra ID includes the facility to add custom app management policies. The settings of a custom app management policy override the default tenant app management policy. To illustrate the usefulness of custom app management policies, I created a policy to allow app secrets for a maximum of 90 days. When doing so, I encountered several problems.
First, a UI bug in the Entra admin center meant that any settings applied by a custom app management policy were not respected. Microsoft updated the admin center in early April; 2025. Next, the examples in the documentation included settings that didn’t work. Finally, the upgrade to V2.27 of the Microsoft Graph PowerShell SDK introduced a bug that stopped the New–MgPolicyAppManagementPolicy cmdlet working (bug report here). In any case, here’s how I created a custom app management policy.
Because the cmdlet didn’t work, I had to post a Graph request to the appManagementPolicy endpoint. The payload contains two settings. The first removes the disablement for app secrets. The second limits app secrets to 90 days:
$CustomAppPolicyParameters = ’{ "displayName": "Allow Apps to Use App Secrets", "description": "This policy allows apps within its scope to create new app secrets. It should be assigned to new apps being tested.", "isEnabled": true, "restrictions": { "passwordCredentials": [ { "restrictionType": "passwordAddition", "state": "disabled", "maxLifetime": null }, { "restrictionType": "passwordLifetime", "state": "enabled", "maxLifetime": "P90D" } ] } }’
Next, we post the request:
$Uri = "https://graph.microsoft.com/v1.0/policies/appManagementPolicies/" Invoke-MgGraphRequest -Uri $Uri -Method POST -Body $CustomAppPolicyParameters
The identifier of the new policy is needed to assign the policy to apps. Unhappily, the Get-MgPolicyAppManagementPolicy cmdlet doesn’t support server-side filtering. This command doesn’t work:
$CustomAppPolicy = Get-MgPolicyAppManagementPolicy -Filter "DisplayName eq 'Allow Apps to Use App Secrets'"
Client-side filtering does work:
$CustomAppPolicy = Get-MgPolicyAppManagementPolicy | Where-Object {$_.DisplayName -eq 'Allow Apps to Use App Secrets'}
Bugs like this are an annoying downside of Microsoft generating the Graph PowerShell SDK cmdlets from Graph metadata. If a flaw exists in the metadata, it leaks through into the cmdlet.
To check the settings of the new policy, use the Get-MgPolicyAppManagementPolicy cmdlet. Here we can see that the password addition restriction is disabled (to make it possible to add password secrets to apps) but that password lifetimes are restricted to 90 days.
$Data = Get-MgPolicyAppManagementPolicy -AppManagementPolicyId $CustomAppPolicy.id $Data.Restrictions.Passwordcredentials | fl MaxLifetime : RestrictForAppsCreatedAfterDateTime : 01/01/0001 00:00:00 RestrictionType : passwordAddition State : disabled AdditionalProperties : {} MaxLifetime : 90.00:00:00 RestrictForAppsCreatedAfterDateTime : 01/01/0001 00:00:00 RestrictionType : passwordLifetime State : enabled AdditionalProperties : {}
Applying and Managing a Custom App Management Policy
Only a single app management policy can apply to an app or service principal. If you want to assign a different app management policy to an app, you must remove the current assignment before assigning a new policy (or delete the existing custom application management policy).
To assign a custom app management policy to an app, create a hash table with the identifier for the policy to assign:
$Assignment = @{} $Assignment.Add("@odata.id", ("https://graph.microsoft.com/v1.0/policies/appManagementPolicies/{0}" -f $CustomAppPolicy.Id))
The object identifier of the app to receive the assignment is needed. It’s easily found with:
$AppId = (Get-MgApplication -Filter "DisplayName eq 'AppSecretTestApp'").Id
The New-MgApplicationAppManagementPolicyByRef cmdlet assigns the policy to the app. To check if the assignment is in place, use the Get-MgPolicyAppManagementPolicyApplyTo cmdlet, or simply try to create an app secret for the newly-assigned app (Figure 2). Note that the lifetime of the app secret is restricted to 90 days.
New-MgApplicationAppManagementPolicyByRef -ApplicationId $AppId -BodyParameter $Assignment Get-MgPolicyAppManagementPolicyApplyTo -AppManagementPolicyId $CustomAppPolicy.Id | Select-Object -ExpandProperty additionalProperties

The Remove-MgApplicationAppManagementPolicyAppManagementPolicyByRef cmdlet removes the assignment for a custom app management policy from an app:
Remove-MgApplicationAppManagementPolicyAppManagementPolicyByRef -ApplicationId ‘975fa0ad-4502-44f8-89a7-3b32f576ac3b’ -AppManagementPolicyId $CustomAppPolicy,Id
Removing the assignment of a custom app management policy does not remove any app secrets. The app can continue to use valid app secrets until they expire. However, they cannot create any new app secrets.
Finally, if you need to remove a custom app management policy, use the Remove-MgPolicyAppManagementPolicy cmdlet:
Remove-MgPolicyAppManagementPolicy -AppManagementPolicyId $Policy.Id
Tenant App Management is an Important Skill
Apps have been around for as long as Office 365. App management is not a new skill for Microsoft 365 tenant administrators to learn, but it has become increasingly important as the number and diversity of apps has grown within tenants. Making sure that apps use good credentials is an important step toward better tenant security. Eliminating app secrets is part of that journey, and now you know how to do it!