Azure deployment — portal walkthrough¶
Portal-only, point-and-click walkthrough for deploying Identity Atlas to a fresh Azure subscription. No Azure CLI required.
For the architecture rationale, sizing, and ops notes see azure-deployment.md.
What you'll get¶
- An App Service for Linux Containers running the Identity Atlas web image
- A Postgres Flexible Server (random admin password kept in Key Vault; firewall limited to the web app's outbound IP addresses, or reachable only through a private endpoint with
networkMode=private) - A Container App worker that polls Microsoft Graph on a schedule
- Key Vault, Storage Account (for
/data/uploads), Log Analytics - Entra ID single sign-on enforced on every API endpoint — no anonymous access
Total time: ~15 minutes.
The three steps¶
| What | Where | Time | |
|---|---|---|---|
| Step 1 | Deploy the app stack | Bicep template main.json |
~6 min |
| Step 2 | Register the Entra App (the deployed app's own UI walks you through it) | Azure portal — Entra ID | ~5 min |
| Step 3 | Fill in tenant + client IDs as env vars on the Web App | Azure portal — Environment variables | ~1 min |
Auth is on from the first deploy. The Bicep ships AUTH_ENABLED=true with empty AUTH_TENANT_ID / AUTH_CLIENT_ID. After Step 1, opening the app's URL shows a "Set up Entra ID" page with the exact steps (Steps 2 and 3 of this walkthrough) — there's no usable open-mode state where someone could accidentally use the deployment without auth.
Prerequisites¶
Permissions you need in the target tenant + subscription:
| Scope | Role |
|---|---|
| Subscription | Owner (or Contributor + the ability to assign Key Vault access policies) |
| Entra directory | Application Administrator (or Cloud Application Administrator + Application Developer) |
Both roles are granted by your tenant admin under Entra ID → Roles and administrators.
Resource providers (one-time per subscription): if the subscription has never used Azure Container Instances, the bootstrap deployment-script in Step 1 hangs for 20 minutes trying to register the provider itself and then fails. Register them up-front:
- Top search → Subscriptions → click your target subscription
- Left nav → Resource providers
- For each of these, type into the filter, click the row, click Register at the top:
Microsoft.ContainerInstance— most importantMicrosoft.AppMicrosoft.DBforPostgreSQLMicrosoft.WebMicrosoft.KeyVaultMicrosoft.OperationalInsightsMicrosoft.ManagedIdentity
Each one takes ~30 seconds. You can register them all in parallel.
Step 1 — Deploy the app stack (~6 min)¶
Click the button (opens in a new tab). Fill in:
| Field | Value |
|---|---|
| Subscription | Target subscription |
| Resource group | Create new — name it whatever you want (e.g. idatlas-rg) |
| Region | Sweden Central has been the most reliable in EU; West/North Europe sometimes have capacity issues |
| Size profile | s (~€79/mo) for normal use, xs (~€45/mo) for cheapest demo |
| Image channel | stable |
| Existing log analytics workspace id | Leave blank to create a fresh workspace, or paste the FULL ARM resource ID of an existing one |
Click Review + create → Create.
What happens: the bootstrap deployment-script first validates the LAW workspace ID format (and fails fast in <30s if it's wrong — pasting a resource group ID instead of a workspace ID is a known trap, the error tells you so directly). Then everything provisions in parallel: storage, identities, KV, Postgres, App Service, Container Apps, worker.
When the deployment finishes, click the deployment → Outputs. Copy:
- appUrl — e.g. https://idatlas-abc1234-web.azurewebsites.net — this is the URL you'll use as the redirect URI in Step 2.
Open the URL — you'll see the "Entra ID setup required" page. That's the expected state after Step 1: the app is deployed with auth turned on, but no Entra App is wired up yet. The page contains exactly the instructions you need for Steps 2 and 3 below — you can follow them on screen or use the more detailed version here.
If Step 1 fails¶
| Error message | Cause | Fix |
|---|---|---|
Action SequencerJob exceeded max allowed time |
Microsoft.ContainerInstance provider isn't registered |
Do the RP registration in Prerequisites, then redeploy |
existingLogAnalyticsWorkspaceId is not a valid Log Analytics workspace resource ID |
You pasted the resource group's ARM ID, not the workspace's | Get the workspace's ID from its Overview → JSON View; or leave the field blank to create a fresh workspace |
Site name 'xxx-web' is not available |
Hostname collision (extremely rare with the auto-generated prefix) | Pick a different resource group name — the prefix is derived from the RG ID, so changing the RG changes the prefix |
No available instances to satisfy this request |
Regional capacity exhausted | Pick a different region and redeploy to a fresh RG |
VaultAlreadyExists |
Key Vault name collision with a soft-deleted vault from a previous attempt in a same-named RG | Use a different resource group name (Key Vault soft-delete reserves names for 7 days) |
Requested data Disk size … cannot be less than current size |
Postgres storage was previously larger; you can't shrink Postgres storage | Use a larger sizeProfile, OR delete the RG and redeploy from scratch |
Step 2 — Register the Entra App (~5 min, portal)¶
You need an App Registration in your tenant so users can sign in. One-time setup per deployment.
- Search Entra ID in the Azure portal → open the Microsoft Entra ID blade
- Left nav → App registrations → + New registration
- Fill in:
- Name:
Identity Atlas(or whatever you prefer) - Supported account types: Accounts in this organizational directory only (Single tenant)
- Redirect URI dropdown: Single-page application (SPA) → paste the
appUrlfrom Step 1 (e.g.https://idatlas-abc1234-web.azurewebsites.net) - Click Register
On the app's Overview page, copy: - Application (client) ID - Directory (tenant) ID
Note: an SPA application uses the PKCE flow — there is no client secret. Tenant ID and client ID are all you need.
Expose the API scope¶
The MSAL client requests a scope api://<client-id>/access. Tell Entra about it:
- Left nav → Expose an API
- Click Add next to Application ID URI at the top → accept the default value (
api://<client-id>) → Save - Click + Add a scope:
- Scope name:
access - Who can consent: Admins and users
- Admin consent display name:
Access Identity Atlas - Admin consent description:
Allow the application to access Identity Atlas on behalf of the signed-in user. - User consent display name:
Access Identity Atlas - User consent description:
Allow the application to access Identity Atlas on your behalf. - State: Enabled
- Add scope
Step 3 — Fill in the tenant + client IDs (~1 min, portal)¶
The Bicep deploy in Step 1 already created AUTH_TENANT_ID and AUTH_CLIENT_ID env vars on the Web App — they're just empty. You fill them in now.
- Azure portal → Resource groups → your RG → click the App Service (named
idatlas-<hash>-web) - Left nav → Settings → Environment variables
- Find the existing
AUTH_TENANT_IDrow → click it → paste the Directory (tenant) ID from Step 2 → Apply - Same for
AUTH_CLIENT_ID→ paste the Application (client) ID from Step 2 → Apply - Click Apply at the bottom of the page to commit both changes → confirm Apply changes
The Web App restarts automatically (~30 seconds). Refresh the app URL — you'll be redirected to Entra to sign in.
Validation¶
- Open
https://<your-app-url>(refresh if you already had it open from Step 1) - You'll be redirected to Entra → sign in with a tenant user
- Consent prompt on first sign-in → click Accept
- You land on the Identity Atlas dashboard
Quick smoke test¶
- No yellow "Authentication is disabled" banner (it only shows if you explicitly set
AUTH_ENABLED=false) - Admin sub-tabs visible: Crawlers · Data · Account Linking · Risk Scoring · LLM Settings · Performance · About
- Hidden on Azure: Authentication
Common first-sign-in issues¶
| Symptom | Cause | Fix |
|---|---|---|
| Redirect loop between app and login.microsoftonline.com | SPA redirect URI on the App Reg doesn't exactly match the hostname | Step 2.3 — must be https://<auto-generated-name>-web.azurewebsites.net (no trailing slash, https not http) |
AADSTS500011: The resource principal named api://<guid> was not found in the tenant |
The access scope or Application ID URI is missing |
Step 2.5-2.7 — Expose an API → set the Application ID URI, then add the access scope |
AADSTS65001: The user or administrator has not consented |
Tenant requires admin consent for the scope and you signed in as a non-admin | Have a tenant admin sign in first to grant consent, or use Grant admin consent on the API permissions page of the App Reg |
Microsoft Defender for Cloud: "App Service apps should have authentication enabled"¶
If the subscription has Microsoft Defender for Cloud enabled, the idatlas-<hash>-web App Service is listed as unhealthy under Recommendations → "App Service apps should have authentication enabled" — even after Steps 1-3 are complete and Entra sign-in demonstrably works.
This is expected. It is not a sign that your deployment is unauthenticated.
Why it appears¶
That recommendation audits one specific thing: the App Service platform auth feature (App Service Authentication, a.k.a. Easy Auth — the authSettingsV2 resource config). Identity Atlas deliberately never configures it. Steps 1-3 set the AUTH_ENABLED / AUTH_TENANT_ID / AUTH_CLIENT_ID app settings, and sign-in is then enforced inside the application: the API validates every Entra access token itself and rejects anything else with a 401.
Defender reads the platform setting, not the application, so it correctly reports "no platform authentication configured" while the app is in fact fully authenticated. The recommendation is accurate about the layer it measures and blind to the layer we use.
Why it's by design¶
- The app registration is deliberately secret-less. Identity Atlas is registered as a public SPA client (no client secret). Easy Auth's interactive login flow expects a confidential client with a secret, which would add a credential to store, rotate, and leak.
- Platform enforcement would have to be perforated anyway. Easy Auth sits in front of every request, so enabling it in enforcing mode would require auth exclusions for the App Service health probe (
/api/health), the Container App worker's ingest calls (which authenticate with anfgc_crawler API key against the public URL), and thefgr_read-token GETs used by Excel / Power Query. Those exclusions would carve unauthenticated tunnels through the write-heavy ingest surface — a worse posture than the one the finding complains about. - In-app validation is stricter than the platform check. The API pins the token audience to
api://<clientId>, rejects id_tokens, verifies the issuing tenant, and applies theAUTH_REQUIRED_ROLES/ role-to-permission mapping. Easy Auth does none of that.
How to confirm auth really is on¶
Two checks, both from your own machine:
- Open
https://<your-app-url>in a private browser window — you are redirected to Entra to sign in, and you never see application data before signing in. - Call the API without a token — it must be rejected:
A
200here would mean auth is genuinely off — check thatAUTH_ENABLED=trueand thatAUTH_TENANT_ID/AUTH_CLIENT_IDare filled in (Step 3).
How to clear the marker (Defender exemption)¶
Once you've confirmed the above, exempt the resource so the recommendation stops flagging it:
- Azure portal → Microsoft Defender for Cloud → Recommendations
- Open "App Service apps should have authentication enabled"
- Select the
idatlas-<hash>-webApp Service in the resource list → Exempt - Scope: the resource (or the resource group, if you prefer it to survive a redeploy)
- Category: Mitigated — justification e.g. "Authentication is enforced at the application layer (Entra ID JWT validation in the API); platform Easy Auth is intentionally not configured. See Identity Atlas docs."
Creating an exemption requires a role that can write policy exemptions at that scope — Owner, Resource Policy Contributor, or Security Admin. The exemption is recorded in Azure Policy, so it stays auditable.
Adding platform-level (Easy Auth) authentication as defence-in-depth is tracked as possible future Azure hardening, alongside the network/secret and container hardening work. It is not part of the current deployment shape.
Changing something later¶
| Want to change | Do this |
|---|---|
| Size profile (xs ↔ s ↔ m ↔ l ↔ xl) | Re-run Step 1 with the new profile. ⚠ This resets AUTH_TENANT_ID and AUTH_CLIENT_ID to empty (Bicep's appSettings is a full replace) — you'll see the setup-required page again and have to redo Step 3. The IDs themselves don't change, so it's a 30-second paste. |
| Image channel (stable ↔ edge) | Re-run Step 1 with the new channel. Same Step-3-rerun caveat. |
| Log Analytics workspace (BYO vs new) | Re-run Step 1 with the changed value. Same Step-3-rerun caveat. |
| Entra tenant or client ID | Web App → Environment variables → edit AUTH_TENANT_ID / AUTH_CLIENT_ID → Apply. No redeploy needed. |
| Turn auth OFF (debug or demo) | Web App → Environment variables → set AUTH_ENABLED=false → Apply. The app comes back up in OPEN mode with the "Authentication is disabled" banner. Set it back to true (and ensure the IDs are still there) to re-enable. |
Tearing down¶
Resource Group → top menu Delete resource group → type the name to confirm.
Caveat: the Key Vault enters soft-delete with purge protection enabled — its name is reserved for 7 days. If you want to redeploy to a same-named RG within that window, the deploy fails at the key-vault step. Wait 7 days or use a different RG name.
Restricting which users can sign in¶
By default, any user in the tenant can sign in. To restrict to specific roles:
- App Registration → App roles → Create app role — define roles like
IdentityAtlas.ReadandIdentityAtlas.Admin - Enterprise applications → your app → Users and groups — assign users / groups to those roles
- Azure portal → your Web App → Environment variables → set
AUTH_REQUIRED_ROLES=IdentityAtlas.Read,IdentityAtlas.Admin→ Apply (the app restarts). Only users with at least one of those roles can sign in.
This is advanced enough that it isn't on either deploy form — set it post-deploy from the Web App's Environment variables blade.