Skip to content

Microsoft SSO Entra Setup

Microsoft SSO Entra Setup

This page covers the command-line steps to register a Glimpse Tracker app in Microsoft Entra ID, create its client secret, store both in AWS Secrets Manager, and connect them to an environment. Production was set up this way on 2026-11-05. To rotate an existing secret, see Rotating the client secret.

For what the API does with these values, see Environment Variables.

Apps and secrets

All apps are registered in the glimpse.technology tenant (7b37b4de-5f73-4127-bd60-30728366aaac). The client IDs below are not secret.

Environment Entra app Client ID AWS account Secrets Manager names
Local dev Glimpse Tracker (DEV) 29517c6a-fc61-4b60-a725-6f0a39f523ca n/a (.env) n/a
Staging Glimpse Tracker (Staging) 79f63b3e-b2c3-419b-8086-5c1ef71b6c8e 802732539686 tracker/staging/microsoft-client-{id,secret}
Production Glimpse Tracker 68e42dfb-6545-4a3c-873f-99f3215157a2 951665295205 tracker/production/microsoft-client-{id,secret}

Prerequisites

  • CLIs: az, aws, jq, terraform, plus ImageMagick (magick) if you are setting the logo.
  • Entra access: an account in the tenant that can register apps. To update an app later, you need to be its owner or hold Application Administrator.
  • AWS access: an SSO profile for the target account: glimpse-staging or glimpse-prod.

The commands below use bash syntax. In fish, replace VAR=value with set VAR value and unset VAR with set -e VAR.

az login --tenant 7b37b4de-5f73-4127-bd60-30728366aaac --allow-no-subscriptions
aws sso login --profile glimpse-prod

1. Read an existing app's configuration

Copy the settings from an app that already works, so the new one matches:

az ad app list --filter "startswith(displayName,'Glimp')" \
  --query '[].{name:displayName, appId:appId}' -o table

az ad app show --id 79f63b3e-b2c3-419b-8086-5c1ef71b6c8e \
  --query '{displayName:displayName, signInAudience:signInAudience, web:web,
            optionalClaims:optionalClaims, requiredResourceAccess:requiredResourceAccess,
            info:info, notes:notes}' -o json

The working configuration:

  • Sign-in audience: AzureADMultipleOrgs, which allows work or school accounts from any tenant. The API also refuses personal Microsoft accounts.
  • Permissions: Microsoft Graph User.Read (e1fe6dd8-ba31-4d61-89e7-88639da4683d). The openid, email and profile scopes are requested at sign-in and don't need to be listed.
  • Implicit grant: off for both access and ID tokens. The API uses the authorization-code flow.
  • Optional claims: none. Work accounts with a mailbox already get the email claim when the email scope is requested.

2. Register the app

There must be one Web redirect URI for each hostname that offers Microsoft sign-in, which means both the public site and the admin site:

az ad app create --display-name "Glimpse Tracker" \
  --sign-in-audience AzureADMultipleOrgs \
  --web-home-page-url https://glimpseanalytics.com \
  --web-redirect-uris \
    https://tracker.glimpse.technology/api/v1/auth/microsoft/callback \
    https://tracker-admin.glimpse.technology/api/v1/auth/microsoft/callback \
  --required-resource-accesses '[{"resourceAppId":"00000003-0000-0000-c000-000000000000",
    "resourceAccess":[{"id":"e1fe6dd8-ba31-4d61-89e7-88639da4683d","type":"Scope"}]}]' \
  --enable-id-token-issuance false --enable-access-token-issuance false \
  --query '{appId:appId, id:id}' -o json

Note both IDs from the output:

  • appId is the client ID, which goes into Secrets Manager and the commands below.
  • id is the app's object ID, which is what Microsoft Graph calls use.
APP_ID=<appId>
OBJECT_ID=<id>

az ad app create has no options for the terms and privacy URLs, so set them, and the notes field, through Microsoft Graph:

az rest --method PATCH \
  --uri "https://graph.microsoft.com/v1.0/applications/${OBJECT_ID}" \
  --headers Content-Type=application/json \
  --body '{"notes":"<initials> <yyyymmdd>",
           "info":{"termsOfServiceUrl":"https://tracker.glimpse.technology/terms",
                   "privacyStatementUrl":"https://tracker.glimpse.technology/privacy"}}'

Create the service principal (the "enterprise application") in the home tenant. The portal does this automatically; the CLI does not.

az ad sp create --id "${APP_ID}" --query '{sp:id, name:displayName}' -o json

Use the staging app's logo. Entra expects about 215×215 pixels and under 100 KB, so resize it first. az rest can corrupt binary bodies, so upload with curl:

LOGO_URL=$(az ad app show --id 79f63b3e-b2c3-419b-8086-5c1ef71b6c8e --query info.logoUrl -o tsv)
curl -sfL -o logo-src.png "${LOGO_URL}"
magick logo-src.png -resize 215x215 logo-215.png

TOKEN=$(az account get-access-token --resource-type ms-graph --query accessToken -o tsv)
curl -sf -o /dev/null -w 'HTTP %{http_code}\n' -X PUT \
  -H "Authorization: Bearer ${TOKEN}" -H "Content-Type: image/png" \
  --data-binary @logo-215.png \
  "https://graph.microsoft.com/v1.0/applications/${OBJECT_ID}/logo"
unset TOKEN

A 204 response means the upload succeeded.

5. Store the client ID and secret

The client ID goes in as plain text. The secret goes straight from az into Secrets Manager through a pipe, so it never appears on screen, in shell history or in the process list. Store the secret's Value, not its Secret ID; Entra shows both in the portal.

export AWS_PROFILE=glimpse-prod AWS_REGION=eu-west-2
ENV_NAME=production   # or: staging

aws secretsmanager create-secret --name "tracker/${ENV_NAME}/microsoft-client-id" \
  --description "Glimpse Tracker (${ENV_NAME}) Entra app client ID" \
  --secret-string "${APP_ID}" --query ARN --output text

az ad app credential reset --id "${APP_ID}" --append --only-show-errors \
  --display-name "tracker-${ENV_NAME}-$(date +%Y-%m-%d)" --years 1 \
  --query password -o tsv \
| tr -d '\n' \
| aws secretsmanager create-secret --name "tracker/${ENV_NAME}/microsoft-client-secret" \
  --description "Glimpse Tracker (${ENV_NAME}) Entra app client secret" \
  --secret-string file:///dev/stdin --query ARN --output text

Things to know about this command:

  • --append is essential on an app that already has secrets. Without it, az deletes every existing secret.
  • Lifetime: --years 1 sets it. Diarise the expiry.
  • ARNs: each create-secret prints an ARN that ends in a random suffix such as -T3wstt. You need these in the next step.

Check the result without revealing the secret. A secret Value is 40 characters; a GUID-length (36 character) result means the Secret ID was stored by mistake.

az ad app credential list --id "${APP_ID}" \
  --query '[].{name:displayName, keyId:keyId, expires:endDateTime}' -o table

aws secretsmanager get-secret-value --secret-id "tracker/${ENV_NAME}/microsoft-client-secret" \
  --query SecretString --output text | tr -d '\n' | wc -c

6. Point Terraform at the secrets

Terraform only references these secrets by ARN; it never creates them. Set the ARNs in infra/envs/<env>/terraform.tfvars:

microsoft_client_id_secret_arn     = "arn:aws:secretsmanager:eu-west-2:951665295205:secret:tracker/production/microsoft-client-id-XXXXXX"
microsoft_client_secret_secret_arn = "arn:aws:secretsmanager:eu-west-2:951665295205:secret:tracker/production/microsoft-client-secret-XXXXXX"

Then deploy:

  • Staging: make staging-plan, then terraform -chdir=infra/envs/staging apply staging.tfplan.
  • Production: cut a release first, as described in Versioning & releases, then run make prod-deploy.

The Microsoft button appears on the login page once the API is running with the client ID set.

Changing an existing app

--web-redirect-uris replaces the whole list, so always pass every URI the app needs:

az ad app update --id 79f63b3e-b2c3-419b-8086-5c1ef71b6c8e \
  --web-redirect-uris \
    https://tracker.staging.glimpse.technology/api/v1/auth/microsoft/callback \
    https://tracker-admin.staging.glimpse.technology/api/v1/auth/microsoft/callback

az ad app update --id 79f63b3e-b2c3-419b-8086-5c1ef71b6c8e \
  --display-name "Glimpse Tracker (Staging)"

Rotating the client secret

Once the app and both secrets exist, rotate with:

scripts/rotate_microsoft_client_secret.sh staging|prod [years]

The script does the following:

  1. Adds a new secret with --append, so the old one keeps working.
  2. Writes the new secret to Secrets Manager with put-secret-value. The ARN doesn't change, so Terraform needs no change.
  3. Force-redeploys the api ECS service, because ECS reads secrets only when a task starts.
  4. Asks whether to delete the old Entra secrets. Answer yes only after you have confirmed that Microsoft sign-in works.

To delete an old secret by hand:

az ad app credential delete --id "${APP_ID}" --key-id <keyId>

Publisher verification

The apps show as unverified on the consent screen until someone who holds both an Entra admin role and Partner Center admin adds the Partner One ID under Branding & properties. This step can't be done with the commands on this page.