Relationships
↑ child of #662#685 serve-auth: mode: token — server token authentication on WebSocket connections
Opened by stack72 · 6/18/2026· Shipped 6/19/2026
Parent
Sub-issue of #662 (serve authentication & authorization). Layer 4, item 2.
Dependencies
- ✅ #673 — Server token model (mint/redeem/revoke/expire)
- ✅ #674 — Client credential storage (ServerCredentialRepository)
- ✅ #682 — Auth config schema (`mode: token`)
- ✅ #683 — Admin materialization
Summary
When `swamp serve` runs with `--auth-mode token`, WebSocket connections must present a server token to authenticate. This is the simplest authentication mode — no OAuth provider, no browser flow. An admin mints tokens on the server, distributes them to users, and users present them when connecting.
This completes the authentication loop: the server knows WHO is connecting and can stamp the connection with a principal identity.
The flow
Admin mints a token (on the server host)
```bash swamp access token mint --name adam-token --principal user:adam → Token: adam-token.a1b2c3d4e5f6... Give this to the user. It cannot be retrieved again. ```
This creates a ServerToken model instance (#673) — stores the hash in a vault, returns the plaintext once.
User connects with the token
```bash swamp model method run hello execute \ --input 'run=echo hello' \ --server wss://swamp.acme.internal:9090 \ --token adam-token.a1b2c3d4e5f6... ```
Or store the token for reuse: ```bash
Save the token for this server
swamp auth server-login --server wss://swamp.acme.internal:9090 \ --token adam-token.a1b2c3d4e5f6...
Now --server uses the stored token automatically
swamp model method run hello execute \ --input 'run=echo hello' \ --server wss://swamp.acme.internal:9090 ```
Server authenticates the connection
On WebSocket upgrade:
- Client presents the token (in a header or as the first frame)
- Server splits `.`, looks up the ServerToken model instance by name
- Calls the `redeem` method — validates via timing-safe comparison, checks not expired/revoked, updates `lastUsedAt`
- On success, issues a session credential via the existing `SessionCredentialService` (same as worker enrollment)
- Stamps the connection with the principal (`user:` from the token record)
Unauthenticated connections are rejected
When `mode: token`, any WebSocket connection that doesn't present a valid token is rejected before it can send any frames. When `mode: none`, authentication is skipped (today's behavior preserved).
What to build
1. Token presentation on connection
Extend the WebSocket connection handler in `src/serve/connection.ts` to:
- Check `authConfig.mode` from ConnectionContext
- If `token`: require token presentation, validate, stamp connection with principal
- If `none`: skip authentication (existing behavior)
The token can be presented as:
- A query parameter on the WebSocket URL: `wss://host:9090/?token=name.secret`
- Or as the first message after connection (a pre-auth frame)
The URL query parameter is simpler — the WebSocket API supports it natively and no protocol changes are needed. The server reads it during the upgrade.
2. Principal on connection
After successful authentication, the connection carries a `Principal` (`user:`). This is passed to downstream handlers so they know who's acting. The `ConnectionContext` (or a per-connection object) gains the authenticated principal.
3. `swamp access token mint` CLI command
A new command for admins to mint server tokens:
```bash swamp access token mint --name --principal user:id [--duration-days ] ```
Calls the ServerToken model's `mint` method (#673). Outputs the plaintext token once. Default duration 30 days.
4. `swamp access token list` CLI command
List active server tokens (without secrets):
```bash swamp access token list → NAME PRINCIPAL CREATED EXPIRES LAST USED adam-token user:adam 2026-06-18 2026-07-18 2026-06-18 ```
5. `swamp access token revoke` CLI command
```bash swamp access token revoke adam-token ```
Calls the ServerToken model's `revoke` method. Takes effect immediately — the next request from that token is rejected.
6. Client-side token storage
When connecting with `--server`, the CLI looks up a stored credential via `ServerCredentialRepository` (#674). If `--token` is explicitly provided, it takes precedence. The `swamp auth server-login` command stores the token for a server URL.
7. Session credential issuance
After token validation, the server issues a session credential via `SessionCredentialService` — the same mechanism workers use after enrollment. The session credential is short-lived (15 min, sliding window) and used for subsequent data plane requests. This reuses all existing session machinery.
Scope
- Token presentation and validation on WebSocket connections
- Principal stamping on authenticated connections
- `swamp access token mint/list/revoke` CLI commands
- `--token` flag on `--server` commands
- `swamp auth server-login` for storing tokens
- Client-side credential lookup from ServerCredentialRepository
- Session credential issuance after token validation
- Tests for all paths
Out of scope
- OAuth login flow — layer 4, item 3
- Authorization enforcement (checking what the principal can DO) — layer 5
- `can-i` command — layer 5
References
- Server token model: `src/domain/models/access/server_token_model.ts`
- Client credential storage: `src/domain/auth/server_credential.ts`, `src/infrastructure/persistence/server_credential_repository.ts`
- Session credential service: `src/domain/remote/session_credential.ts`
- Worker enrollment (pattern to follow): `src/serve/worker_gateway.ts`
- Connection handler: `src/serve/connection.ts`
- Auth config: `src/domain/access/serve_auth_config.ts`
- Remote run (client side): `src/cli/remote_run.ts`
Shipped
Click a lifecycle step above to view its details.