Entra Credential Monitor

Your app registrations expire.
Usually on a Saturday.

This watches the client secrets and certificates of your Entra ID app registrations and reports how many days each one has left, as PRTG XML, JSON and a small web page. One container, read-only, every tenant you look after.

$ docker compose -f docker-compose.portal.yml up -d
The problem

Nothing tells you in time

A secret quietly reaches its last day and the thing it powered stops: directory sync, a backup job, a signing flow. The error you get points at the symptom, never at the credential.

24 months

is the longest life Entra grants a client secret. Certificates last longer, which only means the surprise arrives later.

One mail

Microsoft sends a digest to the app owner. Owners leave, shared mailboxes get filtered, and nobody notices the gap.

Per tenant

The portal shows one directory at a time. Looking after twelve customers means twelve logins and no shared view.

Portal overview listing five customer tenants with shortest remaining runtime, status, credential counts and the time of the last check.
Every customer on one page, sorted by urgency. Numbers below the warning threshold turn orange, below the error threshold red.
Monitoring

It speaks PRTG natively

Point an HTTP Data Advanced sensor at the endpoint and you get one channel per application plus three summary channels, with the warning and error limits already set. No script, no scheduled task, no return-code parsing.

<!-- /prtg/<token> -->
<prtg>
  <result>
    <channel>Minimale Restlaufzeit</channel>
    <value>4</value>
    <customunit>Tage</customunit>
    <limitminwarning>30</limitminwarning>
    <limitminerror>14</limitminerror>
  </result>
  <!-- one channel per application -->
</prtg>

What the sensor shows

  • 4 dSVC-Backup-Veeam
  • 19 dSVC-Exchange-Archiv
  • 47 dConnectSyncProvisioning
  • 213 dSVC-Monitoring-PRTG
  • 340 dAPP-Intranet-SSO

Thresholds are per request, so one tenant can feed several sensors: the self-renewing Entra Connect certificate on a short threshold of its own, everything else on the normal one.

Two ways

A customer base, or no container at all

Both ship from the same repository and share the same Graph logic, so a sensor keeps its channel names and history when you move between them. Pick by whether you want one place that holds every customer, or nothing to host at all.

Portal

Customers added in the browser

Adds what a managed service provider needs: accounts with mandatory two-factor, encrypted credential storage and one scan per customer per day spread over 24 hours.

  • Onboarding without touching a config file
  • Credentials encrypted, bound to their record
  • Per customer PRTG token, served from the stored scan
  • Roles, audit trail, forced check on demand
docker compose -f docker-compose.portal.yml up -d
PRTG script sensor

No server of your own

A PowerShell sensor that runs on the PRTG probe itself. Windows PowerShell 5.1, no modules, outbound 443 only. Right for a customer where nothing may be hosted.

  • Nothing to deploy, nothing to keep patched
  • Same channel names and limits as the container
  • One tenant per sensor, no shared view
  • Every poll is a fresh Graph query
prtg\Get-EntraSecretExpiry.ps1
Portal dashboard listing six customer tenants with status, shortest remaining runtime, credential counts, time of last check and the daily scan slot.
The portal view: six customers, one already past an expiry and one inside the warning window. A sensor poll answers from the stored scan and never calls Microsoft Graph.
REST API

Or let your own portal drive it

If you already run a customer portal, onboarding twice is work nobody asked for. The portal exposes /api/v1 so your system creates the customer, triggers the scan, reads the remaining lifetimes and hands out the sensor URL, all without anyone opening this UI.

MethodPathScopeWhat it does
GET/customers read Every customer with its summary
POST/customers write Create one, with tenant id, client id and secret
PATCH/customers/<key> write Change it, omitted fields stay put
POST/customers/<key>/check write Scan now, the answer carries the result
GET/customers/<key>/credentials read Its credentials, shortest runtime first
GET/customers/<key>/urls read The sensor URLs to paste into PRTG
POST/customers/<key>/token write New sensor token, the old URL goes quiet
GET/openapi.json read Generated description, feed it to a client generator

Keys, not passwords

Issued in the portal by an administrator, shown once, stored as an Argon2 hash. Two scopes: read sees customers, credentials and URLs, write additionally creates, changes, scans and rotates. Revoking one takes effect on the next request, and every use is in the audit trail.

# create a customer
curl -X POST https://portal/api/v1/customers \
  -H "Authorization: Bearer esm_..." \
  -H "Content-Type: application/json" \
  -d '{"key":"musterag",
       "display_name":"Muster AG",
       "tenant_id":"...","client_id":"...",
       "client_secret":"..."}'
Credentials go in, never out. No response carries a client secret or a private key, only whether one is stored and, for a certificate, its thumbprint and expiry. A test asserts that against the database and against every response that renders a customer.
Portal settings page for the API: a form to issue a key with name and scope, the list of issued keys with prefix, scope, status and last use, and a table of the eleven available endpoints with method, path, scope and purpose.
The settings page lists the endpoints of the running instance, so whoever writes the integration does not have to read the source.
The small print

What it needs, and what it never sees

One permission

Application.Read.All, application-only, with admin consent. Metadata only.

Never the secret

Graph does not return secret values at all. Only names, dates and thumbprints leave the tenant.

Read-only by design

No write permission is requested, so it cannot change anything in your directory even by mistake.

Unprivileged container

Runs as UID 10001, read-only root filesystem, no new privileges.

Secret or certificate

A client secret is the default for operational reasons and works in every tenant. A certificate is the stronger credential, and the answer where a policy restricts secrets.

Tested

465 tests covering aggregation, both renderers, the request path and the authentication flows.

Step by step

Guides, in German

The three walkthroughs an engineer actually needs: onboarding a customer tenant, wiring up the PRTG sensor, and driving the whole thing over the API.

Find out what expires next month

One app registration, one container, and you know. MIT licensed, no account, nothing phones home.