-
Notifications
You must be signed in to change notification settings - Fork 1
Troubleshooting
Common issues for the first public release. If your problem isn't here, check the FAQ, search open issues, or file a bug report.
Two flavours, one codebase. The managed portal at https://portal.pimactivation.com and any self-hosted instance run identical code. The differences are which app registration backs them and how it was consented. Sections below call out which scenario applies when it matters; everything unmarked applies to both.
When something looks wrong, try this in order before reading further:
- Click Refresh in the header. This invalidates the role and policy caches and re-fetches everything from Microsoft Graph and ARM.
- Open the activity drawer (bell icon) and look for failed operations — toasts are summarised there with the underlying error from Graph/ARM.
-
Hard reload (
Ctrl/Cmd+Shift+R). Tokens live insessionStorageand survive a normal reload, but a hard reload also clears stale module state. - Sign out and back in. From the user menu, choose Sign out, then sign in again — picking the right account if you have several.
-
Open DevTools → Console and Network. Most "weird" symptoms have a clear MSAL error code (
AADSTSxxxxx,interaction_in_progress) or an HTTP response with a claims challenge that pinpoints the cause.
The HTML shell loaded but the app failed to render. The <div id="app"> stays hidden until MSAL initialises successfully and the first roles fetch returns. Most common causes:
-
Self-hosted with placeholders left in
Portal/js/msal-config.js. The deployment script's "verifying injection" step should fail loudly if__PORTAL_CLIENT_ID__or__PORTAL_TENANT_ID__weren't replaced. Re-run the deployment with the same parameters — each run uses a freshdeploymentScriptRunId, so retries are safe. - Wrong tenant or wrong account. You signed in with an account whose home tenant doesn't have the app registration (typical for self-hosted), or the managed portal can't get a token for the directory you picked. Sign out, then sign in again and choose the correct account in the Microsoft account picker.
-
Browser blocking
login.microsoftonline.com. Strict third-party-cookie modes, tracking-protection extensions, or enterprise web filters can break the redirect handshake. Try the portal in a clean profile or a private window with extensions disabled to confirm. -
CSP blocked a critical script. The portal sets a strict Content-Security-Policy in
staticwebapp.config.json. If you've forked and added a script from a domain that isn't allowlisted, you'll see the violation in DevTools → Console.
The redirect URI on the app registration doesn't match the URL you're loading the portal from.
-
Managed portal: This should never appear on
portal.pimactivation.com. If it does on a different host that loads the same code, you're pointing a fork at the managed app's client ID — use your own app registration instead. -
Self-hosted: Add the SWA URL (and any custom domain) under Authentication → Single-page application in the app registration. The Bicep deployment outputs the exact URIs in
redirectUris; the script tries to apply them automatically, but falls back to a warning if the deploying identity lacks the Graph permission to update the app.
Your tenant requires admin consent before users can sign in.
-
Managed portal: A Global Admin (or Privileged Role Admin) in your tenant must grant consent once. The tenant in the URL is the consenting tenant — i.e. your tenant, not the publisher's. Either of these works:
- Pick-at-sign-in: https://login.microsoftonline.com/organizations/adminconsent?client_id=d3e13acf-c60d-46b4-b8e7-c077a7bf532a
- Explicit tenant:
https://login.microsoftonline.com/<your-tenant-id-or-domain>/adminconsent?client_id=d3e13acf-c60d-46b4-b8e7-c077a7bf532a
-
Self-hosted: Use the
adminConsentUrloutput from the Bicep deployment, or visit the same URL pattern with your own client ID. The tenant segment is still the consenting tenant.
After admin consent, individual users in that tenant do not need to consent again.
These errors come from the tenant switcher, not initial sign-in. The managed portal authenticates against the organizations authority, so the initial sign-in always lands in your account's home tenant — there is no Microsoft tenant picker before you reach the app.
After sign-in, the Switch tenant button lists every directory your account can reach via ARM (home, guests, member). Picking one triggers a fresh token request against that tenant; that's where these errors surface, and they typically mean one of:
-
AADSTS50020— your account isn't actually a member or guest of the target tenant (ARM listed it for some related reason but Entra rejects the token request). -
AADSTS500011— the portal's app registration isn't provisioned in the target tenant. For the managed portal, that means an admin in that tenant hasn't consented yet — see the admin consent URL above. -
"user not found in tenant" — same root cause as
AADSTS50020.
Click Back / switch to a tenant where you do have eligibilities, or have an admin in the failing tenant grant consent and try again.
A previous interactive flow didn't clean up — usually because a redirect was interrupted (browser closed mid-flow, MFA prompt cancelled, network blip). Close the tab, reopen the portal, and sign in again. If it persists, open DevTools → Application → Storage and clear sessionStorage for the portal origin.
Switch tenant calls ARM's tenants endpoint to enumerate every directory your account can reach (home, guests, member). Possible causes for a short list:
- Your account genuinely has access to only one directory.
- A Conditional Access policy on a guest tenant blocks ARM token acquisition.
- ARM consent for the managed app hasn't completed in the target tenant. Click Grant access in the banner to retry the ARM consent leg.
The chosen tenant is stored in sessionStorage under pim-portal-preferred-tenant and survives a refresh of the same tab — but not closing the tab.
The ARM consent leg failed. The portal will still load Entra and Group roles. Click Grant access in the banner to trigger an interactive ARM consent. If your tenant requires admin consent, the consent screen will say so — escalate to an admin.
- You may genuinely have no eligibilities in the current tenant. Switch tenants from the header.
- Check the type filter pills at the top of each table (User / Group / Azure / All). A plane may be filtered out from a previous session — settings persist in
localStorage. - Some directories restrict
Policy.Read.AllorRoleManagement.ReadWrite.Directorysuch that the eligibility queries return empty. Ask an admin to grant tenant-wide admin consent for the portal app. - For Azure Resource roles, your account must be able to enumerate role assignments at the tenant root via ARM's
asTarget()filter. Most directories allow this for any signed-in user, but custom RBAC can block it.
- Check the Active Roles tab — it might already be active. The eligible table hides already-active roles by default.
- Toggle Show already-active roles in eligible in Settings to confirm.
- Check the type filter pills; a plane may be filtered out.
- For AU-scoped roles, expand the row — the AU display name is resolved on demand and the row title shows only the role name.
The 30-minute in-memory policy cache may be stale, or a per-role policy fetch failed silently and the role rendered without details.
- Click Refresh in the header to invalidate both caches and re-fetch.
- Check DevTools → Console for warnings of the form "policy fetch failed for …". These are non-fatal but indicate a Graph permission, throttling, or network issue.
- AU-scoped Entra roles use a tenant-root bulk policy fetch to avoid
403s; if that bulk fetch is denied, every Entra role will render without policy details.
The portal detects auth-context requirements from policy and pre-emptively triggers a step-up before activation. The step-up is a full-page redirect to login.microsoftonline.com and back — not a popup. If the redirect never happens, check that browser extensions or enterprise web filters aren't blocking navigation to login.microsoftonline.com, then click Activate again.
Almost always one of:
- The role policy requires an auth context that your token doesn't satisfy. Open the role policy in Entra and check Activation requirements. The portal should detect this and step you up automatically; if it doesn't, please file a bug with the role definition ID and the policy.
- The role requires approval and the request was rejected by an approver — see the activity drawer.
- A Conditional Access policy unrelated to PIM (e.g. requiring a compliant device, sign-in frequency, or named location) is blocking the activation request itself. The Network tab will show the underlying claims challenge or a
denyReasonfrom CA. - Your account temporarily lost the eligibility between page load and activation (rare but possible). Refresh and try again.
This is normal — every Graph and ARM client in the portal recognises the WWW-Authenticate: insufficient_claims header (or the equivalent JSON body), pauses the operation, calls MSAL with the requested claims, and resumes with the new token. You'll see a redirect to login.microsoftonline.com and back, then the original operation completes.
If it loops or fails outright, file a bug with the offending request URL, the decoded claims challenge, and a HAR if you can.
- The role likely requires approval and is in PendingApproval state — it appears in the Active table with a "Pending approval" tag and in the activity drawer.
- For Azure Resource roles, ARM's view of the assignment can lag a few seconds behind a successful response. Click Refresh.
The portal does not currently poll for approval state. Refresh manually after your approver acts. (Tracked on the Roadmap.)
Open the activity drawer and click the operation to see the per-role outcome. Each row shows the role name, plane, status (Activated, PendingApproval, Failed), HTTP status, and the underlying error string. Common per-role failures:
- Justification too short — most policies require ≥ 10 characters; the server returns a 400 with a specific message.
- Ticket number required — surface the field by re-opening the activation dialog; it's only shown when policy requires it.
- Duration exceeds policy maximum — reduce the requested duration.
- MFA / strong auth required — Microsoft returns a claims challenge; the portal will step you up and the next attempt should succeed.
The Justification field is always shown — whether the policy lists it as required or optional. Other fields (ticket number, ticket system, custom duration) only render when at least one role being activated has a policy that requires them. If you expected the ticket field for a role and it's missing, the policy may have changed since the cache was populated — click Refresh to invalidate the cached policy and reopen the activation dialog.
Activation profiles are stored in your browser's IndexedDB (database pimactivation-profiles). They don't sync automatically — each browser profile, device, or private window starts with its own empty list. To move them, use the Export and Import buttons in the Profiles modal: Export writes a JSON file (pim-activation-profiles-YYYY-MM-DD-HHMM.json) you can re-import on another browser or machine.
In Settings, the Tenant-scoped activation profiles option is on by default. With it on, profiles are visible only in the tenant they were created in (legacy profiles from before this option are migrated and remain visible everywhere). Turn the option off to share profiles across all tenants.
- Private / incognito browsing disables IndexedDB writes in some browsers.
- Site storage quota exceeded — clear stale data in DevTools → Application → Storage.
- Some enterprise browser policies disable IndexedDB entirely. Try a different browser to confirm.
The role definition or eligibility was removed in Entra. The portal still sends the request and surfaces the error from the server. Edit the profile to remove the stale role.
Profile role IDs are tenant-specific. Importing a profile exported from a different tenant keeps the profile but its roles won't match anything in your current eligible list — you'll see a "… role(s) are no longer eligible and were skipped" warning when you try to activate. Either switch to the originating tenant or edit the profile to map it to roles in the current tenant.
The file you picked isn't a profiles export. Import only accepts JSON files produced by Export — a top-level array of profile objects with the expected fields. If you've hand-edited the file, check that it still parses as JSON and that each profile has at least name and roles.
Once you pick Light, Dark, or High contrast explicitly in Settings, that overrides the OS preference. Choose System to restore auto-detect. The choice is persisted in localStorage under pim-portal-theme.
Browser favicon caches are sticky. Open in a private window or fully clear site data for the portal origin to validate.
Persistence options live in Settings (e.g. Persist quick filters, Persist section state, Swap eligible/active sections). They store to localStorage; if you've cleared site data or your browser quota was reclaimed, they'll reset.
By design. Tokens live in sessionStorage, which survives reload but not tab close. After the tab closes, MSAL silent acquisition needs a fresh interactive sign-in.
The activity drawer is also session-scoped — it's cleared on tab close. Operations are not persisted server-side because the portal has no server.
Entra and Group calls render first; Azure Resource calls usually arrive shortly after. In tenants with many subscriptions and many resource roles, ARM enumeration can take several seconds even with the asTarget() tenant-root path. Subsequent visits in the same tab benefit from the per-tenant role cache in localStorage.
The Graph batch engine handles 429 by honouring Retry-After and applying exponential backoff. Brief bursts are normal during bulk activations of 20+ roles. If they persist or the operation fails, your tenant may be applying aggressive throttling — file a bug with the captured 429 response headers.
ARM activations run with bounded concurrency, but ARM's per-tenant write limits are stricter than Graph's. If a bulk Azure activation partially fails with 429 or 503, retry just the failed rows from the activity drawer.
Each tenant switch triggers a redirect and refetches everything for the new tenant. The role and policy caches are per-tenant, so switching back to a recent tenant is fast; switching to a brand-new one will take a full load.
These items only apply to self-hosted instances. The managed portal is built and deployed by CI; you should never see them on portal.pimactivation.com.
ERROR: portalSourceArchiveUrl downloaded successfully, but the ZIP does not contain Portal/index.html
Your supplied archive doesn't contain a Portal/ folder at the expected depth. The script searches up to four directory levels from the ZIP root for Portal/index.html. Verify the archive structure and re-run.
GitHub returns 404 to unauthenticated downloads of private repositories. Either:
- Deploy from a public branch with
portalSourceBranch, or - Build a ZIP locally and host it on a publicly reachable URL (e.g. a Blob with a short-lived SAS) and pass it as
portalSourceArchiveUrl.
The deployment identity could not update the app registration's redirect URIs via Graph. Add the values from the Bicep redirectUris output manually under Authentication → Single-page application in the app registration.
The fix is already built in — each run uses a fresh resource name driven by deploymentScriptRunId. Just rerun the template; the new run gets a new resource name automatically.
After binding the domain on the SWA, make sure the same domain is listed under your app registration's SPA redirect URIs. The deployment script attempts to add it; if the attempt failed, add it manually.
For a single-tenant self-hosted deployment the Bicep template injects your tenant ID directly into Portal/js/msal-config.js (replacing __PORTAL_TENANT_ID__). If the deployed file still contains organizations or a placeholder, the injection step failed — re-run the deployment.
The default staticwebapp.config.json enforces a strict CSP that allows scripts only from self and cdn.jsdelivr.net, and connects only to login.microsoftonline.com, graph.microsoft.com, and management.azure.com. If you've added telemetry, custom CDNs, or third-party scripts, extend the CSP accordingly. See Security and Privacy for the full header set.
When opening an issue, include:
- Whether you're on
portal.pimactivation.comor a self-hosted build (and the build SHA if known). - Browser and version, OS, and whether the issue reproduces in a private window with extensions disabled.
- The MSAL error code or HTTP status from DevTools, plus the request URL.
- The decoded claims challenge if the failure is auth-related.
- A short HAR (with sensitive headers redacted) for activation failures.
- Whether the role is Entra, Group, or Azure Resource, and whether it's AU-scoped.
The more of these you include, the faster the fix.