Simple Admin Reporter: Enterprise Identity Reporting Platform
Last reviewed: June 2026
Most of the identity reporting I've done across twenty years of infrastructure work lived in the same fragile place: a folder of PowerShell scripts, a few saved LDAP queries, and a monthly ritual of running them by hand and merging the output in Excel. On a large, multi-domain estate spread across many sites, that ritual stops being an annoyance and becomes a risk. Access reviews, license cleanup, stale-account remediation, and audit evidence all depend on reports, and when those reports come from scripts only one person understands, the whole process is one resignation away from breaking.
Simple Admin Reporter is my answer to that. It's an open-source, self-hosted platform that turns identity reporting across Active Directory, Microsoft Entra ID, and Microsoft 365 into something repeatable, auditable, and ownable by a team rather than a person. The code is on GitHub at github.com/ilyafedotov-ops/SimpleAdminReporter. This post is about why it exists and what building it taught me — the feature list lives in the repository, where it stays current with the code.
Why reporting has to become a process
Identity reporting is easy to treat as an ad-hoc task: someone needs a list of stale accounts, you write a query, you send a CSV. That works right up until the report starts driving decisions. The moment a report feeds an access review, a license reclaim, or — most dangerously — a list of accounts to disable, it inherits all the requirements of a production process:
- One-off scripts are hard to audit and hard to hand over.
- Manual exports are inconsistent and often run with unclear permissions.
- Scheduled identity reports need retry handling, history, and evidence.
- Service owners need answers without writing LDAP or Graph queries themselves.
- Operations need logs, health checks, and a backup model around the reporting itself.
The shift I wanted to demonstrate is exactly that one: from "a clever script" to "a governed reporting process." That's the difference between a tool and a platform, and it's the part of the project worth showing in a portfolio.
What it does
A self-hosted web application that runs identity reports across all three Microsoft identity systems from one place:
- Pre-built reports for the common questions — inactive users, password expiry, locked accounts, privileged-group membership, MFA status, risky sign-ins, license usage, guest sprawl, mailbox and Teams activity.
- A query builder so a service owner can build and save a custom report without LDAP, PowerShell, or Graph knowledge.
- Scheduled reports with email delivery, plus execution history with status, row counts, and timing.
- Export to Excel, CSV, PDF, and JSON.
- Real-time health and log monitoring over WebSockets.
- Role-based access control, encrypted credential storage, and audit logging — because identity data is sensitive and the platform itself becomes an access-controlled system.
It's MIT-licensed and self-hosted by design: identity data is exactly the kind of data most organisations don't want leaving their network, so "runs in your own environment" was a requirement, not a preference.
Architecture
A deliberately boring stack — React front end, a Node.js/Express API, PostgreSQL for reports, history, audit, and configuration, Redis-backed Bull queues for scheduled and background work, and connectors into AD over LDAP and into Entra ID and Microsoft 365 over the Graph API.
Users → Nginx → React SPA
→ Node.js / Express API (REST + WebSocket)
→ PostgreSQL (reports, history, audit, config)
→ Redis / Bull (scheduled jobs, queues, cache, sessions)
→ AD (LDAP) · Entra ID · Microsoft 365 (Graph)
→ Exports, dashboards, audit trails
The report flow is queue-based so the UI never blocks on a slow directory query: the API enqueues a job, a background worker runs it against cached and live sources, persists the result and an audit record, and pushes completion back to the UI over WebSocket. "Boring" is the compliment here — the interesting problems in identity reporting are governance and correctness, not novel architecture, and the stack stays out of the way of both.
The enterprise pattern behind the project
The architecture is deliberately small, but the pattern is the same one I would use in an enterprise reporting service:
| Layer | Responsibility |
|---|---|
| Connectors | Read from AD, Microsoft Entra ID, Microsoft 365, and other approved identity sources. |
| Normalisation | Convert source-specific attributes into report-friendly models. |
| Policy | Apply scoping, exclusions, sensitivity rules, and report ownership. |
| Execution | Run reports on demand or schedule, with retry and timing evidence. |
| Evidence | Store run history, row counts, filters, exports, and audit events. |
| Review | Route reports to the people who can act on the result. |
That model matters because identity reporting should not stop at "here is a CSV." The platform should preserve enough evidence that a reviewer can answer: when was this report run, by whom, against which source, with which filter, using which permissions, and what action came out of it? Without that chain, the report is useful for troubleshooting but weak for governance.
Connector permissions and least privilege
The Microsoft Graph side of identity reporting is powerful, and power is exactly why connector permissions need care. Microsoft explicitly recommends requesting the least privileged permissions an app needs. That principle shapes how I think about reporting connectors: a stale-user report, an MFA-status report, and a license-usage report should not all require the same broad permission set just because broad permissions are easier during development.
In a production version, I would treat connector profiles as security objects:
| Connector profile | Scope |
|---|---|
| AD read-only inventory | LDAP read access for users, groups, OUs, and selected attributes. |
| Entra reporting | Graph permissions limited to directory and audit data needed by enabled reports. |
| Microsoft 365 reporting | Service-specific read permissions for mailbox, Teams, or license reporting. |
| Admin profile | Configuration-only access to report definitions, schedules, and connector settings. |
The point is not to make setup painful. The point is to avoid the common reporting-platform mistake: one overprivileged credential that can read everything because it was convenient during the first proof of concept. Identity data deserves better than convenience.
Security is the whole point, not a feature
Because the platform stores directory bind credentials and returns identity data, the security model isn't a section bolted on at the end — it's the reason several design decisions went the way they did:
- Encrypted credential storage for stored LDAP/Graph credentials, with secrets kept in environment or vault rather than in the database.
- Role-based access control with row-level scoping, so a user sees only the reports they created or were granted — the same identity-aware boundary I'd insist on for any system that exposes directory data.
- Append-only audit logging of authentication, report execution, configuration changes, and exports, exportable to a SIEM.
- Hygiene at the boundaries — parameterised queries, LDAP filter sanitisation, rate limiting, and standard security headers.
The principle underneath all of it: a reporting platform that can read your entire directory is a high-value target, and it has to be treated like one. The fastest way to turn a helpful tool into a liability is to make directory data easier to reach without making it easier to audit.
An example report, and why the shape matters
The reports are designed to end in an action, not a spreadsheet. The stale-users report is the clearest example:
| Field | Example | Why it's there |
|---|---|---|
| SamAccountName | j.smith | Unique account reference |
| Enabled | True | Filters to active risk |
| Last logon | 120 days ago | Inactivity evidence |
| Manager | Department manager | Review owner |
| Exception | Service / break-glass exclusion | Prevents unsafe cleanup |
| Action | Review, disable, or keep | Turns the report into a workflow |
The exception column is the one most home-grown stale-user scripts leave out, and it's the column that stops someone disabling a break-glass account or a service account that simply never logs on interactively. That single field is the difference between a report that informs a safe access review and a report that causes an outage — which is exactly the kind of operational knowledge a platform should encode once, so nobody has to remember it every month.
Reports as operational workflows
The useful reports are the ones that lead to a controlled action:
| Report | Bad use | Better use |
|---|---|---|
| Inactive users | Export list and bulk-disable accounts. | Route to managers with exceptions, evidence, and approval state. |
| Privileged groups | Screenshot Domain Admins once a quarter. | Track changes, owner, justification, and review history. |
| License usage | Send finance a CSV. | Identify reclaim candidates with last activity and business owner. |
| Risky sign-ins | Dump events into a mailbox. | Assign investigation with user, app, IP, location, and outcome. |
| Guest accounts | List guests. | Track sponsor, purpose, expiry, and last sign-in. |
This is why I built the project as a platform rather than a script collection. A script can answer a question. A platform can keep the answer attached to history, ownership, and evidence. That is what turns reporting into something a team can operate.
What "production ready" would mean
If this were running as a serious internal service, I would hold it to the same expectations as any identity-adjacent platform:
- Separate admin, report author, reviewer, and read-only roles.
- Connector health checks with visible failure states.
- Scheduled-job retry handling with alerting when a report fails.
- Export watermarking or audit trails for sensitive report downloads.
- Retention policy for report results and exports.
- Backup and restore testing for PostgreSQL, configuration, and encryption keys.
- SIEM forwarding for authentication, configuration changes, export events, and failed jobs.
- A documented break-glass procedure for recovering access without exposing stored credentials.
Those controls may sound heavy for a side project, but they are exactly the controls that make the idea enterprise-relevant. The lesson is not "use this exact app." The lesson is "identity reporting deserves the same engineering discipline as the systems it reports on."
What I'd do differently next
The honest part of any portfolio project. If I took this further:
- Stronger policy-as-code around report definitions, so report logic is reviewed like code rather than edited in place.
- A native approval workflow for high-impact report actions — especially anything that produces a disable or remediation list.
- More built-in evidence packages aimed directly at audit cycles, so a monthly access review is a button, not a project.
- Cleaner per-customer connector profiles for managed-service scenarios.
- Deeper Microsoft Entra access-review integration.
Getting started
It's a Docker Compose deployment: clone the repo, copy .env.example to .env, fill in the database, Redis, and optional AD/Entra/SMTP settings, then bring it up with docker-compose up -d, run the database migrations, and create the first admin user. The full setup, connector configuration, and SSL steps live in the repository README so they track the code rather than drifting out of date here.
Closing
Simple Admin Reporter is a side project, but the point of it isn't the feature count — it's the shift it demonstrates: from "a folder of scripts one person understands" to "a reporting process a team can run, audit, and hand over." After enough years watching identity reporting live on someone's laptop until that someone leaves, that governance shift is the thing I actually wanted to build.
References
- Microsoft Graph permissions reference
- Microsoft Graph permissions overview
- Microsoft Entra sign-in logs
- Microsoft Entra audit logs
Related infrastructure guides
- Active Directory Migration: Principles, Risks, and Production Checklists — the kind of migration work these reports support.
- Production-Ready Active Directory Automation with PowerShell — the script-level patterns this platform grew out of.
- Azure Security Assessment Templates: Reusable Infrastructure Patterns — the same "make it repeatable and auditable" idea, applied to security reviews.