Web Management UI
The vending ui command starts a browser-based management dashboard for ITL Subscription Vending.
It is built on FastAPI + HTMX + Tailwind CSS and served by Uvicorn on the local machine.
Pages
| Page | Route | Description |
|---|---|---|
| Dashboard | /dashboard |
Health status, queue statistics, recent jobs (auto-refresh every 30 s) |
| Jobs | /jobs |
Job queue viewer, dead-letter queue, DLQ purge |
| Provision | /provision |
Run or preflight a subscription provisioning workflow |
| Config | /config |
Active configuration (secrets redacted) |
Installation
pip install "itl-subscription-vending[ui]"
The [ui] extra adds click, uvicorn, itsdangerous (session middleware), and msal (Entra ID SSO).
Starting the UI
# Minimal — no auth
vending ui --remote http://vending-api:8000
# With SSO (recommended)
vending ui --remote http://vending-api:8000 \
--tenant-id <tid> \
--client-id <cid>
# Open browser automatically
vending ui --remote http://vending-api:8000 --open
All options can be set via environment variables — see Configuration below.
Authentication
When --tenant-id and --client-id are configured (or built into the package — see below), the UI requires authentication before granting access.
When auth is disabled (no tenant/client ID), the UI is openly accessible to anyone who can reach the port.
Login page
The login page (/auth/login) detects credentials automatically and presents the most convenient option first:
1. Azure session auto-detected (green card)
On page load the server checks the following sources in order:
| Priority | Source | Set by |
|---|---|---|
| 1 | ARM_ACCESS_TOKEN env var |
Azure Pipelines, Terraform |
| 2 | AZURE_ACCESS_TOKEN env var |
Manually exported |
| 3 | VENDING_TOKEN env var |
vending --token <t> or shell export |
| 4 | az account get-access-token |
User ran az login |
If any source returns a valid JWT, a green card appears:
Azure session detected
Found a token for user@example.com via Azure CLI.
[Continue as user@example.com →]
One click — session created, user lands on the dashboard.
2. Sign in with Microsoft
Starts a browser-based PKCE/OAuth 2.0 flow:
- User clicks “Sign in with Microsoft”
- Redirected to
login.microsoftonline.com - Signs in with their organisation account (MFA and Conditional Access apply normally)
- Redirected back to
/auth/callback - Group and/or role membership checked (if
--required-group/--required-roleconfigured) - Session and API token stored; user lands on the dashboard
No client secret is required — the PKCE flow (PublicClientApplication) is used by default.
Set --client-secret (or VENDING_UI_ENTRA_CLIENT_SECRET) to switch to a confidential client flow.
3. Paste a token
For users without a browser redirect flow available. The page shows the exact Azure CLI command to run:
az account get-access-token --query accessToken -o tsv
The user copies the output, pastes it into the text field, and clicks submit.
The JWT payload is decoded locally to extract the display name — signature verification is enforced by the API on every actual API call.
Entra ID App Registration (one-time, admin)
- Go to Azure Portal → Entra ID → App registrations → New registration
- Platform: Web
- Add redirect URI:
http://127.0.0.1:8080/auth/callback
(adjust port if--portdiffers) - Under Authentication, enable “Allow public client flows”
(required for PKCE without a client secret) - Note the Application (client) ID and Directory (tenant) ID — these are the values for
--client-idand--tenant-id
Optional: group-based access control
To restrict access to members of a specific Entra ID group:
- Under Token configuration, add a Groups claim → select Security groups → save
- Pass the group’s Object ID as
--required-group(orVENDING_UI_ENTRA_REQUIRED_GROUP)
Optional: role-based access control
To restrict access using App Roles:
- Under App roles, create a role (e.g. value
VendingUI.Access) - Assign users or groups to the role under Enterprise applications → [your app] → Users and groups
- Pass the role value as
--required-role(orVENDING_UI_ENTRA_REQUIRED_ROLE)
Built-in defaults
To ship SSO pre-configured so users need no flags at all, set the constants at the top of src/subscription_vending/cli/ui.py:
_DEFAULT_TENANT_ID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
_DEFAULT_CLIENT_ID = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
_DEFAULT_API_SCOPE = None # set to "api://<api-cid>/.default" only if API auth is on
With these set, vending ui --remote http://... is all a user needs.
CLI flags and environment variables always override the built-in defaults.
Configuration
Environment variables
| Variable | CLI flag | Default | Description |
|---|---|---|---|
VENDING_API_URL |
--remote |
(none) | Base URL of the running vending API to proxy |
VENDING_UI_HOST |
--host |
127.0.0.1 |
Host to bind the UI server to |
VENDING_UI_PORT |
--port |
8080 |
Port for the web UI |
VENDING_UI_ENTRA_TENANT_ID |
--tenant-id |
(none) | Entra ID tenant ID. Enables SSO when set together with client ID |
VENDING_UI_ENTRA_CLIENT_ID |
--client-id |
(none) | App registration client ID |
VENDING_UI_ENTRA_CLIENT_SECRET |
--client-secret |
(none) | Client secret. Omit to use PKCE (no secret) |
VENDING_UI_REDIRECT_URI |
--redirect-uri |
http://127.0.0.1:<port>/auth/callback |
OAuth2 redirect URI |
VENDING_UI_ENTRA_REQUIRED_GROUP |
--required-group |
(none) | Entra ID group Object ID — only members are granted access |
VENDING_UI_ENTRA_REQUIRED_ROLE |
--required-role |
(none) | App role value — only users with this role are granted access |
VENDING_UI_ENTRA_API_SCOPE |
--api-scope |
(none) | API scope for access token delegation (needed only when API auth is enabled and users log in via SSO) |
Token auto-detection env vars
The login page also checks these env vars for a pre-existing token (checked before attempting az cli):
| Variable | Typical source |
|---|---|
ARM_ACCESS_TOKEN |
Azure Pipelines, Terraform |
AZURE_ACCESS_TOKEN |
Manually exported |
VENDING_TOKEN |
vending --token <t> or $env:VENDING_TOKEN |
Auth routes
| Route | Method | Description |
|---|---|---|
/auth/login |
GET |
Login page (auto-detects existing session, shows all sign-in options) |
/auth/az-login |
GET |
One-click login using detected env var or Azure CLI token |
/auth/sso |
GET |
Starts the Entra ID PKCE browser redirect flow |
/auth/callback |
GET |
OAuth2 callback — validates state, PKCE, group/role, creates session |
/auth/token |
POST |
Accepts a pasted Bearer token, decodes claims, creates session |
/auth/logout |
GET |
Clears the session and redirects to the login page |
API scope (when is it needed?)
--api-scope is only required when both of these are true:
- The vending API has
VENDING_API_ENTRA_TENANT_IDset (API Bearer auth is enabled) - Users log in via the SSO (Microsoft button) flow — not via token paste or env var detection
In that case, MSAL must request an access_token scoped to the API alongside the id_token used for the UI session.
For token-paste and env-var flows, the token supplied already contains the correct audience — no extra scope needed.
Relationship to the API’s Bearer auth
When VENDING_API_ENTRA_TENANT_ID is set on the API server, every endpoint requires a valid Entra ID Bearer token.
The UI forwards the stored api_token as Authorization: Bearer <token> on every proxied request automatically.
See API Reference → Authentication for the API-side configuration.