Une inscription d'application reçoit un secret, le projet part en production, et l'identifiant reste dans l'annuaire avec le droit de lire des boîtes aux lettres, sans personne pour en répondre. Cet article construit un balayage planifié en Microsoft Graph PowerShell : inventaire, propriétaires, identifiants expirants ou inutilisés, rotation avec chevauchement, puis désactivation avant suppression. Vous saurez aussi ce que l'App Instance Lock et les politiques de gestion d'applications changent à vos scripts.
Application object et service principal : l'erreur de cible qui coûte cher
Un service principal est l'identité locale que votre tenant émet pour qu'une application y soit autorisée ; son modèle global est l'objet application. La documentation Microsoft Learn résume la relation ainsi : l'objet application est la représentation globale pour tous les tenants, le service principal la représentation locale pour un tenant donné.
Une application multi-tenant possède un seul objet application dans son tenant d'origine et un service principal distinct dans chaque tenant qui a consenti. Un secret créé sur l'objet application authentifie donc ailleurs que là où vous croyez avoir nettoyé. Nettoyer le mauvais objet laisse l'identifiant actif.
Trois conséquences pratiques pour le script :
- les secrets et certificats utilisés en authentification applicative se gèrent sur l'objet application (son
Id, pas celui du service principal) ; - l'interrupteur de confinement,
AccountEnabled, est sur le service principal ; - Microsoft recommande de faire fonctionner un service avec l'identifiant défini sur l'objet application plutôt que sur le service principal (documentation Microsoft Learn sur la recommandation
servicePrincipalKeyExpiry).
Les éditeurs publient des ratios de machines par humain (144 pour 1 chez Entro Security dans son rapport H1 2025, 109 pour 1 dans le rapport 2026 de CyberArk). Ce sont leurs propres télémétries : mesurez votre tenant avant de dimensionner la démarche. Même prudence pour le chiffre selon lequel 3 % seulement des permissions accordées aux workload identities sont utilisées sur un an (rapport 2024 de Microsoft sur la sécurité multicloud). Le comportement décrit est réel : un script qui casse faute de permission ouvre un ticket dans l'heure, une permission accordée « au cas où » n'en ouvre jamais.
Ce qui change depuis 2026 pour vos scripts
Deux évolutions modifient la façon d'écrire un balayage.
Microsoft avait demandé d'agir avant cette date : Entra ID bloque l'authentification des applications multi-tenant non Microsoft sans service principal dans le tenant d'authentification. L'inventaire de ce guide sert aussi de liste des applications impactées.
Selon Our Cloud Network, Microsoft active par défaut l'App Instance Lock sur toutes les nouvelles applications, avec un déploiement annoncé du début à la fin de juin 2026. Les applications existantes ne sont pas touchées.
Une modification bloquée par le verrou renvoie une erreur 400 Bad Request. Un script qui ajoute un identifiant à un service principal d'une application multi-tenant peut donc échouer.
Comme le détaille Michev, activer l'App Instance Property Lock ne supprime pas les identifiants locaux déjà ajoutés à un service principal, et ils continuent de fonctionner. Un nettoyage reste nécessaire. Il signale aussi une propriété bêta, enforcementScope (par exemple foreignTenantOnly), qui limite le verrou aux tenants étrangers : dans le tenant d'origine, les utilisateurs habilités peuvent toujours modifier les identifiants.
Les app management policies (defaultAppManagementPolicy ou politiques personnalisées) bloquent l'ajout de secrets ou limitent la durée de vie des secrets et des certificats, sur les applications comme sur les service principals. Une politique personnalisée assignée à une application remplace la politique par défaut, et des exemptions existent par attribut de sécurité personnalisé (Microsoft Learn). Elles peuvent faire échouer une rotation si le nouvel identifiant dépasse la durée autorisée : votre script doit donc lire la politique avant de choisir un EndDateTime.
Prérequis et permissions Graph minimales
Faites tourner le balayage sous une identité d'automatisation dédiée, pas sous un compte administrateur. Commencez en lecture seule, ajoutez les scopes d'écriture au moment de la rotation.
Pour un audit en lecture seule, les trois premières lignes de lecture plus AuditLog.Read.All et DirectoryRecommendations.Read.All suffisent. L'API des recommandations est servie par le endpoint bêta de Graph (/beta/directory/recommendations) ; les rôles Reports Reader, Security Reader et Global Reader donnent un accès en lecture seule (Microsoft Learn).
- PowerShell 7.4 ou ultérieur avec le module Microsoft.Graph (testé avec la version 2.19.0) ; le module Az.KeyVault pour la rotation.
- Aucun recours aux modules retirés AzureAD et MSOnline.
- Une identité machine pour l'exécution planifiée : identité managée système d'un compte Azure Automation, ou service principal avec certificat.
- Un coffre Azure Key Vault et un accès en écriture pour l'identité qui effectue la rotation.
- Une licence Microsoft Entra ID P1 ou P2 pour télécharger les journaux de connexion via l'API Graph.
La recommandation staleAppCreds (« Remove unused credentials from apps ») est toujours en préversion à la date de rédaction. Son exemple de réponse indique requiredLicenses = microsoftEntraWorkloadId, donc une licence Workload ID Premium (Microsoft Learn). Les alertes d'App Governance (Defender for Cloud Apps) sont aussi réservées aux clients Workload ID Premium selon Thomas Naunheim, mais son article date de 2023 : revérifiez les licences. Sans la licence, notez la capacité comme indisponible dans le rapport plutôt que d'afficher zéro.
Mise en œuvre
Le pipeline écrit un artefact par étape : une défaillance tardive ne détruit jamais les preuves précédentes. Les scripts ci-dessous partagent les variables $Apps, $Sps et $ReportPath, et s'exécutent dans la même session.
Étape 1 : se connecter avec le plus petit jeu de scopes
Confirmez l'identité et le tenant réellement portés par la session : une session héritée d'une ancienne connexion peut pointer vers un autre annuaire et fausser chaque comptage sans erreur.
1Install-Module Microsoft.Graph -Scope CurrentUser -MinimumVersion 2.19.02Connect-MgGraph -Scopes 'Application.Read.All','AuditLog.Read.All','DirectoryRecommendations.Read.All'3Get-MgContext | Select-Object Account, TenantId, Scopes1# Dans le runbook Azure Automation : aucun secret stocké2Connect-MgGraph -Identity3Get-MgContext | Select-Object Account, TenantId, ScopesSortie attendue : le compte, l'identifiant du tenant et la liste des scopes accordés. Si un scope manque, le premier appel concerné renvoie un 403.
Étape 2 : inventorier et repérer les objets sans propriétaire
Collectez les deux collections avec les propriétés que les étapes suivantes lisent, puis écrivez-les sur disque. L'opérateur $count dans un filtre est une requête avancée : elle exige -ConsistencyLevel eventual et -CountVariable. Les apostrophes simples évitent que PowerShell n'interprète $count avant l'envoi.
1$ReportPath = Join-Path $PWD 'sp-sweep'2New-Item -ItemType Directory -Path $ReportPath -Force | Out-Null3 4# Les deux collections, avec les propriétés de gouvernance5$Apps = Get-MgApplication -All -Property Id,AppId,DisplayName,CreatedDateTime,PasswordCredentials,KeyCredentials6$Sps = Get-MgServicePrincipal -All -Property Id,AppId,DisplayName,AccountEnabled,ServicePrincipalType,ApplicationTemplateId,PasswordCredentials,KeyCredentials7 8$Apps | Select-Object DisplayName, AppId, Id, CreatedDateTime |9 Export-Csv -Path (Join-Path $ReportPath 'apps.csv') -NoTypeInformation10$Sps | Select-Object DisplayName, AppId, Id, ServicePrincipalType, AccountEnabled, ApplicationTemplateId |11 Export-Csv -Path (Join-Path $ReportPath 'serviceprincipals.csv') -NoTypeInformation12 13# Objets sans propriétaire (requête avancée)14[array]$OwnerlessApps = Get-MgApplication -All -Property DisplayName,Id,AppId,CreatedDateTime `15 -Filter 'owners/$count eq 0' -CountVariable OwnerCount -ConsistencyLevel eventual16[array]$OwnerlessSps = Get-MgServicePrincipal -All -Property DisplayName,Id,AppId `17 -Filter 'owners/$count eq 0' -CountVariable SpOwnerCount -ConsistencyLevel eventual18 19$OwnerlessApps | Select-Object DisplayName, AppId, Id |20 Export-Csv -Path (Join-Path $ReportPath 'ownerless-apps.csv') -NoTypeInformation21$OwnerlessSps | Select-Object DisplayName, AppId, Id |22 Export-Csv -Path (Join-Path $ReportPath 'ownerless-sps.csv') -NoTypeInformationApplicationTemplateId identifie un service principal créé depuis un modèle d'application ; il vaut null sinon. Retirez ces lignes de la file de remédiation et adressez-les à l'éditeur. Deux propriétaires par application constituent le minimum qui survit à une démission ; l'un des deux devrait être un groupe. Si la requête avancée expire sur un grand tenant, repliez-vous sur une boucle Get-MgServicePrincipalOwner par objet, planifiée de nuit : elle émet une requête Graph par service principal.
Pour contrôler les privilèges détenus, pas seulement demandés, les affectations de rôles d'application (Get-MgServicePrincipalAppRoleAssignment) restent la bonne source : elles incluent les octrois qui n'ont jamais traversé un écran de consentement.
Étape 3 : détecter la dormance dans les journaux de connexion
Les connexions de service principals forment une classe de journaux distincte. La ressource signIn en v1.0 ne porte aucun champ de service principal : lisez donc le rapport servicePrincipalSignInActivities, exposé en bêta.
1$Since = (Get-Date).AddDays(-90)2 3# Parcours paginé du rapport d'activité4$Rows = @()5$Uri = 'https://graph.microsoft.com/beta/reports/servicePrincipalSignInActivities'6while ($Uri) {7 $Page = Invoke-MgGraphRequest -Method GET -Uri $Uri8 $Rows += $Page.value9 $Uri = $Page.'@odata.nextLink'10}11 12# Indexation par appId : on garde la date la plus récente des quatre flux d'activité13$LastSeenByAppId = @{}14foreach ($Row in $Rows) {15 $Timestamps = @(16 $Row.lastSignInActivity.lastSignInDateTime17 $Row.lastSignInActivity.lastNonInteractiveSignInDateTime18 $Row.lastSignInActivity.lastSuccessfulSignInDateTime19 $Row.applicationAuthenticationClientSignInActivity.lastSignInDateTime20 $Row.applicationAuthenticationClientSignInActivity.lastNonInteractiveSignInDateTime21 $Row.applicationAuthenticationClientSignInActivity.lastSuccessfulSignInDateTime22 $Row.delegatedClientSignInActivity.lastSignInDateTime23 $Row.delegatedClientSignInActivity.lastNonInteractiveSignInDateTime24 $Row.delegatedClientSignInActivity.lastSuccessfulSignInDateTime25 ) | Where-Object { $_ } | ForEach-Object { [datetime]$_ }26 $LastSeenByAppId[$Row.appId] = ($Timestamps | Sort-Object -Descending | Select-Object -First 1)27}28 29$DormantSps = $Sps | Where-Object {30 $_.ServicePrincipalType -eq 'Application' -and31 (-not $LastSeenByAppId.ContainsKey($_.AppId) -or $LastSeenByAppId[$_.AppId] -lt $Since)32}33$DormantSps | Select-Object DisplayName, AppId, Id |34 Export-Csv -Path (Join-Path $ReportPath 'dormant-principals.csv') -NoTypeInformationCharger tout le rapport en mémoire convient à un petit tenant. À grande échelle, envoyez les journaux de connexion des service principals vers un espace de travail Log Analytics : sinon, votre fenêtre de dormance se réduit à la rétention par défaut.
Étape 4 : séparer identifiants expirants et identifiants inutilisés
Une date d'expiration est un fait de calendrier lu sur l'objet ; un identifiant qui n'est plus utilisé est un fait de télémétrie que seul le moteur de recommandations fournit. Les deux appellent des actions différentes.
1$Threshold = (Get-Date).AddDays(60)2$Now = Get-Date3 4$Expiring = foreach ($App in $Apps) {5 foreach ($Cred in (@($App.PasswordCredentials) + @($App.KeyCredentials))) {6 $Bucket = $null7 # Test du null en premier : $null -le [datetime] vaut $true en PowerShell8 if ($null -eq $Cred.EndDateTime) { $Bucket = 'NeverExpires' }9 elseif ($Cred.EndDateTime -lt $Now) { $Bucket = 'Expired' }10 elseif ($Cred.EndDateTime -le $Threshold) { $Bucket = 'Expiring' }11 12 if ($Bucket) {13 $Days = $null14 if ($Cred.EndDateTime) { $Days = [int]($Cred.EndDateTime - $Now).TotalDays }15 [PSCustomObject]@{16 ObjectType = 'Application'17 DisplayName = $App.DisplayName18 ObjectId = $App.Id19 CredentialName = $Cred.DisplayName20 KeyId = $Cred.KeyId21 EndDateTime = $Cred.EndDateTime22 Bucket = $Bucket23 DaysRemaining = $Days24 }25 }26 }27}28$Expiring | Export-Csv -Path (Join-Path $ReportPath 'expiring-credentials.csv') -NoTypeInformationLe seau NeverExpires est le plus sévère. Il faut le séparer d'Expiring, sinon un identifiant sans fin de vie reçoit un nombre de jours absurde.
Pour la télémétrie d'usage, interrogez le endpoint bêta des recommandations et exportez les ressources impactées de staleAppCreds.
1$Recos = (Invoke-MgGraphRequest -Method GET -Uri 'https://graph.microsoft.com/beta/directory/recommendations').value2$Stale = @($Recos | Where-Object { $_.recommendationType -eq 'staleAppCreds' })3 4if ($Stale.Count -eq 0) {5 Write-Warning 'Recommandation staleAppCreds absente : licence ou disponibilité à vérifier. Dormance des identifiants : indisponible.'6}7foreach ($Reco in $Stale) {8 $ImpactedUri = 'https://graph.microsoft.com/beta/directory/recommendations/{0}/impactedResources' -f $Reco.id9 $Impacted = Invoke-MgGraphRequest -Method GET -Uri $ImpactedUri10 $Impacted | ConvertTo-Json -Depth 6 |11 Set-Content -Path (Join-Path $ReportPath 'stale-credentials.json')12}Deux règles de lecture. Un identifiant est jugé inutilisé s'il n'a pas servi depuis 30 jours, et les identifiants déjà expirés n'apparaissent pas dans la liste des ressources impactées. Un secret expiré que rien n'utilise relève du backlog de nettoyage ; un identifiant vivant et inutilisé relève d'une décision de révocation, à approuver par son propriétaire. La recommandation servicePrincipalKeyExpiry, aussi en préversion, se déclenche pour les identifiants de service principal qui expirent dans 30 jours.
Étape 5 : faire tourner un secret sans coupure
La rotation est d'abord un problème de séquence. Un échange dans le mauvais ordre casse la charge de travail et rend l'équipe méfiante envers l'hygiène des identifiants.
Créez le nouveau secret sur l'objet application, en utilisant son Id issu de l'inventaire. Le mauvais identifiant renvoie un 404 qui ressemble à un défaut de permission.
La documentation Microsoft Learn de addPassword est explicite : le mot de passe ne peut plus être récupéré ensuite. Le texte du secret n'existe que dans cette réponse.
Mettez à jour l'application cliente, exécutez sa charge une fois et contrôlez le KeyId utilisé dans le journal de connexion.
Les deux secrets sont valides pendant le chevauchement : gardez-le court, sinon vous recréez le sprawl que la rotation devait résoudre.
Le script suivant couvre les étapes 1, 2 et 4 avec un mode simulation. Les noms de secrets Key Vault n'acceptent que lettres, chiffres et traits d'union, d'où le nettoyage du nom.
1# Prérequis : Az.KeyVault, session Az connectée (Connect-AzAccount), scope Application.ReadWrite.All2$DryRun = $true3$AppObjectId = '<application-object-id>'4$VaultName = '<key-vault-name>'5 6$App = Get-MgApplication -ApplicationId $AppObjectId7$PreviousKeyId = $App.PasswordCredentials |8 Sort-Object EndDateTime | Select-Object -First 1 -ExpandProperty KeyId9$SecretName = (($App.DisplayName -replace '[^0-9a-zA-Z-]', '-') -replace '-{2,}', '-').Trim('-')10 11if ($DryRun) {12 Write-Host ('Simulation : un secret serait ajouté à {0}, puis {1} serait retiré.' -f $App.DisplayName, $PreviousKeyId)13}14else {15 # Durée : à aligner sur la politique de gestion d'applications en vigueur16 $NewSecret = Add-MgApplicationPassword -ApplicationId $AppObjectId -PasswordCredential @{17 DisplayName = ('Rotated ' + (Get-Date -Format 'yyyyMMdd'))18 EndDateTime = (Get-Date).AddMonths(6)19 }20 Set-AzKeyVaultSecret -VaultName $VaultName -Name ($SecretName + '-client-secret') `21 -SecretValue (ConvertTo-SecureString $NewSecret.SecretText -AsPlainText -Force) | Out-Null22 Write-Host ('Nouveau KeyId {0}, expiration {1}' -f $NewSecret.KeyId, $NewSecret.EndDateTime)23 24 # À exécuter seulement après validation du consommateur :25 # Remove-MgApplicationPassword -ApplicationId $AppObjectId -KeyId $PreviousKeyId26}Le retrait reste commenté : il doit suivre la vérification du journal de connexion, pas le même passage de script. Même séquence pour un certificat, car un remplacement en une seule étape comporte le même risque de coupure. Microsoft recommande d'utiliser une identité managée comme identifiant chaque fois que possible, à défaut des certificats plutôt que des mots de passe, de renouveler souvent, de ne pas partager un identifiant entre applications et d'en limiter le nombre par application (Microsoft Learn).
Étape 6 : désactiver avant de supprimer
La suppression est la seule étape irréversible : elle suit une période de désactivation qui préserve la capacité d'enquête.
1$DryRun = $true2$SpId = '<service-principal-object-id>'3 4if ($DryRun) {5 Write-Host ('Simulation : le service principal {0} serait désactivé.' -f $SpId)6}7else {8 Update-MgServicePrincipal -ServicePrincipalId $SpId -AccountEnabled:$false9}10 11# Relecture pour confirmer12Get-MgServicePrincipal -ServicePrincipalId $SpId | Select-Object DisplayName, AccountEnabledSortie attendue après passage hors simulation : AccountEnabled à False. Laissez l'identité désactivée pendant un cycle de reporting complet : un service principal dont personne ne se plaint après 30 jours de blocage est un service principal dont personne n'a besoin.
Remove-MgServicePrincipal et les cmdlets de retrait d'identifiants modifient le tenant. Entra ID restaure une inscription d'application supprimée avec son service principal pendant la fenêtre de suppression réversible de 30 jours ; la suppression définitive depuis les éléments supprimés est le geste que rien n'annule. Testez d'abord sur une application de test.
Étape 7 : planifier sans stocker de secret
Une identité managée système sur un compte Azure Automation donne au runbook un service principal sans secret client. Accordez-lui uniquement les rôles d'application de lecture ; ajoutez Application.ReadWrite.All seulement le jour où la rotation est automatisée.
1# Prérequis : compte habilité à attribuer des rôles d'application2Connect-MgGraph -Scopes 'AppRoleAssignment.ReadWrite.All','Application.Read.All'3 4$MiPrincipalId = '<automation-account-managed-identity-principal-id>'5$GraphSp = Get-MgServicePrincipal -Filter 'appId eq ''00000003-0000-0000-c000-000000000000'''6 7foreach ($Permission in 'Application.Read.All','AuditLog.Read.All','DirectoryRecommendations.Read.All') {8 $RoleId = ($GraphSp.AppRoles |9 Where-Object { $_.Value -eq $Permission -and $_.AllowedMemberTypes -contains 'Application' }).Id10 11 New-MgServicePrincipalAppRoleAssignedTo -ServicePrincipalId $GraphSp.Id -BodyParameter @{12 PrincipalId = $MiPrincipalId13 ResourceId = $GraphSp.Id14 AppRoleId = $RoleId15 }16}Importez ensuite Microsoft.Graph.Authentication et les sous-modules appelés depuis la galerie de modules Automation, en phase avec l'environnement d'exécution du runbook, puis attachez une planification : inventaire de nuit, notification hebdomadaire.
Pour l'alerte, le journal d'audit nomme les événements à surveiller : ajout ou retrait d'identifiants de service principal (le signal le plus fort en cas d'entrée inattendue), Add app role assignment to service principal, Add owner to application et Remove owner from service principal. Routez les paramètres de diagnostic Entra vers Log Analytics et interrogez la table AuditLogs sur ActivityDisplayName et InitiatedBy.
Dépannage : six symptômes fréquents
Empêcher le retour du sprawl
Le balayage nettoie, mais ne prévient rien. Placez la règle des deux propriétaires dans le processus de changement qui crée l'inscription, et rendez le rapport nocturne visible à l'équipe propriétaire de la charge. Imposez ensuite par politique la durée de vie maximale des secrets et des certificats, ou le blocage des secrets, avec les app management policies.
Pour approfondir la gouvernance des identités applicatives, voyez notre guide pour sécuriser les identités applicatives avec Microsoft Entra, et notre analyse de la limitation des privilèges Application Administrator par application. Pour contrôler qui peut interroger Graph avec le SDK, consultez le contrôle d'accès au SDK Graph PowerShell.
Questions fréquentes
Faut-il une licence payante pour ce pipeline ?
Inventaire, propriétaires, rotation et désactivation reposent sur des permissions Microsoft Graph dans tout tenant Entra ID. Le téléchargement des journaux de connexion via l'API Graph demande une licence Entra ID P1 ou P2. La détection d'identifiants inutilisés (staleAppCreds) indique une licence Workload ID Premium dans l'exemple de réponse de Microsoft Learn.
Quelle différence entre un identifiant expirant et un identifiant inutilisé ?
Un identifiant expirant a un EndDateTime dans votre fenêtre d'alerte : c'est une tâche de maintenance avec une échéance. Un identifiant inutilisé n'a pas servi depuis 30 jours selon le moteur de recommandations : c'est une décision de révocation que le propriétaire doit approuver.
Peut-on supprimer tous les service principals sans propriétaire ?
Non, pas sans vérifier l'activité de connexion : objets sans propriétaire et identités mortes sont des populations qui ne se recouvrent que partiellement. Désactivez l'objet, attendez un cycle et vérifiez qu'aucune charge ne casse. Une tâche nocturne non documentée ressemble exactement à un objet abandonné jusqu'à ce qu'on la coupe.
Un certificat vaut-il mieux qu'un secret ?
Oui, quand la charge de travail peut conserver une clé privée en sécurité : on supprime la chaîne de caractères qui finit dans un fichier de configuration ou une conversation. Les certificats vivent dans la collection keyCredentials, séparée des passwordCredentials qui portent les secrets. Une identité managée reste préférable quand elle est possible.
L'App Instance Lock protège-t-il mes applications existantes ?
Non : son activation par défaut depuis juin 2026 ne concerne que les nouvelles applications. Et même activé, le verrou n'efface pas les identifiants locaux déjà ajoutés à un service principal, qui continuent de fonctionner. Le nettoyage de l'existant reste à votre charge.
Par où commencer
- Lancer l'inventaire en lecture seule (étape 2) et comparer
apps.csvetserviceprincipals.csvd'un balayage à l'autre. - Traiter d'abord les identifiants
NeverExpires, puis ceux du seauExpiringà 7 et 30 jours. - Vérifier la politique de gestion d'applications avant d'automatiser la rotation, pour éviter une erreur 400.
- Désactiver, pas supprimer, les identités dormantes et sans propriétaire ; supprimer après un cycle complet sans plainte.
Si votre organisation dispose de Workload ID Premium, branchez la recommandation staleAppCreds dès maintenant, tout en gardant en tête qu'elle est en préversion. Sinon, appuyez-vous sur la dormance calculée depuis les journaux de connexion et enregistrez la détection d'identifiants inutilisés comme indisponible. Deux éléments restent manuels : les applications issues d'un modèle, à signaler à l'éditeur, et un propriétaire qui refuse un renouvellement, qui demande un arbitrage du responsable de la charge.
Pour aller plus loin
- Recommandation Microsoft Entra : supprimer les identifiants inutilisés (préversion) : définition de « inutilisé », rôles, permissions, appels Graph et licence.
- Configurer des restrictions sur la configuration des applications : imposer une durée de vie maximale aux secrets et certificats, ou bloquer les secrets.
- App instance property lock gets a scoping mechanism (Michev) : limites du verrou et nettoyage des identifiants déjà ajoutés.
- Microsoft Entra Workload ID : gestion du cycle de vie et supervision (Thomas Naunheim) : recommandations, rapports d'activité et access reviews des workload identities.
- Bonnes pratiques de sécurité pour les propriétés d'application dans Microsoft Entra ID : choix entre identité managée, certificat et secret.
