|
1
|
# Cross-Domain FWD Spawning — Internal OIDC Variant (v2)
|
|
2
|
|
|
3
|
> **Scope**: Internal FWD server → spawned FWD client bootstrapping only. User authentication is unchanged. OIDC subset used: JWT for tokens, JWKS for key distribution.
|
|
4
|
>
|
|
5
|
> **Key design decision**: **No cookies**. Third-party cookies in cross-origin iframes are blocked by modern browsers. All session state is maintained through WebSocket + postMessage + signed URLs.
|
|
6
|
|
|
7
|
---
|
|
8
|
|
|
9
|
## 1. What Breaks Today (Cross-Domain)
|
|
10
|
|
|
11
|
| # | Interaction | Why it breaks |
|
|
12
|
|---|-------------|---------------|
|
|
13
|
| 1 | [getRemoteUri()](file:///home/hc/gcd/p2j_repo/p2jtrunk/src/com/goldencode/p2j/main/WebClientSpawner.java#267-297) appends `?token=<opaque>` | Opaque token, no expiry, no audience scoping |
|
|
14
|
| 2 | Iframe navigates to `http://other-host:port/...` | Iframe becomes cross-origin; postMessage bridge must pre-know client origin |
|
|
15
|
| 3 | Spawned client's embedded Jetty has no CORS config | REST fetches from cross-origin iframe are blocked |
|
|
16
|
| 4 | Session cookies from the client | Third-party cookies blocked in cross-origin iframes by modern browsers |
|
|
17
|
| 5 | Top-level resource URLs (e.g. PDFs for printing) | No cookie = no auth for plain browser GETs in new tabs |
|
|
18
|
| 6 | Client iframe loading resources from FWD server | Cross-origin `<script src>`, `<img src>` — can't attach auth headers to HTML tag loads |
|
|
19
|
|
|
20
|
> [!NOTE]
|
|
21
|
> The login AJAX POST ([fwd_sdk.js](file:///home/hc/gcd/p2j_repo/p2jtrunk/src/com/goldencode/p2j/main/fwd_sdk.js) → FWD server) is **not** affected — the login page is loaded via `srcdoc` (same-origin with parent).
|
|
22
|
|
|
23
|
---
|
|
24
|
|
|
25
|
## 2. Solution Overview
|
|
26
|
|
|
27
|
```mermaid
|
|
28
|
flowchart TD
|
|
29
|
subgraph "Authentication (unchanged)"
|
|
30
|
A[Login form / SSO] -->|credentials| B[FWD Server]
|
|
31
|
end
|
|
32
|
|
|
33
|
subgraph "Spawn Handover (JWT)"
|
|
34
|
B -->|spawn process| C[FWD Client]
|
|
35
|
C -->|clientIsReady| B
|
|
36
|
B -->|mint JWT| D["JSON {url, jwt}"]
|
|
37
|
D -->|iframe navigate| C
|
|
38
|
C -->|validate JWT| E[Session established via WebSocket]
|
|
39
|
end
|
|
40
|
|
|
41
|
subgraph "Runtime (cookie-free)"
|
|
42
|
E -->|WebSocket| F[Normal operation]
|
|
43
|
F -->|reconnect needed| G[postMessage → parent → server → new JWT]
|
|
44
|
F -->|PDF/print| H[Signed resource URL in new tab]
|
|
45
|
F -->|JS/CSS/img from server| I["Option A: Client proxies server resources<br/>Option B: Pre-signed server URLs"]
|
|
46
|
end
|
|
47
|
```
|
|
48
|
|
|
49
|
---
|
|
50
|
|
|
51
|
## 3. Initial Handover (JWT)
|
|
52
|
|
|
53
|
### 3.1 Flow
|
|
54
|
|
|
55
|
```mermaid
|
|
56
|
sequenceDiagram
|
|
57
|
participant Login as Login (srcdoc iframe)
|
|
58
|
participant Server as FWD Server
|
|
59
|
participant Client as Spawned Client
|
|
60
|
participant Parent as web_landing.html
|
|
61
|
|
|
62
|
Login->>Server: POST /gui (credentials)
|
|
63
|
Server->>Server: Authenticate + spawn
|
|
64
|
Client-->>Server: clientIsReady(uri)
|
|
65
|
Server->>Server: Mint JWT {iss, aud, exp, uuid}
|
|
66
|
Server-->>Login: JSON {url, jwt, storageId}
|
|
67
|
Login->>Parent: postMessage({origin: clientOrigin})
|
|
68
|
Note over Parent: Pre-register iframeOrigin
|
|
69
|
Login->>Client: navigate iframe to url?token=jwt
|
|
70
|
Client->>Client: Validate JWT → establish WebSocket
|
|
71
|
```
|
|
72
|
|
|
73
|
### 3.2 JWT Structure
|
|
74
|
|
|
75
|
```json
|
|
76
|
{
|
|
77
|
"iss": "https://fwd-server:8443",
|
|
78
|
"sub": "<fwd-user-id>",
|
|
79
|
"aud": "https://192.168.1.50:9443",
|
|
80
|
"exp": "<+60s>",
|
|
81
|
"iat": "<now>",
|
|
82
|
"jti": "<unique-id>",
|
|
83
|
"uuid": "<spawned client UUID>"
|
|
84
|
}
|
|
85
|
```
|
|
86
|
|
|
87
|
### 3.3 What Changes vs. Current
|
|
88
|
|
|
89
|
| Aspect | Current | New |
|
|
90
|
|--------|---------|-----|
|
|
91
|
| Token format | Opaque from `ServerKeyStore` | Signed JWT (RS256/EdDSA) |
|
|
92
|
| Token scope | No audience, no expiry | `aud` = client origin, `exp` = 30–120s |
|
|
93
|
| Key distribution | Full `ServerKeyStore` via RPC | Public key via [getServerData()](file:///home/hc/gcd/p2j_repo/p2jtrunk/src/com/goldencode/p2j/main/WebClientSpawner.java#315-324) RPC (or JWKS endpoint) |
|
|
94
|
| Post-handover session | Cookie / in-memory | **WebSocket = session, no cookies** |
|
|
95
|
|
|
96
|
---
|
|
97
|
|
|
98
|
## 4. Session Continuity Without Cookies
|
|
99
|
|
|
100
|
### 4.1 Normal Operation
|
|
101
|
|
|
102
|
The WebSocket connection **is** the session. No cookies needed while it's alive. This matches current FWD behavior.
|
|
103
|
|
|
104
|
### 4.2 Reconnection (WebSocket Drops)
|
|
105
|
|
|
106
|
```mermaid
|
|
107
|
sequenceDiagram
|
|
108
|
participant Client as Client iframe (cross-origin)
|
|
109
|
participant Parent as web_landing.html (server-origin)
|
|
110
|
participant Server as FWD Server
|
|
111
|
|
|
112
|
Note over Client: WebSocket connection lost
|
|
113
|
Client->>Parent: postMessage({type: "MSG_RECONNECT_TOKEN", uuid: "..."})
|
|
114
|
Parent->>Server: fetch("/api/reconnect-token?uuid=...") (same-origin)
|
|
115
|
Server->>Server: Validate session still active, mint fresh JWT
|
|
116
|
Server-->>Parent: {jwt: "<new-token>"}
|
|
117
|
Parent->>Client: postMessage({type: "MSG_RECONNECT_TOKEN", jwt: "..."})
|
|
118
|
Client->>Client: Reconnect WebSocket with token
|
|
119
|
```
|
|
120
|
|
|
121
|
**Why this works**:
|
|
122
|
- [web_landing.html](file:///home/hc/gcd/p2j_repo/p2jtrunk/src/com/goldencode/p2j/main/web_landing.html) is **same-origin** with the FWD server → its `fetch()` is a first-party request, no CORS issues
|
|
123
|
- The postMessage bridge is **already in place** for parent↔iframe relay
|
|
124
|
- The spawned client validates the same JWT format as the initial handover
|
|
125
|
- Only one new message type (`MSG_RECONNECT_TOKEN`) in the bridge
|
|
126
|
|
|
127
|
### 4.3 Top-Level Resource Access (PDFs, Print, Downloads)
|
|
128
|
|
|
129
|
For resources that must open in a new browser tab (no iframe, no WebSocket):
|
|
130
|
|
|
131
|
```mermaid
|
|
132
|
sequenceDiagram
|
|
133
|
participant App as P2J App (in iframe)
|
|
134
|
participant Client as Embedded Jetty
|
|
135
|
participant Browser as New browser tab
|
|
136
|
|
|
137
|
App->>Client: WebSocket: "need URL for /reports/invoice.pdf"
|
|
138
|
Client->>Client: Mint resource-scoped JWT {res: "/reports/invoice.pdf", exp: +60s, jti: "..."}
|
|
139
|
Client-->>App: "https://client:port/reports/invoice.pdf?access=<jwt>"
|
|
140
|
App->>Browser: window.open(signedUrl)
|
|
141
|
Browser->>Client: GET /reports/invoice.pdf?access=<jwt>
|
|
142
|
Client->>Client: Validate JWT: signature ✓, expiry ✓, resource path ✓, jti not replayed ✓
|
|
143
|
Client-->>Browser: 200 OK (PDF content)
|
|
144
|
```
|
|
145
|
|
|
146
|
**Properties**:
|
|
147
|
- Token is **resource-scoped** — [res](file:///home/hc/gcd/p2j_repo/p2jtrunk/src/com/goldencode/p2j/main/WebDriverHandler.java#1513-1527) claim must match the requested path
|
|
148
|
- Token is **single-use** — `jti` tracked in a short-lived in-memory set
|
|
149
|
- Token is **short-lived** — 5–30 minutes (configurable), long enough for slow connections and large resources
|
|
150
|
- Same pattern as S3 pre-signed URLs
|
|
151
|
|
|
152
|
### 4.4 Loading FWD Server Resources from the Client Iframe
|
|
153
|
|
|
154
|
When the spawned client's HTML page needs `<script>`, `<img>`, `<link>` from the FWD server (cross-origin), there are two options. Both are fully specified; the choice is a deployment-time configuration.
|
|
155
|
|
|
156
|
#### Option A — Client-Side Reverse Proxy
|
|
157
|
|
|
158
|
```mermaid
|
|
159
|
sequenceDiagram
|
|
160
|
participant Browser as Browser (iframe)
|
|
161
|
participant Client as Spawned Client (Embedded Jetty)
|
|
162
|
participant Server as FWD Server
|
|
163
|
|
|
164
|
Note over Client: At startup, client knows FWD server URL (config override)
|
|
165
|
Browser->>Client: GET /server-res/p2j.screen.js
|
|
166
|
Client->>Client: Check local cache
|
|
167
|
alt Cache miss
|
|
168
|
Client->>Server: GET /webres/p2j.screen.js (server-to-server, no CORS)
|
|
169
|
Server-->>Client: 200 OK + resource body
|
|
170
|
Client->>Client: Store in cache
|
|
171
|
end
|
|
172
|
Client-->>Browser: 200 OK + resource + Cache-Control headers
|
|
173
|
```
|
|
174
|
|
|
175
|
**Implementation details**:
|
|
176
|
- The client's HTML emits `<script src="/server-res/p2j.screen.js">` — same-origin to the client
|
|
177
|
- The embedded Jetty has a **single handler** at `/server-res/*` that:
|
|
178
|
1. Maps the path: `/server-res/foo.js` → `<fwd-server-url>/webres/foo.js`
|
|
179
|
2. Fetches from the FWD server using a standard HTTP client (server-to-server, no browser involvement)
|
|
180
|
3. Caches the response in memory (with `ETag` / `Last-Modified` for revalidation)
|
|
181
|
4. Returns the resource to the browser with appropriate `Cache-Control` headers
|
|
182
|
- FWD server URL is known at spawn time (passed via config override `client:web:server_origin`)
|
|
183
|
- **Zero CORS complexity** — the browser never makes a cross-origin request
|
|
184
|
- **Zero token management** — the server-to-server fetch can use internal auth (e.g. the spawned client's UUID)
|
|
185
|
|
|
186
|
**Pros**: No CORS on FWD server, no tokens for resources, simplest browser-side model
|
|
187
|
**Cons**: Memory/CPU overhead on spawned client for caching + proxying; doubled network bandwidth for first load
|
|
188
|
|
|
189
|
---
|
|
190
|
|
|
191
|
#### Option B — Pre-Signed Server Resource URLs
|
|
192
|
|
|
193
|
```mermaid
|
|
194
|
sequenceDiagram
|
|
195
|
participant Client as Spawned Client (Embedded Jetty)
|
|
196
|
participant Server as FWD Server
|
|
197
|
participant Browser as Browser (iframe)
|
|
198
|
|
|
199
|
Note over Client: At startup or on HTML generation
|
|
200
|
Client->>Server: RPC: getSignedResourceUrls(["/webres/p2j.screen.js", "/webres/style.css", ...])
|
|
201
|
Server->>Server: For each resource, mint a resource-scoped JWT
|
|
202
|
Server-->>Client: [{path, signedUrl}, ...]
|
|
203
|
Client->>Client: Emit HTML with signed URLs
|
|
204
|
Client-->>Browser: HTML with signed src attributes
|
|
205
|
|
|
206
|
Browser->>Server: GET /webres/p2j.screen.js?access=<jwt>
|
|
207
|
Server->>Server: Validate JWT: signature ✓, exp ✓, resource path ✓
|
|
208
|
Server-->>Browser: 200 OK + resource + CORS headers + cache headers
|
|
209
|
```
|
|
210
|
|
|
211
|
**Implementation details**:
|
|
212
|
- The client's HTML emits `<script src="https://fwd-server:8443/webres/p2j.screen.js?access=<jwt>">` — cross-origin
|
|
213
|
- The **server-side resource JWT** has a longer TTL than the handover JWT (resource tokens can live minutes to hours since they grant read-only access to static content):
|
|
214
|
```json
|
|
215
|
{
|
|
216
|
"iss": "https://fwd-server:8443",
|
|
217
|
"aud": "https://fwd-server:8443",
|
|
218
|
"sub": "<client-uuid>",
|
|
219
|
"res": "/webres/p2j.screen.js",
|
|
220
|
"exp": "<+3600s>",
|
|
221
|
"jti": "<unique-id>"
|
|
222
|
}
|
|
223
|
```
|
|
224
|
- The FWD server validates the JWT on each resource request and adds CORS headers:
|
|
225
|
```
|
|
226
|
Access-Control-Allow-Origin: <client-origin>
|
|
227
|
Access-Control-Allow-Methods: GET
|
|
228
|
Access-Control-Allow-Credentials: false
|
|
229
|
Cache-Control: public, max-age=3600
|
|
230
|
```
|
|
231
|
- The `client-origin` is encoded in the JWT (`sub` maps to `client-uuid`, server looks up the client origin)
|
|
232
|
- Browser caches resources normally via `Cache-Control`; the signed URL stays valid for the TTL
|
|
233
|
|
|
234
|
**Pros**: No proxy overhead on spawned client, leverages browser caching natively, FWD server has full control over resource access
|
|
235
|
**Cons**: CORS headers needed on FWD server, per-resource JWT management, signed URLs in HTML source
|
|
236
|
|
|
237
|
---
|
|
238
|
|
|
239
|
#### Trade-off Comparison
|
|
240
|
|
|
241
|
| | Option A (Proxy) | Option B (Signed URLs) |
|
|
242
|
|---|---|---|
|
|
243
|
| CORS on FWD server | None | Yes (scoped to client origin) |
|
|
244
|
| Token management | None | Per-resource JWTs |
|
|
245
|
| Caching | Proxy cache on client | Browser cache (native) |
|
|
246
|
| Network hops | 2 (browser→client→server) | 1 (browser→server direct) |
|
|
247
|
| Load on spawned client | Proxy + cache memory | None |
|
|
248
|
| Complexity | Low (one proxy handler) | Medium (JWT signing + CORS) |
|
|
249
|
| Resource access control | Implicit (only proxy serves) | Explicit (JWT per resource) |
|
|
250
|
|
|
251
|
> [!TIP]
|
|
252
|
> Both options can coexist. A deployment could use **Option A** by default (simplest) and fall back to **Option B** for specific resources that need direct server access or where proxy overhead is unacceptable. This is controlled via configuration.
|
|
253
|
|
|
254
|
---
|
|
255
|
|
|
256
|
## 5. postMessage Bridge Changes
|
|
257
|
|
|
258
|
The existing bridge in [web_landing.html](file:///home/hc/gcd/p2j_repo/p2jtrunk/src/com/goldencode/p2j/main/web_landing.html) needs **one new message type**:
|
|
259
|
|
|
260
|
| Message Type | Direction | Purpose |
|
|
261
|
|---|---|---|
|
|
262
|
| `MSG_RECONNECT_TOKEN` | Client → Parent | Request a fresh JWT for WebSocket reconnection |
|
|
263
|
| `MSG_RECONNECT_TOKEN` | Parent → Client | Deliver the fresh JWT |
|
|
264
|
|
|
265
|
The parent handler:
|
|
266
|
```
|
|
267
|
case 'MSG_RECONNECT_TOKEN':
|
|
268
|
fetch('/api/reconnect-token?uuid=' + event.data.uuid)
|
|
269
|
.then(r => r.json())
|
|
270
|
.then(data => iframeWindow.postMessage({
|
|
271
|
msgId: event.data.msgId,
|
|
272
|
type: 'MSG_RECONNECT_TOKEN',
|
|
273
|
jwt: data.jwt
|
|
274
|
}, iframeOrigin));
|
|
275
|
break;
|
|
276
|
```
|
|
277
|
|
|
278
|
All existing message types (`MSG_SET_KEY_VALUE`, `MSG_READ_CLIPBOARD_VALUE`, etc.) continue to work unchanged — `postMessage` is inherently cross-origin capable.
|
|
279
|
|
|
280
|
---
|
|
281
|
|
|
282
|
## 6. CORS Configuration Summary
|
|
283
|
|
|
284
|
| Component | Option A (Proxy) | Option B (Signed URLs) |
|
|
285
|
|-----------|-----------------|------------------------|
|
|
286
|
| **FWD Server** | No CORS needed. Login is same-origin (srcdoc). Reconnect-token fetch is same-origin (from parent). Server resources are proxied server-to-server. | CORS needed on resource endpoints: `Access-Control-Allow-Origin: <client-origin>`, `GET` only. Login and reconnect-token remain same-origin. |
|
|
287
|
| **Spawned Client** | No CORS needed for resources (same-origin proxy). WebSocket `Origin` header validated server-side. | Same as Option A — no additional CORS. |
|
|
288
|
|
|
289
|
> [!NOTE]
|
|
290
|
> In both options, the initial iframe navigation (`GET /index.html?token=<jwt>`) is a simple GET by the browser — it does not trigger a CORS preflight. The embedded Jetty only needs to validate the JWT, not add CORS headers for this request.
|
|
291
|
|
|
292
|
---
|
|
293
|
|
|
294
|
## 7. Component Impact
|
|
295
|
|
|
296
|
### Server Side
|
|
297
|
|
|
298
|
| File | Change |
|
|
299
|
|------|--------|
|
|
300
|
| [WebDriverHandler.java](file:///home/hc/gcd/p2j_repo/p2jtrunk/src/com/goldencode/p2j/main/WebDriverHandler.java) | Mint JWT in [spawnWorker()](file:///home/hc/gcd/p2j_repo/p2jtrunk/src/com/goldencode/p2j/main/WebDriverHandler.java#775-1008) after spawn succeeds. Include `jwt` in [writeUrlInResponse()](file:///home/hc/gcd/p2j_repo/p2jtrunk/src/com/goldencode/p2j/main/WebDriverHandler.java#1562-1600) JSON. Add `/api/reconnect-token` endpoint. |
|
|
301
|
| [WebClientSpawner.java](file:///home/hc/gcd/p2j_repo/p2jtrunk/src/com/goldencode/p2j/main/WebClientSpawner.java) | [getRemoteUri()](file:///home/hc/gcd/p2j_repo/p2jtrunk/src/com/goldencode/p2j/main/WebClientSpawner.java#267-297): remove opaque `?token=` appending. |
|
|
302
|
| [ServerKeyStore.java](file:///home/hc/gcd/p2j_repo/p2jtrunk/src/com/goldencode/p2j/main/ServerKeyStore.java) | Add JWT signing key pair. Add `getJwtPublicKey()` for RPC distribution. |
|
|
303
|
| **[NEW] `JwtService.java`** | `mintToken(claims)` + `validateToken(jwt, audience)`. |
|
|
304
|
|
|
305
|
### Client Side (Spawned Process)
|
|
306
|
|
|
307
|
| File | Change |
|
|
308
|
|------|--------|
|
|
309
|
| `TemporaryClientTask.doWork()` in [WebClientSpawner.java](file:///home/hc/gcd/p2j_repo/p2jtrunk/src/com/goldencode/p2j/main/WebClientSpawner.java#L332-L471) | Receive JWT public key via [getServerData()](file:///home/hc/gcd/p2j_repo/p2jtrunk/src/com/goldencode/p2j/main/WebClientSpawner.java#315-324). Pass FWD server URL for resource proxy config. |
|
|
310
|
| **Embedded Jetty** | JWT validation filter on initial load. Signed-URL validation filter for resource GETs. **Option A**: `/server-res/*` reverse proxy handler with cache. **Option B**: update HTML template to emit signed URLs from server. WebSocket `Origin` validation. |
|
|
311
|
|
|
312
|
### Browser / JavaScript
|
|
313
|
|
|
314
|
| File | Change |
|
|
315
|
|------|--------|
|
|
316
|
| [fwd_sdk.js](file:///home/hc/gcd/p2j_repo/p2jtrunk/src/com/goldencode/p2j/main/fwd_sdk.js) | Extract `jwt` from response, append to iframe URL. |
|
|
317
|
| [web_landing.html](file:///home/hc/gcd/p2j_repo/p2jtrunk/src/com/goldencode/p2j/main/web_landing.html) | Add `MSG_RECONNECT_TOKEN` handler to postMessage bridge. |
|
|
318
|
| [p2j.screen.js](file:///home/hc/gcd/p2j_repo/p2jtrunk/src/com/goldencode/p2j/ui/client/gui/driver/web/res/p2j.screen.js) | Request signed URL via WebSocket before opening new tabs. Request reconnect token via postMessage on connection loss. |
|
|
319
|
|
|
320
|
### Configuration
|
|
321
|
|
|
322
|
| Key | Description | Default |
|
|
323
|
|-----|-------------|---------|
|
|
324
|
| `server:jwt:algorithm` | RS256, EdDSA, or HS256 | `RS256` |
|
|
325
|
| `server:jwt:ttl_seconds` | Handover token TTL | `60` |
|
|
326
|
| `server:jwt:resource_ttl_seconds` | Resource-scoped token TTL (top-level access) | `600` |
|
|
327
|
| `client:web:server_origin` | FWD server origin for proxy + validation | *(auto from spawn)* |
|
|
328
|
|
|
329
|
---
|
|
330
|
|
|
331
|
## 8. Implementation Roadmap
|
|
332
|
|
|
333
|
### Phase 1 — JWT Infrastructure
|
|
334
|
- Add key pair generation to `ServerKeyStore`
|
|
335
|
- Create `JwtService` with `mintToken()` / `validateToken()`
|
|
336
|
- Extend [getServerData()](file:///home/hc/gcd/p2j_repo/p2jtrunk/src/com/goldencode/p2j/main/WebClientSpawner.java#315-324) RPC to include public key
|
|
337
|
|
|
338
|
### Phase 2 — Spawn Handover
|
|
339
|
- Mint JWT in `WebDriverHandler.spawnWorker()` post-spawn
|
|
340
|
- Include `jwt` in response JSON; remove opaque `?token=`
|
|
341
|
- Update [fwd_sdk.js](file:///home/hc/gcd/p2j_repo/p2jtrunk/src/com/goldencode/p2j/main/fwd_sdk.js) to append JWT to iframe URL
|
|
342
|
- Add JWT validation filter to embedded Jetty initial load
|
|
343
|
|
|
344
|
### Phase 3 — Cookie-Free Session Support
|
|
345
|
- Add `MSG_RECONNECT_TOKEN` to postMessage bridge ([web_landing.html](file:///home/hc/gcd/p2j_repo/p2jtrunk/src/com/goldencode/p2j/main/web_landing.html))
|
|
346
|
- Add `/api/reconnect-token` endpoint to FWD server
|
|
347
|
- Update `p2j.screen.js` to request reconnect token via postMessage on WebSocket loss
|
|
348
|
- Add signed resource URL generation for top-level access (PDFs)
|
|
349
|
- Add `jti` replay detection (in-memory set with TTL)
|
|
350
|
|
|
351
|
### Phase 4 — Server Resource Access + Hardening
|
|
352
|
- **Option A**: Add `/server-res/*` reverse proxy handler to embedded Jetty (fetch + cache from FWD server). Update client HTML generation to use `/server-res/` prefix.
|
|
353
|
- **Option B**: Add `getSignedResourceUrls()` RPC endpoint to FWD server. Add JWT validation + CORS filter on server resource endpoints. Update client HTML generation to emit signed URLs.
|
|
354
|
- Add configuration to select Option A, Option B, or both
|
|
355
|
- Add WebSocket `Origin` header validation
|
|
356
|
- Key rotation support
|
|
357
|
- Integration testing with genuinely cross-domain setup (different IPs, no reverse proxy)
|
|
358
|
|
|
359
|
---
|
|
360
|
|
|
361
|
## 9. What Stays Unchanged
|
|
362
|
|
|
363
|
- User authentication (login form, SSO, external OIDC)
|
|
364
|
- OS process spawning via native tool
|
|
365
|
- [ClientDriver](file:///home/hc/gcd/p2j_repo/p2jtrunk/src/com/goldencode/p2j/main/ClientDriver.java#173-350) entry point
|
|
366
|
- WebSocket protocol between browser and spawned client
|
|
367
|
- [web_landing.html](file:///home/hc/gcd/p2j_repo/p2jtrunk/src/com/goldencode/p2j/main/web_landing.html) wrapper / iframe structure
|
|
368
|
- All existing postMessage bridge message types
|
|
369
|
- Session fork flow (adapted to use JWT, same pattern)
|
|
370
|
|
|
371
|
---
|
|
372
|
|
|
373
|
## 10. Security Summary
|
|
374
|
|
|
375
|
| Threat | Mitigation |
|
|
376
|
|--------|------------|
|
|
377
|
| JWT interception via URL | Short TTL (30–120s), single-use `jti`, audience-restricted |
|
|
378
|
| Token replay to different client | `aud` = specific client origin |
|
|
379
|
| Token replay same client | `jti` tracking with TTL-based expiry |
|
|
380
|
| Third-party cookie attacks | **No cookies used anywhere** |
|
|
381
|
| Cross-origin iframe attacks | postMessage origin validation (existing) |
|
|
382
|
| WebSocket hijacking | Server-side `Origin` header validation |
|
|
383
|
| Server resource leakage (Option A) | Proxied through authenticated client (no direct cross-origin access) |
|
|
384
|
| Server resource leakage (Option B) | Resource-scoped JWT, read-only, client-origin-restricted CORS |
|
|
385
|
| Signed URL leakage (PDFs) | Resource-scoped + short-lived + single-use |
|