Back to Checks
azure

App registration credentials are not expired or expiring soon

entra_app_registration_credential_not_expired
Service: entraSeverity: high
OfficialBuilt-inVerified
Categories:identity-access
Resource Type:NotDefined

Microsoft Entra ID app registrations are evaluated for credential validity. Each app's password secrets and certificate credentials are checked for expiration. Credentials that are already expired, expiring within 30 days, or have no expiration date are flagged.

Risk

Expired credentials cause service outages when apps can no longer authenticate. Credentials without expiration violate least privilege by persisting indefinitely. Long-lived or leaked secrets enable unauthorized API access, data exfiltration, and lateral movement via the app's permissions.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler azure --checks entra_app_registration_credential_not_expired

Recommendation

Rotate app registration credentials before expiration. Set expiration to 6-12 months maximum. Prefer managed identities or federated credentials over secrets. Monitor credential expiry with Azure Monitor alerts or Microsoft Entra workbooks.

Remediation

CLI

az ad app credential reset --id <app_id> --years 1

Other

1Sign in to Microsoft Entra admin center
2Go to Identity > Applications > App registrations
3Select the application with the expiring credential
4Go to Certificates & secrets
5Delete the expired credential
6Add a new credential with an appropriate expiration (recommended: 6-12 months)
7Update the consuming application with the new credential
8Consider migrating to managed identities or federated credentials where possible

Check Code

from datetime import datetime, timezone

from prowler.lib.check.models import Check, Check_Report_Azure
from prowler.providers.azure.services.entra.entra_client import entra_client

EXPIRY_WARNING_DAYS = 30


class entra_app_registration_credential_not_expired(Check):
    """
    Ensure Microsoft Entra ID app registration credentials are not expired or expiring soon.

    This check evaluates each app registration's password secrets and certificate credentials. A credential is reported individually and flagged when it is already expired, expiring within 30 days, or has no expiration date. Apps with no credentials are skipped.

    - PASS: The credential is valid for more than 30 days.
    - FAIL: The credential is expired, expiring within 30 days, or has no expiration date.
    """

    def execute(self) -> Check_Report_Azure:
        findings = []

        for tenant_domain, apps in entra_client.app_registrations.items():
            for app_id, app in apps.items():
                if not app.credentials:
                    continue

                for credential in app.credentials:
                    report = Check_Report_Azure(metadata=self.metadata(), resource=app)
                    report.subscription = f"Tenant: {tenant_domain}"
                    report.resource_name = (
                        f"{app.name} ({credential.credential_type}: "
                        f"{credential.display_name or 'unnamed'})"
                    )

                    if credential.end_date_time is None:
                        report.status = "FAIL"
                        report.status_extended = (
                            f"App '{app.name}' has a {credential.credential_type} "
                            f"credential with no expiration date."
                        )
                    else:
                        now = datetime.now(timezone.utc)
                        end = credential.end_date_time
                        if end.tzinfo is None:
                            end = end.replace(tzinfo=timezone.utc)
                        if end <= now:
                            days_ago = (now - end).days
                            report.status = "FAIL"
                            report.status_extended = (
                                f"App '{app.name}' has a {credential.credential_type} "
                                f"credential that expired {days_ago} days ago."
                            )
                        elif (end - now).days <= EXPIRY_WARNING_DAYS:
                            days_left = (end - now).days
                            report.status = "FAIL"
                            report.status_extended = (
                                f"App '{app.name}' has a {credential.credential_type} "
                                f"credential expiring in {days_left} days (within the "
                                f"{EXPIRY_WARNING_DAYS}-day rotation threshold); rotate it soon."
                            )
                        else:
                            days_left = (end - now).days
                            report.status = "PASS"
                            report.status_extended = (
                                f"App '{app.name}' has a {credential.credential_type} "
                                f"credential valid for {days_left} more days (beyond the "
                                f"{EXPIRY_WARNING_DAYS}-day rotation threshold)."
                            )

                    findings.append(report)

        return findings