Authentication and external dashboards

The administration panel and external clients use the same HTTP APIs. A host supplies an AuthenticationProvider to RuntimeApplication; installing the package does not choose an identity provider. Production requires an explicit RuntimeScope and authentication binding.

Host contract

authenticate(AuthenticationRequest) verifies credentials and returns an AuthenticatedPrincipal with a stable, opaque principal_id, trusted scope_grants, and one effective_scope. authorize(principal, AuthorizationRequest) checks the action, scope and optional resource digest. See the generated action enum.

Do not derive grants from a workflow input, customer ID, query parameter or caller-supplied tenant header. Customer/global execution has the same authorization scope. A population workflow gains no extra authority; its host-owned database query must constrain the tenant and bound its page size. For multiple granted scopes, the host selects an effective scope only after checking membership.

Raise AuthenticationError for rejected credentials (generic HTTP 401), return False for a denied operation (403), and let unexpected identity-service failures become the API's generic 503. Never log credentials. Remote identity lookups must use asynchronous I/O or bounded offloading. Capability checks have no resource identity; each concrete request still gets an authorization check.

Production credential example

The host example includes host_application.production:create_gateway_application and create_worker_application. The gateway sets up token authentication and browser request checks. Dashboard credentials are loaded only by the gateway. The host_application.gateway and worker factories are local examples and reject production settings.

For a small group of trusted users, this example accepts randomly generated tokens over HTTPS. Browsers use HTTP Basic with a credential ID and token; standalone clients use Authorization: Bearer <token>. Generate tokens in your secret-management workflow using at least 32 random bytes. Use generated tokens rather than passwords chosen by users. The host loads only SHA-256 token digests and compares them in constant time. Basic requires transport encryption; its encoding does not protect credentials. See RFC 7617.

Load host settings once at startup:

Environment variable Value
HOST_APPLICATION_PUBLIC_ORIGIN Exact HTTPS origin, such as https://dashboard.example.com, without trailing slash
HOST_APPLICATION_CREDENTIALS Secret-injected JSON array of CredentialGrant objects
JUSTFLOW_RUNTIME__PROFILE production
JUSTFLOW_RUNTIME__SCOPE Explicit host-owned tenant/application/environment JSON
JUSTFLOW_OPERATIONS__ADMIN_PANEL_ENABLED true when shipping the panel

Each credential record contains credential_id, stable principal_id, token_sha256, aware UTC expires_at, scope, and an explicit actions array. Credential IDs and token digests must be unique. Rotating credentials for the same principal must preserve scope and actions. All grants must match this host's runtime scope. Store the JSON in Secrets Manager and inject it into gateway tasks; never commit live tokens, hashes or credential records. The host's typed HostSettings and CredentialGrant are the schema; startup rejects invalid records.

For browser login, open the origin root / first. The HTTP 401 Basic challenge establishes the origin-wide protection space; successful authentication redirects to /admin. The browser sends the credential on same-origin asset and API requests. The UI contains no token, token field, local storage credential or AWS credential. Every unsafe Basic-authenticated request must carry the exact configured Origin; foreign, duplicate and missing origins are denied. No cross-origin access is enabled. This implements the origin checks described by OWASP.

Terminate HTTPS at the ALB, allow gateway ingress only from its security group, and configure Uvicorn FORWARDED_ALLOW_IPS to the ALB subnet CIDRs. Do not use * on a reachable gateway. The browser boundary rejects non-HTTPS application requests; only aggregate /livez and /readyz remain available over the private health-check connection. Authentication headers must be excluded from proxy/application logs. Use a dedicated dashboard origin with no untrusted applications.

Rotation is a rolling deployment: install overlapping old/new credential records, replace all gateway tasks, distribute the new token, then remove the old record and replace every task again. Keep the principal ID stable for idempotent-operation ownership. Revocation becomes effective when every task has reloaded the new configuration, or at credential expiry, whichever occurs first. Browser Basic credentials can remain cached until the browser session is closed; the example does not promise a logout button, immediate distributed revocation, MFA or SSO. Hosts needing those features should replace this adapter with their identity provider while retaining scope/action checks and browser request protection.

The example's credential flow is exercised through the real ASGI control API in tests/applications/test_host_authentication.py, including denied actions, foreign scopes, expiry, rotation and browser origin enforcement. AWS TLS, proxy configuration and actual browser behavior must also pass the first-deployment acceptance checks.

Configuration modes and capability discovery

Start every dashboard session with GET /v1/operations/capabilities. Read api_compatibility, scope, configuration_mode, and individual permission booleans; refresh after deployment or identity changes. A UI control should be enabled only when its capability is present and true.

Route family / behavior local_source managed unavailable
/v1/configuration/draft, YAML, fragments, schedules, validation Supported with grants Supported with grants Absent
Apply the draft to local source files Supported; development profile only Absent Absent
Publish immutable revision, plan activation, activate/rollback Absent Requires publication/controller bindings Absent
/v1/operations/* reads Per configured source/service Per configured source/service Independent of authoring; dimensions may report unavailable
/v1/workflows/*, /v1/triggers/* controls Per capability Per capability Independent of authoring
/v1/scheduled-starts/* Per capability Production requires an authoritative quota binding Independent of authoring, fail-closed when unbound

The production credential example supplies authentication and starts the runtime. Your application still needs to provide a distributed activation controller, worker deployment and readiness checks, routing updates, and shared quotas for future starts. Managed authoring remains unavailable until its required bindings are installed; AWS storage settings alone do not enable editing. The remaining setup is listed in the Fargate runbook.

Standalone client

Export the OpenAPI contract and implement against its request/response schemas. No browser module imports are needed. For example, with a credential obtained through the host's secret distribution process:

curl --fail --header "Authorization: Bearer ${OPERATOR_TOKEN}" \
  "https://${APPLICATION_DOMAIN}/v1/operations/capabilities"
curl --fail --header "Authorization: Bearer ${OPERATOR_TOKEN}" \
  "https://${APPLICATION_DOMAIN}/v1/operations/workflows"
curl --fail --header "Authorization: Bearer ${OPERATOR_TOKEN}" \
  --header 'Content-Type: application/json' --data-binary @start-request.json \
  "https://${APPLICATION_DOMAIN}/v1/workflows"

Use opaque identifiers returned by the API. Match error code, not message text. For draft YAML, read the draft's version and request /v1/configuration/draft/export?expected_version=<version>; retry the read on conflict. Save with the matching optimistic-concurrency token and preserve local edits made while a save is in flight. For durable mutation APIs, retain the original request and idempotency key so a lost-response retry can recover the original result.