A dark-mode, dedicated React SPA for managing Microsoft Entra ID app registrations and enterprise apps, with a focus on managing API permissions (both application and delegated) on enterprise apps. Authenticates with MSAL using delegated permissions against Microsoft Graph.
- Sign in with MSAL (delegated, browser SPA / PKCE)
- Browse app registrations and view metadata & manifest-required resource access
- Browse enterprise apps (service principals) with search and type filters
- Manage API permissions on enterprise apps:
- Add application (app-only) permissions via
appRoleAssignments - Add delegated permissions via
oauth2PermissionGrants(admin consent by default, single-user consent supported) - Revoke either kind inline
- Pick common APIs (Microsoft Graph, Exchange, SharePoint, Azure Management) or search all service principals
- Add application (app-only) permissions via
- Tags & favorites on app registrations, stored in the directory
(
application.tags) so they're shared with everyone managing the tenant. Tags written here are namespaceddebble:<name>; tags set by Entra or other tooling (HideApp, …) are shown read-only and preserved on every write.debble:Favoriteis a reserved tag rendered as a star; the list filters by tag. - Unique name (
application.uniqueName) shown on the list and detail pages, searchable, and settable once for apps that don't have one yet - Tenant shortcuts: pin a tab to a specific tenant with
?tenant=…
Add ?tenant=<tenant id or domain> to the URL to point sign-in — and therefore
every Graph call — at that directory instead of the saved default. Because the
app uses a HashRouter, the parameter sits in front of the hash and survives all
in-app navigation, so a single bookmark keeps you in that tenant:
https://entra.2cloud.nl/?tenant=debble.com#/enterprise-apps
- Works with a tenant GUID, a verified domain, or
common/organizations. - Useful when your home tenant differs from the one you manage: sign in with the account (guest accounts included) that has access to the pinned tenant.
- The pin is remembered per browser tab, so a redirect sign-in doesn't lose it and a second tab on the plain URL is back on the default tenant.
- The header chip shows the active directory and marks it when pinned by the URL.
- Settings → Tenant shortcuts stores named links per tenant, ready to copy or bookmark. Saving a new default tenant in Settings releases the pin.
- React 18 + TypeScript + Vite
@azure/msal-browser+@azure/msal-react- Microsoft Graph v1.0 via
fetch HashRouterfor zero-config GitHub Pages hosting
npm install
npm run devOpen the URL Vite prints, then either accept the default app registration
(Debble EntraApp) or go to Settings and set your own client/tenant.
The default client is the multi-tenant app registration
438ccf20-d1a2-4027-baf3-0104d018357e (Debble EntraApp) with SPA redirect
URI https://entra.2cloud.nl/. To use a different client you
must register an application in Entra ID with:
- Platform: Single-page application (SPA)
- Redirect URI: the exact URL the SPA is served from, including the
trailing slash (e.g.
https://entra.2cloud.nl/). The Setup screen computes and shows this URL — copy it into the app registration. - Supported account types: any (single-tenant or multi-tenant)
- API permissions (Microsoft Graph, Delegated):
User.ReadApplication.ReadWrite.AllDirectory.AccessAsUser.AllDelegatedPermissionGrant.ReadWrite.All
- Admin consent granted for the tenant (the
.Allscopes all require it)
Pushes to main automatically build and deploy dist/ via
.github/workflows/static.yml. The site is published on the custom domain
https://entra.2cloud.nl/ and built with base path /.
Custom domain setup:
- DNS (TransIP): a
CNAMErecordentra→ronaldvanhelden.github.io. - Repo Settings → Pages → Custom domain:
entra.2cloud.nl, then enable Enforce HTTPS. NoCNAMEfile is needed with Actions-based deploys. - App registration: add SPA redirect URI
https://entra.2cloud.nl/.
Forks without a custom domain are served from https://<owner>.github.io/<repo>/;
build those with VITE_BASE=/<repo>/ (set it on the Build step in the workflow).
- Delegated permissions are stored on the client service principal as
oauth2PermissionGrantsrows, one row per (clientId, resourceId, consentType). Multiple scopes live in the same row as a space-separatedscopestring. Adding a scope PATCHes the row; the last scope removed DELETEs the row. - Application (app-only) permissions are stored as
appRoleAssignmentsunder the client service principal, one row per granted role. Adding POSTs a new row; revoking DELETEs by assignment id.
- No secrets are stored; all calls go directly from the browser to Graph with a user access token acquired via PKCE.
- Configuration (client/tenant IDs) lives in
localStorageunderentraapp.authConfigso each device can point at a different client. - The same permissions that let you grant/revoke API permissions let you do serious damage to the tenant — only use admin accounts that understand this.