Wygaśnięcie sekretów App Registration w Entra ID: jak nie dać się zaskoczyć w produkcji
Sekrety klienta App Registration wygasają bez e-mail alertu — gdy termin minie, wszystkie integracje padają natychmiast. Pokazuję jak skryptem PowerShell monitorować wygasające poświadczenia w całym tenancie i jak bezpiecznie rotować sekrety bez okna awarii.

Wygaśnięcie sekretów App Registration w Entra ID: jak nie dać się zaskoczyć w produkcji
TL;DR: Sekrety klienta i certyfikaty App Registration w Entra ID wygasają bez domyślnego powiadomienia e-mail — gdy termin minie, wszystkie integracje przestają działać natychmiast. W artykule pokazuję jak skryptem PowerShell wyciągnąć raport wygasających poświadczeń z całego tenanta, jak bezpiecznie rotować sekrety bez okna awarii oraz jak wymusić politykę zarządzania poświadczeniami dla nowych aplikacji.
Problem
Scenariusz zna każdy administrator M365 o stażu powyżej roku: poniedziałkowy ranek, telefony od użytkowników że "coś nie działa", przeglądasz logi w Entra i widzisz błąd AADSTS7000215: Invalid client secret provided. Aplikacja działała przez miesiące bez problemu. Co się stało? Sekret klienta App Registration wygasł w niedzielę wieczór.
W środowiskach Microsoft 365 App Registration to mechanizm który pozwala aplikacjom i automatyzacjom uwierzytelniać się w Entra ID i uzyskiwać dostęp do Microsoft Graph, Exchange Online, SharePoint czy Teams. Każda taka rejestracja może mieć poświadczenia w dwóch formach: sekrety klienta (ciągi tekstowe, tzw. client secrets) lub certyfikaty (klucze asymetryczne X.509). Obie formy mają datę wygaśnięcia. Żadna domyślnie nie generuje e-maila alertowego.
W większych organizacjach liczba App Registrations rośnie szybciej niż ktokolwiek planował. Każda integracja z zewnętrznym systemem, każdy skrypt PowerShell używający Graph API, każdy konektor Power Automate, każda aplikacja Azure Functions czy Logic App — wszystkie mogą potrzebować własnej rejestracji z poświadczeniami. W tenancie z kilkuset użytkownikami łatwo skończyć z 40-80 rejestracjami, w środowiskach MSP zarządzających wieloma klientami — setkami.
Problem pogłębia historia samego mechanizmu. Przez lata administratorzy ustawiali sekrety na "Never Expire" — co technicznie oznaczało datę 31 grudnia 2299. Brzmiało bezpiecznie. Ale po zmianie polityki Microsoftu i przy braku żadnych alertów, dziesiątki organizacji odkryły problem dopiero gdy aplikacja przestała działać.
Dlaczego tak się dzieje
Historia ograniczeń czasowych sekretów
Microsoft przez lata zmieniał politykę dotyczącą czasów życia sekretów:
- Przed marcem 2021: opcja "Never Expire" dostępna w portalu. Technicznie ustawiała datę wygaśnięcia na rok 2299.
- Marzec/Kwiecień 2021: Microsoft usunął opcję "Never Expire" z portalu Azure. Nowe sekrety wymagały konkretnej daty.
- Luty 2022: oficjalne ogłoszenie że maksymalny czas życia nowych sekretów to 2 lata. Próba ustawienia dłuższego terminu przez portal powoduje błąd. Przez API wciąż można było obejść limit — ale Microsoft zapowiedział zamknięcie tej furtki.
Organizacje które migrowały z on-premise AD lub budowały integracje przed 2021 rokiem mogą mieć w tenantach sekrety z datą wygaśnięcia 12/31/2299. Wciąż działają, ale są to zapomniane, nigdy nierotowane poświadczenia z pełnym dostępem — dokładnie ten rodzaj ryzyka bezpieczeństwa który Microsoft chciał wyeliminować.
Brak natywnych alertów e-mail
Microsoft nie wysyła żadnego e-maila gdy sekret App Registration jest bliski wygaśnięcia. Jedyny wbudowany mechanizm to zakładka Recommendations w Entra ID portal (entra.microsoft.com > Overview > Recommendations) — ale:
- Ostrzega dopiero 30 dni przed terminem wygaśnięcia.
- Wymaga aktywnego sprawdzenia portalu — nie ma push notification.
- Rekomendacja
applicationCredentialExpirypokazuje tylko App Registrations, nie certyfikaty SAML w Enterprise Applications. - Wymaga licencji Microsoft Entra Workload ID dla pełnego API dostępu.
30 dni to mało czasu jeśli rotacja wymaga koordynacji z właścicielem aplikacji, przejścia przez Change Management i testów regresji. Organizacje które zarządzają tym profesjonalnie potrzebują 60-90 dni ostrzeżenia.
Rozwiązanie krok po kroku
Krok 1: Zainstaluj moduł Microsoft Graph PowerShell
Install-Module Microsoft.Graph -Scope CurrentUser -Force
# Zweryfikuj instalację
Get-InstalledModule Microsoft.Graph | Select-Object Name, Version
Krok 2: Raport wygasających sekretów — wszystkie App Registrations
Poniższy skrypt pobiera wszystkie App Registrations i wyświetla poświadczenia wygasające w ciągu określonej liczby dni. Kluczowy szczegół: parametr -Property jest obowiązkowy — bez passwordCredentials,keyCredentials cmdlet zwróci puste listy nawet gdy poświadczenia istnieją.
Connect-MgGraph -Scopes "Application.Read.All"
$thresholdDays = 60 # Ostrzegaj 60 dni przed wygaśnięciem
$threshold = (Get-Date).AddDays($thresholdDays)
$today = Get-Date
$apps = Get-MgApplication -All -Property "id,displayName,appId,passwordCredentials,keyCredentials"
$report = foreach ($app in $apps) {
# Sprawdź sekrety klienta (passwordCredentials)
foreach ($secret in $app.PasswordCredentials) {
if ($secret.EndDateTime -and $secret.EndDateTime -lt $threshold) {
[PSCustomObject]@{
AppName = $app.DisplayName
AppId = $app.AppId
Type = "ClientSecret"
SecretName = $secret.DisplayName
ExpireDate = $secret.EndDateTime.ToString("yyyy-MM-dd")
DaysLeft = [math]::Round(($secret.EndDateTime - $today).TotalDays)
Status = if ($secret.EndDateTime -lt $today) { "WYGASŁ" } else { "WYGASA" }
}
}
}
# Sprawdź certyfikaty (keyCredentials)
foreach ($cert in $app.KeyCredentials) {
if ($cert.EndDateTime -and $cert.EndDateTime -lt $threshold) {
[PSCustomObject]@{
AppName = $app.DisplayName
AppId = $app.AppId
Type = "Certificate"
SecretName = $cert.DisplayName
ExpireDate = $cert.EndDateTime.ToString("yyyy-MM-dd")
DaysLeft = [math]::Round(($cert.EndDateTime - $today).TotalDays)
Status = if ($cert.EndDateTime -lt $today) { "WYGASŁ" } else { "WYGASA" }
}
}
}
}
if ($report) {
$report | Sort-Object DaysLeft | Format-Table -AutoSize
Write-Host "Łącznie znaleziono: $($report.Count) wygasających poświadczeń"
} else {
Write-Host "Brak wygasających poświadczeń w ciągu $thresholdDays dni."
}
Krok 3: Raport certyfikatów SAML — Enterprise Applications
App Registrations to tylko połowa historii. Aplikacje enterprise z sekcji Enterprise Applications (np. integracje SAML z systemami HR, SSO do Salesforce) mają certyfikaty przechowywane jako Service Principals — osobny obiekt w Entra ID. Nie są widoczne przez Get-MgApplication.
Connect-MgGraph -Scopes "Application.Read.All"
$threshold = (Get-Date).AddDays(60)
$today = Get-Date
$sps = Get-MgServicePrincipal -All `
-Property "id,displayName,appId,keyCredentials,passwordCredentials" `
-Filter "servicePrincipalType eq Application"
$report = foreach ($sp in $sps) {
foreach ($cert in $sp.KeyCredentials) {
if ($cert.EndDateTime -and $cert.EndDateTime -lt $threshold) {
[PSCustomObject]@{
AppName = $sp.DisplayName
AppId = $sp.AppId
Type = "ServicePrincipal-KeyCredential"
ExpireDate = $cert.EndDateTime.ToString("yyyy-MM-dd")
DaysLeft = [math]::Round(($cert.EndDateTime - $today).TotalDays)
Status = if ($cert.EndDateTime -lt $today) { "WYGASŁ" } else { "WYGASA" }
}
}
}
}
$report | Sort-Object DaysLeft | Format-Table -AutoSize
Krok 4: Bezpieczna rotacja sekretu klienta
Rotacja musi następować w ściśle określonej kolejności. Odwrócenie kroków 2 i 3 powoduje przerwę w działaniu aplikacji.
Connect-MgGraph -Scopes "Application.ReadWrite.All"
$appId = "TWOJ-APP-ID-TUTAJ" # Użyj appId (nie objectId)
# Pobierz obiekt aplikacji
$app = Get-MgApplication -Filter "appId eq "
Write-Host "Aplikacja: $($app.DisplayName) | Aktualna liczba sekretów: $($app.PasswordCredentials.Count)/48"
# 1. Utwórz nowy sekret
$endDate = (Get-Date).AddMonths(6)
$newSecret = Add-MgApplicationPassword -ApplicationId $app.Id `
-PasswordCredential @{
DisplayName = "Rotated-$(Get-Date -Format yyyy-MM-dd)"
EndDateTime = $endDate
}
# KRYTYCZNE: Wartość sekretu widoczna jest TYLKO RAZ
Write-Host "=== ZAPISZ TERAZ ==="
Write-Host "Nowy sekret: $($newSecret.SecretText)"
Write-Host "KeyId: $($newSecret.KeyId)"
Write-Host "Wygasa: $($newSecret.EndDateTime)"
Write-Host "===================="
# 2. Zaktualizuj konfigurację aplikacji nowym sekretem
# 3. Przetestuj że aplikacja działa
# 4. Odczekaj minimum 30 minut
# 5. Dopiero wtedy usuń stary sekret:
# Remove-MgApplicationPassword -ApplicationId $app.Id -KeyId "STARY-KEY-ID"
Krok 5: Wymuś politykę zarządzania poświadczeniami
Application Management Policy pozwala ograniczyć tworzenie sekretów klienta lub skrócić maksymalny czas życia certyfikatów dla nowych aplikacji:
Connect-MgGraph -Scopes 'Policy.ReadWrite.All'
Import-Module Microsoft.Graph.Identity.SignIns
$params = @{
isEnabled = $true
applicationRestrictions = @{
keyCredentials = @(
@{
restrictionType = "asymmetricKeyLifetime"
state = "enabled"
maxLifetime = "P180D" # ISO 8601: 180 dni
restrictForAppsCreatedAfterDateTime = [DateTime]::Parse("2025-01-01T00:00:00Z")
}
)
}
}
Update-MgPolicyDefaultAppManagementPolicy -BodyParameter $params
Write-Host "Polityka aktywna. Nowe App Registrations (po 2025-01-01) mają max certyfikat 180 dni."
Krok 6: Automatyczne powiadomienia przez Logic Apps
Dla środowisk gdzie cotygodniowy manual to za mało — Logic App z triggerem Recurrence (np. co poniedziałek o 8:00) pobierająca dane z Graph API:
https://graph.microsoft.com/v1.0/applications?$select=id,appId,displayName,passwordCredentials
Kluczowe szczegóły implementacji:
- Utwórz dedykowaną App Registration z uprawnieniem
Application.Read.All(application permissions, nie delegated) - Włącz paginację w akcji HTTP — domyślny limit Graph API to 999 rekordów. Dla tenantów z 1000+ App Registrations wymagana obsługa
@odata.nextLink - Filtruj
endDateTime< dzisiaj + 60 dni w pętli For Each - Wyślij skonsolidowany e-mail z tabelą zamiast jednego maila per sekret
Typowe pułapki i edge cases
Pułapka 1: Sekrety "2299" — pozornie wieczne, faktycznie zapomniane
W starszych tenantach znajdziesz App Registrations z datą wygaśnięcia 31/12/2299. Technicznie działają. Praktycznie: to poświadczenia nierotowane od 3-5+ lat, stworzone przez programistów którzy już dawno odeszli. Znajdź je:
Connect-MgGraph -Scopes "Application.Read.All"
$legacyDate = Get-Date "2200-01-01"
Get-MgApplication -All -Property "displayName,appId,passwordCredentials" |
ForEach-Object {
$app = $_
$app.PasswordCredentials | Where-Object { $_.EndDateTime -gt $legacyDate } |
ForEach-Object {
[PSCustomObject]@{
App = $app.DisplayName
AppId = $app.AppId
SecretName = $_.DisplayName
FakeExpiry = $_.EndDateTime
}
}
}
Dla każdego wyniku: sprawdź w Sign-in logs czy aplikacja jest aktywnie używana, następnie zaplanuj rotację lub decommission całej rejestracji.
Pułapka 2: Limit 48 sekretów na App Registration
Każda App Registration ma limit 48 password credentials. Jeśli przy każdej rotacji dodajesz nowy sekret ale nie usuwasz starego, w pewnym momencie trafisz na ten limit. Kolejna rotacja skończy się błędem InvalidKeyCredentialsCount. Nie ma ostrzeżenia gdy zbliżasz się do limitu.
Skrypt z Kroku 4 wyświetla aktualną liczbę sekretów ($app.PasswordCredentials.Count/48). Każda operacja rotacji powinna kończyć się usunięciem starego sekretu.
Pułapka 3: Wartość sekretu widoczna tylko raz — zapisz do Key Vault
Wartość client secret jest dostępna wyłącznie w momencie tworzenia — zarówno przez portal jak i API. Kolejne odczyty zwracają null. To prowadzi do sytuacji gdzie tworzy się nowy sekret "bo nie wiadomo gdzie jest stary" — co dodaje kolejny wpis do limitu 48.
Jedyne bezpieczne rozwiązanie to Azure Key Vault:
# Po Add-MgApplicationPassword — natychmiast zapisz do Key Vault
$kvName = "twoj-keyvault"
$secretName = "$($app.DisplayName)-appsecret"
$secureVal = ConvertTo-SecureString $newSecret.SecretText -AsPlainText -Force
Set-AzKeyVaultSecret -VaultName $kvName -Name $secretName -SecretValue $secureVal
Write-Host "Sekret zapisany do Key Vault: $kvName/$secretName"
Pułapka 4: Opóźnienie propagacji po rotacji
Po dodaniu nowego sekretu i zaktualizowaniu konfiguracji aplikacji istnieje okno propagacji — Microsoft dokumentuje do 15 minut zanim nowe poświadczenia będą działać we wszystkich datacenter. Jeśli usuniesz stary sekret zbyt szybko po dodaniu nowego, możesz trafić w moment gdy ani stary (usunięty) ani nowy (jeszcze nie propagowany) nie jest akceptowany.
Bezpieczna strategia: po zweryfikowaniu że nowy sekret działa (sprawdź Sign-in logs w Entra — powinieneś zobaczyć aktywność z nowym Key ID), utrzymaj oba sekrety przez minimum 30-60 minut przed usunięciem starego.
Pułapka 5: Application Management Policy działa tylko prospektywnie
Parametr restrictForAppsCreatedAfterDateTime oznacza że polityka dotyczy tylko aplikacji stworzonych po tej dacie. Istniejące App Registrations mogą nadal dodawać nowe sekrety bez ograniczeń — polityka ich nie blokuje retroaktywnie. Polityka jest cenna dla budowania dobrych nawyków od teraz, ale nie zastępuje audytu istniejących rejestracji.
Alternatywa: Managed Identities jako docelowe rozwiązanie
Najlepszy sekret to taki którego nie trzeba zarządzać. Dla aplikacji działających na Azure (Azure Functions, App Service, Automation Runbooks, Virtual Machines) Managed Identity eliminuje problem całkowicie — aplikacja uwierzytelnia się do Graph API bez żadnego sekretu ani certyfikatu. Microsoft automatycznie rotuje poświadczenia w tle, bez udziału administratora.
Jeśli masz App Registrations używane przez aplikacje Azure, migracja na Managed Identity powinna być na roadmapie. Dla aplikacji zewnętrznych (on-premise, inni dostawcy chmury) sekrety pozostają jedyną opcją — tutaj wdrożenie monitoringu z Kroku 2 jest niezbędne.
Podsumowanie
- Entra ID nie wysyła e-maili gdy sekret wygasa. Entra Recommendations ostrzega 30 dni wcześniej tylko jeśli aktywnie sprawdzasz portal — za mało na profesjonalne zarządzanie zmianą.
Get-MgApplicationwymaga jawnego-Property passwordCredentials,keyCredentials— bez tego pola są puste i skrypt nie znajdzie niczego nawet jeśli wszystko wygasa jutro.- Service Principals to osobna kwestia — certyfikaty SAML i poświadczenia Enterprise Applications monitorujesz przez
Get-MgServicePrincipal, nie przezGet-MgApplication. - Rotacja ma kolejność: stwórz nowy sekret → zaktualizuj konfigurację → zweryfikuj w logach → odczekaj 30-60 min → usuń stary.
- Limit 48 sekretów jest realny i bez ostrzeżeń — czyść stare poświadczenia po każdej rotacji.
- Application Management Policy ogranicza nowe aplikacje, nie istniejące — nie zastępuje audytu historycznych sekretów.
- Docelowo: Managed Identity dla aplikacji Azure eliminuje problem całkowicie.