Skip to content

Self Hosted Deployment

Sebastian F. Markdanner [MVP] edited this page May 11, 2026 · 2 revisions

The PIMActivation Portal can be deployed into your own Azure tenant in a single operation using the included Bicep template. This page is the long-form walkthrough; the README has the short version.

Source of truth: Portal/deploy/bicep/portal-selfhosted.bicep. Portal/deploy/azuredeploy.json is auto-generated by the Sync Deployment Templates workflow.

What you get

  • An Azure Static Web App serving the portal SPA.
  • A user-assigned managed identity scoped narrowly as Website Contributor on the SWA only (so the deployment script can read the SWA deployment token via listSecrets() — no broader resource-group access).
  • A customer-owned storage account that caches the downloaded portal source archive for fallback redeploys.
  • A deployment script that downloads, configures, and deploys the portal in one shot, and attempts to add the generated SPA redirect URIs to your Entra app registration.

Prerequisites

  1. An Azure subscription and a resource group you can deploy into.
  2. An existing single-tenant Entra ID SPA app registration for the portal. See App Registration Setup. Azure deployment scripts cannot complete interactive sign-in to Microsoft Graph reliably, so the app registration must exist before deployment and its application (client) ID is a required parameter.
  3. Tenant-wide admin consent for the delegated permissions if your tenant requires it. The deployment outputs an adminConsentUrl you can use after deployment.

One-click deployment

Click Deploy to Azure in the README — or use the URL directly:

https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FNoble-Effeciency13%2FPIMActivation-Portal%2Fmain%2FPortal%2Fdeploy%2Fazuredeploy.json

You will be prompted for the parameters below.

Parameter reference

Parameter Required Default Description
applicationClientId yes Application (client) ID of an existing single-tenant Entra ID SPA app registration.
tenantId no subscription().tenantId Tenant ID. Defaults to the current subscription tenant.
customDomain no '' Optional custom domain (e.g. pim.contoso.com). Included in the redirect URI auto-merge attempt. The Static Web App custom domain must still be bound manually after DNS validates.
portalSourceBranch no main Branch to download. Each deployment pulls the latest commit at deployment time.
portalSourceArchiveUrl no '' Override the source download with a publicly reachable ZIP. The archive must contain Portal/index.html.
deploymentScriptRunId no utcNow('yyyyMMddHHmmss') Forces the deployment script to rerun and gives each run a fresh resource name to avoid Azure Files sharing violations on retry.
location no resourceGroup().location Azure region.
staticWebAppSku no Free Free or Standard.
resourceTag no PIMActivation Tag applied to all created resources.

What the deployment script does

  1. Downloads the portal source ZIP from portalSourceBranch (or your portalSourceArchiveUrl).
  2. Caches the archive in the customer-owned storage account for fallback redeploys.
  3. Locates Portal/index.html inside the extracted archive.
  4. Injects your applicationClientId and tenantId into Portal/js/msal-config.js via sed replacement of the __PORTAL_CLIENT_ID__ and __PORTAL_TENANT_ID__ placeholders.
  5. Verifies that no placeholders remain after injection (fails the deployment if any are left).
  6. Copies index.html, staticwebapp.config.json, and the css, js, images folders into a deployment payload directory.
  7. Reads the SWA deployment token via az staticwebapp secrets list.
  8. Installs the SWA CLI (@azure/static-web-apps-cli) and uploads the payload.
  9. Attempts to merge the generated default hostname (and customDomain, if supplied) into the app registration's SPA redirect URIs through Microsoft Graph.

Outputs

Output Use
portalUrl URL of the deployed Static Web App.
staticWebAppName Name of the SWA resource.
redirectUris SPA redirect URIs to add to the app registration if the script could not auto-add them.
clientId Echoes the client ID you supplied.
customDomainNextStep If you supplied customDomain, the CNAME and binding instructions.
adminConsentUrl Tenant-wide admin consent URL for the app registration.
sourceArchiveCache The customer-owned blob that holds the cached source archive.
nextStep A reminder to verify redirect URIs and grant admin consent if needed.

After the first deployment

  1. Open portalUrl. You should see the portal sign-in screen.
  2. Verify redirect URIs. If the deployment log shows a Microsoft Graph permission warning, copy the redirectUris output and add them under your app registration's Authentication blade → Single-page application platform.
  3. Grant admin consent if your tenant requires it, using the adminConsentUrl output.
  4. (Optional) Bind your custom domain. If you supplied customDomain, follow customDomainNextStep: create a CNAME pointing your domain at the SWA default hostname, then add and validate the custom domain on the Static Web App resource.

Reruns and redeployment

The deployment script uses a fresh resource name on each run (driven by deploymentScriptRunId), which sidesteps the Azure Files sharing violations that can otherwise block retries. Reruns therefore pick up the newest commit on portalSourceBranch automatically.

If the GitHub branch download fails and portalSourceArchiveUrl was not supplied, the script falls back to the cached source archive in the customer-owned storage account.

Deploying from a private fork

If you fork the repository privately, GitHub returns 404 to the unauthenticated deployment script. To deploy from a private fork, build a ZIP of the repository (PowerShell Compress-Archive works) and upload it somewhere publicly reachable — for example a public Azure Blob with a short-lived SAS URL — then pass that URL as portalSourceArchiveUrl. The archive must contain Portal/index.html somewhere inside it.

Troubleshooting

The deployment finishes but the portal renders a blank shell

Check the deployment script logs for placeholder injection failed. If the placeholders were not replaced, the SPA can't construct a valid MSAL config. Re-run the deployment with the same parameters.

Microsoft Graph permission warning in the deployment log

The deployment identity could not update the app registration. Add the redirectUris output manually under AuthenticationSingle-page application in your app registration.

403 on activation requests after deployment

Almost always one of:

  • The user does not actually have a PIM-eligible assignment for the role.
  • The tenant requires admin consent for one of the delegated scopes — use the adminConsentUrl output.
  • An Conditional Access policy requires an auth context the portal hasn't been told about (open the role's policy in Entra and check Activation requirements).

Custom domain redirect mismatch after binding

After binding the custom domain on the Static Web App, make sure that domain is also listed under your app registration's SPA redirect URIs. The deployment script attempted to add it; if the attempt failed, add it manually.

"AADSTS500113: No reply address is registered for the application"

The redirect URI was not added to the app registration. Add the value of the redirectUris output (or the deployed SWA URL with no trailing slash) under AuthenticationSingle-page application.

Browser caches the old favicon after a sync

Browser favicon caches are sticky. Open in a private window or clear site data to validate.

See also Troubleshooting for runtime issues.

Clone this wiki locally