You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
`bearer_opaque_token`) + an authorizer: resolves the external credential to a
100
+
JWT, scrubs the opaque credential, authorizes, forwards JWT-only;
101
+
-**internal verifier** — a `bearer_jwt_token` authenticator only: verifies the
102
+
JWT already in the prefix and rejects if absent/invalid;
103
+
-**trusting internal** — no `security` block: runs no auth-at-invoke and trusts
104
+
the upstream-stamped context (safe only behind a boundary).
78
105
79
106
Typical Picoforge shape:
80
107
81
108
```text
82
-
browser or API client
83
-
-> Picoforge webapp or direct gateway client
84
-
-> Picomesh gateway frontend
85
-
1. authenticate cookie/bearer credential to JWT or anonymous
86
-
2. authorize endpoint and arguments
87
-
3. invoke active local or remote service
88
-
-> backend service plugins
109
+
browser or API client (presents an OPAQUE cookie/bearer)
110
+
-> Picoforge webapp or direct client
111
+
-> a node configured as a BOUNDARY
112
+
core auth-at-invoke:
113
+
1. resolve cookie/bearer -> JWT (or anonymous), SCRUB the opaque
114
+
2. authorize endpoint + arguments
115
+
3. invoke active local or remote service with JWT-only context
116
+
-> backend service nodes (see only the internal JWT)
89
117
```
90
118
91
-
Backend services that do not run their own auth pipeline are safe only behind
92
-
this gateway trust boundary. Their RPC surface must not be reachable by
93
-
untrusted clients. If a backend frontend is exposed, it must run the same
94
-
pipeline or reject external traffic.
119
+
Internally only the JWT travels. The opaque cookie/bearer crosses only the
120
+
external edge into the boundary node and is scrubbed there; every hop after that
121
+
carries the verified JWT in `yheaders`, never the opaque secret. Backend nodes
122
+
without their own auth config are safe only behind a boundary; their RPC surface
123
+
must not be reachable by untrusted clients. The external edge stays opaque-only:
124
+
a boundary must reject a client-presented raw JWT, so `bearer_jwt_token` is an
125
+
internal-verification authenticator, never enabled on the external edge.
95
126
96
127
## Per-Request Pipeline
97
128
98
-
When a frontend has security configured, it gates service-call surfaces such as
99
-
`/_rpc`, `/_describe`, `/_describe_tree`, and any yrpc/msgpack service-call
100
-
entrypoint. HTML/static/debug routes may pass through only if they do not
101
-
invoke backend service methods.
129
+
When a node has security configured, the core gates every service invocation at
130
+
the invoke boundary — independent of frontend. The frontend has already folded
131
+
its wire headers into `yheaders` and called the method; the core auth step runs
132
+
before the method body. Surfaces gated this way include `/_rpc`, `/_describe`,
133
+
`/_describe_tree`, and any yrpc/msgpack service-call entrypoint. HTML/static/debug
134
+
routes may pass through only if they do not invoke backend service methods.
102
135
103
-
For each service call:
136
+
For each service invocation:
104
137
105
138
```text
106
-
request
107
-
-> parse endpoint path, args/kwargs, headers, cookies
108
-
-> run authenticator chain
139
+
invoke(method, yheaders, args) (yheaders already carries the credential)
140
+
-> run authenticator chain, reading the credential from yheaders
109
141
no credential shape matched: jwt = none
110
142
credential shape matched and valid: jwt = verified JWT
111
143
credential shape matched and invalid: fail 401
112
144
-> run authorizer(endpoint, args/kwargs, jwt)
113
145
allowed: continue
114
146
denied: fail 403
115
-
-> attach verified JWT/auth context to request headers/context
116
-
-> invoke resolved active service
147
+
-> replace the credential in yheaders with the verified JWT/auth context
148
+
-> run the resolved active service method
117
149
```
118
150
119
151
The failure rule is security-critical: if a credential of a known shape is
@@ -135,11 +167,14 @@ Authentication answers "who is this caller?" It does not decide whether the
135
167
caller may perform the requested operation.
136
168
137
169
Authenticators are configured by type. Each authenticator owns only one
138
-
credential shape.
170
+
credential shape, and reads it from `yheaders` — the frontend has already folded
171
+
its protocol's cookie / `Authorization` / forwarded headers into the bag, so an
172
+
authenticator never parses a wire format and behaves identically under any
173
+
frontend.
139
174
140
-
| Type | Credential read | How it yields a JWT |
175
+
| Type | Credential read (from `yheaders`) | How it yields a JWT |
141
176
|---|---|---|
142
-
|`session_cookie`| configured cookie or same-named forwarded header | calls configured `lookup` RPC, usually `session.session.lookup`, and verifies the returned JWT |
177
+
|`session_cookie`| configured cookie or same-named forwarded header | calls configured `lookup` RPC, usually `session.session.jwt`, and verifies the returned JWT |
143
178
|`bearer_jwt_token`|`Authorization: Bearer <jwt>`| verifies the JWT signature and expiry directly |
144
179
|`bearer_opaque_token`|`Authorization: Bearer <prefix>...`| calls configured `lookup` RPC, such as PAT or runner lookup, and verifies the returned JWT |
145
180
@@ -151,7 +186,7 @@ security:
151
186
- type: session_cookie
152
187
cookie: picomesh-sid
153
188
header: picomesh-sid
154
-
lookup: session.session.lookup
189
+
lookup: session.session.jwt
155
190
156
191
- type: bearer_jwt_token
157
192
header: Authorization
@@ -266,8 +301,8 @@ request can create races with parallel browser requests.
266
301
267
302
Some methods consume credentials as their arguments:
268
303
269
-
- `session.session.lookup(session_id)`exchanges an opaque session id for the
270
-
stored access JWT.
304
+
- `session.session.jwt(session_id)`exchanges an opaque session id for the
305
+
stored access JWT (the `session_cookie` authenticator's `lookup`).
271
306
- `token_issuer.token_issuer.refresh(refresh_token)`exchanges a refresh token
272
307
for a fresh access JWT and rotated refresh token.
273
308
- `personal_access_tokens.personal_access_tokens.lookup(token)`exchanges an
@@ -291,9 +326,10 @@ holder of the opaque secret can retrieve a JWT. Prefer one of these designs:
291
326
Authorization answers: "may this caller invoke this endpoint with these
292
327
arguments?"
293
328
294
-
Picomesh should support a frontend-configured authorizer. The yaapp-compatible
295
-
default is a policy authorizer keyed by endpoint name. Endpoints absent from
296
-
policy are denied by default.
329
+
Picomesh runs a config-selected authorizer at the core invoke boundary (per
330
+
node, from that node's `security` config). The yaapp-compatible default is a
331
+
policy authorizer keyed by endpoint name. Endpoints absent from policy are
332
+
denied by default.
297
333
298
334
Policy example:
299
335
@@ -396,11 +432,12 @@ The current implementation only partially matches this design.
396
432
397
433
Known mismatches:
398
434
399
-
- `src/picomesh/frontends/yhttp/authz.c`mixes the authenticator chain,
400
-
hardcoded credential-exchange calls, runner/PAT special cases, and policy
401
-
evaluation inside the yhttp frontend. The yaapp-compatible target is a
402
-
reusable frontend pipeline with configured authenticators and a configured
403
-
authorizer.
435
+
- `src/picomesh/frontends/yhttp/frontend.c`builds and runs the authenticator
436
+
chain + authorizer (`gateway_security`, the auth block in `route_json_rpc`)
437
+
inside the yhttp frontend, and legacy mutation routes resolve identity
438
+
themselves. The target moves that mechanism into the CORE invoke boundary,
439
+
config-selected per node, so every frontend gets it by carrying headers and
440
+
invoking — no per-frontend auth code.
404
441
- The yhttp authenticator code knows PAT and runner internals instead of using
405
442
generic `bearer_opaque_token` entries with configured `lookup` service paths.
406
443
- `src/picomesh/ysecurity/secret.c`is low-level helper code but currently
@@ -424,8 +461,10 @@ Known mismatches:
424
461
425
462
1. Keep `ysecurity` as a low-level library only. Remove policy decisions and
426
463
any invalid-JWT fallback behavior from it.
427
-
2. Introduce a reusable frontend auth pipeline shared by yhttp, yrpc/msgpack,
428
-
bridges, and future service-console frontends.
464
+
2. Move the auth pipeline into the CORE invoke boundary (not a frontend), so
465
+
yhttp, yrpc/msgpack, bridges, and future frontends share it by carrying
466
+
headers and invoking — no per-frontend auth code. Boundary vs internal vs
467
+
trusting is each node's own `security` config.
429
468
3. Implement configured authenticator modules:
430
469
`session_cookie`, `bearer_jwt_token`, and generic
431
470
`bearer_opaque_token`.
@@ -455,8 +494,8 @@ The target design provides:
455
494
- default deny for methods absent from policy;
456
495
- no credential downgrade on malformed tokens;
457
496
- clear separation of authn and authz;
458
-
- clear separation between frontend pipeline and mesh service plugins;
459
-
- one gateway/frontend policy decision before service invocation;
497
+
- clear separation between the core auth mechanism and the mesh service plugins;
498
+
- one core policy decision at the invoke boundary before service invocation;
460
499
- short-lived JWT claims with refresh/login updates;
461
500
- browser sessions that expose only opaque HttpOnly cookies;
462
501
- support for API clients using bearer JWTs or bearer opaque tokens;
0 commit comments