Register an app, wire OIDC single sign-on to Azure, and complete a login round-trip end to end.
An app registration is the front door to SSO
Before Microsoft Entra ID will sign users into your application, it has to know the application exists and is allowed to ask — that record is an app registration. With one in place, OpenID Connect lets a user authenticate with their existing Microsoft account; your app receives an ID token full of claims about who they are and never touches a password. You will wire this with App Service built-in authentication, which implements the entire OIDC flow at the platform layer, so the portal's own code stays about the portal. Then you'll sign in as yourself and read the claims Entra handed back.
The password the app never sees is the password it can never lose.
The config is CLI-verifiable (app registration, redirect URI, provider wired, login required, OIDC discovery reachable). But the payoff — the actual sign-in and the ID-token claims — lives behind an interactive browser flow that no script can drive. You'll do that pass by hand at the end. That's the nature of SSO, not a shortcut.
You need a free Azure account, the Azure CLI (az), then be signed in with az login. First time? The 15-minute Set up your machine page covers the account, the installs (winget / brew / apt), and sign-in. Prefer zero installs? Run everything in Azure Cloud Shell (Bash), preinstalled and already signed in.
You need permission to register apps in Entra (e.g. Application Administrator, Cloud Application Administrator, or Global Administrator). On a personal tenant you have it; on a work tenant you likely don't — use a personal test tenant. Run in Cloud Shell (Bash), and have a browser you can sign into your tenant with. Cost: App Service F1 (Free). Repo: github.com/kloudcaptain/campux-labs.
Variables
The app name is fixed here, so the redirect URI is stable — the app registration must reference it exactly.
# Windows/Git Bash: stop it mangling /subscriptions/... arguments (harmless on macOS/Linux) export MSYS_NO_PATHCONV=1 SUFFIX=$RANDOM RG="campux-lab-sso-rg" LOCATION="eastus" APP="campux-portal-$SUFFIX" # globally unique across *.azurewebsites.net PLAN="campux-plan-$SUFFIX" APP_DISPLAY="Campux Staff Portal $SUFFIX" TENANT_ID=$(az account show --query tenantId -o tsv) REDIRECT="https://$APP.azurewebsites.net/.auth/login/aad/callback" echo "App=$APP"; echo "Redirect=$REDIRECT"; echo "Tenant=$TENANT_ID"
Deploy the portal (still unprotected)
If az appservice plan create fails with "Current Limit (Total VMs): 0", that's a subscription limit, not a mistake. The loop creates the plan in the first region with free-tier quota. (If every region reports 0, request an increase under Quotas → App Service.)
az group create --name "$RG" --location "$LOCATION"
for LOC in "$LOCATION" eastus2 westus2 centralus westus3 northeurope westeurope; do
echo "Trying $LOC ..."
if az appservice plan create --name "$PLAN" --resource-group "$RG" --sku F1 --is-linux --location "$LOC" 1>/dev/null 2>&1; then
echo ">>> Plan created in $LOC"; break
else
echo " no F1 quota in $LOC — trying the next region"
fi
done
az webapp create --name "$APP" --resource-group "$RG" --plan "$PLAN" --runtime "NODE:20-lts"
Get the tiny app and deploy it, then warm the free-tier container:
git clone https://github.com/kloudcaptain/campux-labs.git cd campux-labs/lab-b-app-registration-oidc-sso/app zip app.zip server.js package.json az webapp deploy --name "$APP" --resource-group "$RG" --src-path app.zip --type zip az webapp restart --name "$APP" --resource-group "$RG" sleep 30 for i in 1 2 3 4 5; do curl -sS "https://$APP.azurewebsites.net" && break sleep 20 done
curl says Signed-in user: (no authenticated user). The site loads and, right now, anyone can reach it. We'll fix that.Register the app in Microsoft Entra ID
The --enable-id-token-issuance true is not optional: Easy Auth uses the OIDC hybrid flow, and without it sign-in later fails with AADSTS700054.
APP_ID=$(az ad app create \ --display-name "$APP_DISPLAY" \ --sign-in-audience AzureADMyOrg \ --web-redirect-uris "$REDIRECT" \ --enable-id-token-issuance true \ --query appId -o tsv) # A just-created app registration can lag Graph replication; pause so the next calls don't 404. sleep 15 az ad sp create --id "$APP_ID" # the enterprise app; sign-in needs it CLIENT_SECRET=$(az ad app credential reset --id "$APP_ID" --query password -o tsv) echo "App (client) id: $APP_ID"
az ad app show --id "$APP_ID" --query "{redirect:web.redirectUris, idToken:web.implicitGrantSettings.enableIdTokenIssuance}" -o json shows your exact redirect URI and "idToken": true. Both are required for sign-in.Turn on sign-in
One command enables App Service authentication, sets Microsoft Entra as the provider, and requires every visitor to log in — the classic single-command form, no extension needed.
az webapp auth update \ --resource-group "$RG" --name "$APP" \ --enabled true \ --action LoginWithAzureActiveDirectory \ --aad-client-id "$APP_ID" \ --aad-client-secret "$CLIENT_SECRET" \ --aad-token-issuer-url "https://sts.windows.net/$TENANT_ID/" \ --aad-allowed-token-audiences "$REDIRECT"
Confirm the config, and that Entra's OIDC discovery document — the metadata the whole flow relies on — is reachable:
az webapp auth show --name "$APP" --resource-group "$RG" \
--query "{enabled:enabled, action:unauthenticatedClientAction, clientId:clientId}" -o json
curl -s "https://login.microsoftonline.com/$TENANT_ID/v2.0/.well-known/openid-configuration" | head -c 200; echo
enabled: true, a redirect/login action, and your clientId; the discovery URL returns JSON beginning {"token_endpoint":.... Enabling auth restarts the app — give it ~30 seconds (sleep 30) before the browser step so you don't hit a cold-start mid sign-in.Sign in — the manual payoff
This part cannot be scripted; that's the nature of interactive SSO. In a browser:
- Open a new private/incognito window and go to
https://<your app>.azurewebsites.net. - You're redirected to the Microsoft sign-in page. Sign in with an account in your tenant (accept the consent prompt the first time).
- You land back on the portal, which now shows Signed-in user: your@email — the identity came from Entra, and the app never saw your password.
- View the claims Entra issued: browse to
/.auth/meon the same site. You'll see JSON withname,preferred_username,oid,tid, and token expiry.
/.auth/me lists your claims. That is OIDC SSO working end to end.1. AADSTS700054 → id-token issuance didn't take; re-run az ad app update --id "$APP_ID" --enable-id-token-issuance true. 2. Redirect mismatch → the registration's redirect URI must be exactly $REDIRECT. 3. Issuer/audience error → your app may issue v2 tokens; re-run Step 3 with --aad-token-issuer-url "https://login.microsoftonline.com/$TENANT_ID/v2.0". Try this first if sign-in completes but the app rejects the token.
Tear it down
The app registration and service principal are directory objects — they survive az group delete, so delete them explicitly.
az group delete --name "$RG" --yes
az ad app delete --id "$APP_ID" # also removes the service principal
az ad app list --app-id "$APP_ID" --query "[].appId" -o tsv is empty and az group exists --name "$RG" is false — no orphaned registration, no resources.What you can now honestly claim
You registered an application in Microsoft Entra ID and configured OpenID Connect single sign-on for an App Service using built-in authentication, with no custom auth code. Keep four things: an app registration is how Entra knows about your app and will sign users into it; OIDC lets users sign in with their existing Microsoft identity while your app receives claims, never a password; App Service Easy Auth implements the whole flow and hands you the identity as request headers, with full claims at /.auth/me; and redirect URI, id-token issuance, and issuer/token-version are the three settings that make or break a sign-in. The Bicep piece declares the auth config as code, while the registration — a Graph object — stays CLI.