AWS rollout runbook — Hosted Login for the website + CRM (feature 008)

Plain, copy-paste steps to turn on Hosted Login for the WBSP Website and the AI-Native CRM on AWS (auth.wbsp-demo.com, tenant wbsp). Run the steps in order. Anything in <angle brackets> is a value you paste in.

You run every command below in a terminal on your Mac (Terminal.app). They use curl, which is already installed. Lines that start with # are comments — don't paste those.

Why this is needed: the AWS platform currently runs an older build with no Hosted Login (checked 2026-06-08). So Step 1 (redeploy the platform) must happen first, then the rest.


What's already prepared (committed; nothing deployed)

  • Website wbsp.yaml: post-logout URL + webhook secret declared.
  • CRM wbsp.yaml: switched to the wbsp tenant + confidential client; CRM split-horizon is handled in code.
  • Platform: admin endpoints so the steps below are simple curl calls (no database surgery).

The values you'll use

ThingValue
Auth platform base URLhttps://auth.wbsp-demo.com
Tenant slugwbsp
Tenant id6d8e672b-d5ab-494f-a53a-120681b1ce16
Website public URLhttps://wbsp.ai
CRM public URLhttps://crm.wbsp-demo.com

Step 0 — Platform secrets in .env.aws (do this BEFORE Step 1)

The platform's wbsp.yaml requires two secrets that must exist in the platform repo's .env.aws (git-ignored) before you deploy. If either is missing the deploy fails closed.

  • HAP_ADMIN_API_KEYyou choose this. It's the admin password for the curl steps below. From the platform repo root (390-headless-auth-platform-claude/), generate a strong one and append it:

    echo "HAP_ADMIN_API_KEY=$(openssl rand -base64 32)" >> .env.aws

    You'll load this value into ADMIN_KEY just below (after this step). Setting/changing it is safe — it affects nothing else.

  • HAP_MASTER_ENCRYPTION_KEY — ⚠️ must be the EXISTING value, not a new one. This key decrypts the tenant signing keys already stored in the AWS database. The wbsp tenant already has a live signing key there, so a new or missing master key means the platform can no longer sign tokens → all existing logins break. Recover the original value from wherever the current AWS deployment got it (the previous .env.aws, your secrets vault, or whoever first deployed) and put it in .env.aws as HAP_MASTER_ENCRYPTION_KEY=<original value>. Do not invent a new one. If it is truly lost, you must regenerate the tenant signing key (re-provision), which invalidates existing tokens — avoid unless unavoidable.

Confirm both are present (values hidden):

grep -E '^HAP_ADMIN_API_KEY=|^HAP_MASTER_ENCRYPTION_KEY=' .env.aws | sed -E 's/=.*/=<set>/'

Set these once (paste into your terminal)

Now that the admin key exists in .env.aws, set the three variables the rest of the steps reuse. Run from the platform repo root so .env.aws resolves:

export HAP=https://auth.wbsp-demo.com
export TENANT_ID=6d8e672b-d5ab-494f-a53a-120681b1ce16
export ADMIN_KEY=$(grep '^HAP_ADMIN_API_KEY=' .env.aws | tail -1 | cut -d= -f2-)

They last for the current terminal window only — if you open a new tab, set them again.

Step 1 — Redeploy the platform (you do this)

Deploy the platform repo's main to AWS with your normal deploy process. This ships the Hosted Login code to auth.wbsp-demo.com.

The database schema is migrated automatically on deploy — the container runs alembic upgrade head at startup, so you never update the DB by hand.

Check it worked — this should print 200 (the admin API used below exists only in the new build):

curl -s -o /dev/null -w "%{http_code}\n" "$HAP/v1/applications" \
  -H "X-Admin-Key: $ADMIN_KEY" -H "X-Tenant-Id: $TENANT_ID"

200 = the new platform build is live. Note: don't use end_session_endpoint in discovery as the check — it only appears after Step 2 enables the tenant flag, so it'd look "missing" here even when the deploy is fine.

Step 2 — Turn on Hosted Login for the wbsp tenant

Paste this whole command:

curl -s -X PATCH "$HAP/v1/tenants/$TENANT_ID" \
  -H "X-Admin-Key: $ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"hosted_login_enabled": true, "passwordless_jit_provisioning": true}'

Success looks like: a line of JSON containing "slug":"wbsp" (no error).

Check it worked — this should print 200:

curl -s -o /dev/null -w "%{http_code}\n" \
  "$HAP/t/wbsp/hosted/login?return_to=$(python3 -c 'import urllib.parse;print(urllib.parse.quote("https://auth.wbsp-demo.com/t/wbsp/oauth2/authorize"))')"

Step 3 — Set the two apps' return URLs

First, list the apps to get each one's internal id (you need the id, not the client_id):

curl -s "$HAP/v1/applications" \
  -H "X-Admin-Key: $ADMIN_KEY" -H "X-Tenant-Id: $TENANT_ID" | python3 -m json.tool

In the output, find the two apps by their client_id:

  • Websiteclient_id = hap_Rk6k31-W3ERV0Rbq
  • CRMclient_id = hap_GXqFJwrbFrSBf32Y

Copy each one's id (a long uuid). Then run these two commands, pasting the right id into each:

# Website — paste its id where shown
curl -s -X PATCH "$HAP/v1/applications/<WEBSITE_APP_ID>" \
  -H "X-Admin-Key: $ADMIN_KEY" -H "X-Tenant-Id: $TENANT_ID" -H "Content-Type: application/json" \
  -d '{"redirect_uris": ["https://wbsp.ai/api/auth/callback"],
       "post_logout_redirect_uris": ["https://wbsp.ai/"]}'

# CRM — paste its id where shown
curl -s -X PATCH "$HAP/v1/applications/<CRM_APP_ID>" \
  -H "X-Admin-Key: $ADMIN_KEY" -H "X-Tenant-Id: $TENANT_ID" -H "Content-Type: application/json" \
  -d '{"redirect_uris": ["https://crm.wbsp-demo.com/api/auth/callback/hap"],
       "post_logout_redirect_uris": ["https://crm.wbsp-demo.com/login"]}'

Success: each prints JSON with the client_id and no error. (Re-run the list command to confirm the redirect_uris / post_logout_redirect_uris are set.)

Both apps must be confidential — in the list output they should show "confidential": true. If the CRM shows false, ask the HAP admin to re-provision it as a confidential client (and give you the new secret for Step 5).

Step 4 — Turn on the website's "user was suspended/deleted" webhook

This makes the platform notify the website when an account is suspended or deleted. Paste:

curl -s -X POST "$HAP/v1/webhooks" \
  -H "X-Admin-Key: $ADMIN_KEY" -H "X-Tenant-Id: $TENANT_ID" -H "Content-Type: application/json" \
  -d '{"url": "https://wbsp.ai/api/webhooks/identity",
       "event_types": ["identity.suspended","identity.reinstated","identity.deleted","identity.email_changed"]}'

Important: the response contains a secret (or signing_secret) shown only once. Copy it now — it's the website's HAP_WEBHOOK_SECRET in Step 5.

Step 5 — Add the secrets to the apps' .env.aws files

These files are NOT in git; you edit them locally before deploying.

  • Website — in wbsp-website/.env.aws, add the line:

    HAP_WEBHOOK_SECRET=<the secret you copied in Step 4>

    (HAP_WEBSITE_CLIENT_SECRET should already be there.)

  • CRM — create/edit 41-ai-native-crm-v0/.env.aws and add:

    AUTH_HAP_SECRET=<the CRM confidential client secret from the HAP admin>

    (Also make sure AUTH_SECRET, ENCRYPTION_KEY, ANTHROPIC_API_KEY are present for the CRM.)

Step 6 — Deploy the website and the CRM (you do this)

Deploy both repos' main to AWS with your normal deploy process. (The CRM's in-cluster networking is already handled in code — nothing to configure.)

Step 7 — Try it in a browser

  1. Open https://wbsp.aiSign in → you should land on the Hosted Login page → enter your email → enter the code you receive by email (AWS sends real email) → you're signed in.
  2. Open https://crm.wbsp-demo.comSign in → same Hosted Login → signed in.
  3. One login, both apps: after signing into one, open the other in the same browser — it should let you in without asking for a code again.
  4. Sign out on either app → you're returned to its home page, signed out → signing in again asks for a code.

If something goes wrong — turn it off instantly

This disables Hosted Login again (the apps fall back to their previous login):

curl -s -X PATCH "$HAP/v1/tenants/$TENANT_ID" \
  -H "X-Admin-Key: $ADMIN_KEY" -H "Content-Type: application/json" \
  -d '{"hosted_login_enabled": false}'

It's a safe on/off switch — the rest of login is unaffected.

Quick troubleshooting

  • {"detail":"Not authenticated"} or 401 → your ADMIN_KEY is wrong/empty. Re-copy it from the platform .env.aws.
  • Sign-in loops back to the login page → Step 2 wasn't applied (tenant flag off), or the app's redirect_uris in Step 3 don't exactly match the app's real URL.
  • "Couldn't connect" when running Step 2/3 → the platform isn't deployed/reachable yet (Step 1).
  • Logout doesn't fully sign out → the app's post_logout_redirect_uris (Step 3) isn't set.