Move browser authentication behind a generated-contract BFF

This commit is contained in:
ms2k 2026-07-19 18:21:17 +09:00
commit fa7220ee33
2 changed files with 7259 additions and 2825 deletions

View file

@ -20,6 +20,8 @@ Terms in this glossary are stable identifiers for use across Remora documentatio
| `ResultManifest` | The immutable exact inventory and root identity for one attempt's result aggregate. |
| `CommittedResult` | The concrete durable descriptor corresponding to the attempt committed by `JobProtocol`. |
| `WebClient` | The browser-based user interface for submitting and managing jobs. |
| `WebBFF` | The same-origin confidential OAuth client and token-mediating backend for `WebClient`. |
| `WebSession` | Ephemeral server-side OAuth session state addressed by an opaque browser cookie. |
| `API` | The authenticated service interface for job submission, status, cancellation, history, and deletion. |
| `Worker` | A stateless process that executes attempts and reports progress and outcomes. |
| `IdentityProvider` | The external authority for user identity, administrative roles, and storage authorization scopes. |
@ -51,9 +53,9 @@ Identity, coordination, and durable state are external services. Multiple API an
## Architecture
`WebClient` communicates with `API`, which authorizes requests through `IdentityProvider`, records jobs in `DurableStore`, and submits work through `CoordinationStore`. `Worker` replicas consume jobs and stream data from `Source` through `StorageAdapter` into `TargetStore`.
`WebClient` communicates only with its same-origin `WebBFF`. `WebBFF` is the confidential OAuth and OpenID Connect client, mediates browser API and live-event access, and sends bearer credentials only to `API`. `API` validates the bearer identity and roles supplied by `IdentityProvider`, records jobs in `DurableStore`, and submits work through `CoordinationStore`. `Worker` replicas consume jobs and stream data from `Source` through `StorageAdapter` into `TargetStore`.
`API` and `Worker` replicas are stateless. `DurableStore` realizes `durableVars` and retains architecture data outside the model. `CoordinationStore` realizes `coordinationVars` and may be reconstructed from durable state as permitted by `JobProtocol`.
`API`, `Worker`, and `WebBFF` replicas are stateless. `WebSession` records are ephemeral external state: their loss signs users out and does not alter durable application state. `DurableStore` realizes `durableVars` and retains architecture data outside the model. `CoordinationStore` realizes `coordinationVars` and may be reconstructed from durable state as permitted by `JobProtocol`.
`TargetStore` contains immutable attempt-scoped objects. The model's staging identity abstracts each concrete aggregate, and its committed attempt maps to the manifest descriptor exposed as the `CommittedResult`. `JobProtocol` is the sole specification of attempt authority and publication behavior.
@ -70,6 +72,7 @@ flowchart TD
end
subgraph Service
WebBFF[WebBFF]
API[API]
Worker[Worker]
end
@ -88,8 +91,9 @@ flowchart TD
TargetStore[TargetStore]
end
WebClient -- OIDC --> IdentityProvider
WebClient -- API + live updates --> API
WebClient -- Session + CSRF --> WebBFF
WebBFF -- OAuth + OIDC --> IdentityProvider
WebBFF -- Bearer API + live updates --> API
IdentityProvider -- Identity and roles --> API
API -- Submit and coordinate --> CoordinationStore
API -- Record and query --> DurableStore
@ -100,6 +104,19 @@ flowchart TD
StorageAdapter --> TargetStore
```
## Browser Session Boundary
- `WebBFF` uses the OAuth authorization-code flow as a confidential client with Proof Key for Code Exchange and nonce validation. OAuth and OpenID Connect remain the only user-authentication path.
- Access tokens, refresh tokens, and ID tokens remain inside `WebBFF` and its protected `WebSession` store. Browser JavaScript receives only an authorization snapshot and a CSRF value.
- The browser carries a random session identifier in a host-only `__Host-` cookie with `Secure`, `HttpOnly`, `SameSite=Strict`, and `Path=/`. Authentication creates both the session identifier and CSRF value. Token renewal rotates the identifier while retaining the CSRF value for in-flight same-session requests; an identity change invalidates the session.
- `WebSession` state is stored under a deployment-specific, WebBFF-only namespace in `CoordinationStore`. Its lifetime is bounded by the provider session and token lifetimes. Loss, expiry, unreadable state, or failed renewal destroys the local session and requires authentication again.
- Unsafe same-origin requests require the current session CSRF value in a request header. `WebBFF` also rejects unsafe requests with an absent or foreign `Origin` or a cross-site Fetch Metadata classification. SameSite cookies are defense in depth rather than the sole CSRF control.
- Browser API requests use a same-origin BFF route. `WebBFF` removes browser credentials and forwarding headers, injects the current access token, preserves idempotency keys and response status, and never retries an unsafe application request.
- Browser live-event connections use a same-origin BFF WebSocket route. The browser obtains the one-use event ticket through the API proxy; `WebBFF` validates the browser session and proxies the ticket-bearing connection without exposing a bearer token.
- Local logout invalidates `WebSession` before redirecting to the provider logout endpoint. Provider logout failure cannot restore the local session.
- The public `API` bearer-token interface remains available to non-browser OAuth clients. `WebBFF` does not weaken API authentication or authorization and does not reinterpret roles.
- `WebBFF` is presentation and identity mediation outside the state abstracted by `JobProtocol`. It preserves the authorization-snapshot, event-ticket, replay, and idempotency semantics defined at the API boundary.
## Worker Execution Envelope
- Every Worker process creates a fresh, non-reused `WorkerIncarnation`. A restarted process cannot reuse authority issued to an earlier lifetime, including when it occupies the same replica slot.
@ -244,7 +261,7 @@ flowchart TD
| Observability | Expose job status, progress, and errors through the UI and API |
| Scalability | Support multiple stateless API and worker replicas using shared state services |
| Performance | Stream multi-gigabyte objects without buffering complete objects in worker memory |
| Security | Authenticate and authorize all operations; do not expose long-lived source or target credentials to the UI |
| Security | Authenticate and authorize all operations; keep OAuth tokens and long-lived source or target credentials outside browser JavaScript |
| Deployment | Deploy as a standard Kubernetes service using externally managed identity, state, and object storage |
## Responsibility Boundaries

View file

@ -1,6 +1,6 @@
# Kubernetes Deployment
`deploy/charts/remora` runs API, Worker, and WebClient as separate Deployments.
`deploy/charts/remora` runs API, Worker, and WebClient with its `WebBFF` as separate Deployments.
CloudNativePG, Dragonfly, Vault, ZITADEL, and VersityGW remain externally
managed.
@ -19,8 +19,12 @@ managed.
- Dragonfly uses a deployment-unique namespace.
`REMORA_GLOBAL_ATTEMPT_LIMIT` and `REMORA_WORKER_LOCAL_ATTEMPT_LIMIT` remain
independent.
- ZITADEL supplies the issuer, project audience, client identifier, and exact
project-role claim.
- The WebBFF stores ephemeral WebSessions through a WebBFF-only Dragonfly
credential and namespace. Its credential Secret is not shared with API or
Worker coordination access. Loss of that namespace signs browser users out
without affecting durable application state.
- ZITADEL supplies the issuer, project audience, exact project-role claim, and
a confidential WebBFF client whose secret is mounted from a Secret.
- VersityGW uses dedicated credentials and a bucket whose `_remora` namespace
excludes other writers and lifecycle expiry. Deployments unable to establish
both properties are unsupported.
@ -28,13 +32,18 @@ managed.
## Security
NetworkPolicies default-deny each workload, isolate the Worker and management
ports, and allow dependencies by selector or CIDR. Public source egress excludes
ports, and allow dependencies by selector or CIDR. The WebBFF may reach only
the API, identity provider, and its session store. Public source egress excludes
non-global and metadata-capable IPv4 ranges. Private sources require matching
application and NetworkPolicy CIDR allowances.
Containers run non-root with read-only roots, dropped capabilities, resource
limits, and no service links. Worker temporary storage is bounded.
TLS ingress redirects HTTP to HTTPS and emits HSTS. The WebBFF session cookie
is always `Secure`; unencrypted origins are unsupported outside loopback test
environments.
## Operations
API and Worker expose liveness, dependency-aware readiness, and Prometheus