@eduwork/dsh-oidc 0.2.3 → 0.3.0-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/CHANGELOG.en.md +6 -38
  2. package/CHANGELOG.md +7 -39
  3. package/CODE_OF_CONDUCT.en.md +1 -1
  4. package/CODE_OF_CONDUCT.md +1 -1
  5. package/CONTRIBUTING.en.md +1 -1
  6. package/CONTRIBUTING.md +1 -1
  7. package/README.md +21 -130
  8. package/README_EN.md +36 -0
  9. package/SECURITY.en.md +1 -1
  10. package/SECURITY.md +1 -1
  11. package/SUPPORT.en.md +1 -1
  12. package/SUPPORT.md +1 -1
  13. package/docs/EDUWORK-MIGRATION.en.md +3 -31
  14. package/docs/EDUWORK-MIGRATION.md +3 -31
  15. package/docs/account-extensions.en.md +6 -6
  16. package/docs/account-extensions.md +3 -3
  17. package/docs/architecture.en.md +12 -117
  18. package/docs/architecture.md +12 -118
  19. package/docs/compatibility.en.md +6 -62
  20. package/docs/compatibility.md +6 -62
  21. package/docs/decisions/0001-one-plugin-composition-boundary.en.md +2 -2
  22. package/docs/decisions/0001-one-plugin-composition-boundary.md +2 -2
  23. package/docs/desktop-host.en.md +54 -55
  24. package/docs/desktop-host.md +63 -64
  25. package/docs/development.en.md +44 -44
  26. package/docs/development.md +6 -6
  27. package/docs/dsh-integration.en.md +13 -48
  28. package/docs/dsh-integration.md +14 -49
  29. package/docs/ecnu-reference.en.md +4 -61
  30. package/docs/ecnu-reference.md +4 -61
  31. package/docs/enterprise-profile.en.md +10 -31
  32. package/docs/enterprise-profile.md +11 -32
  33. package/docs/gateway-auth/README.md +168 -0
  34. package/docs/gateway-auth/README_EN.md +70 -0
  35. package/docs/gateway-auth/experimental-oidc-llm.en.md +53 -0
  36. package/docs/gateway-auth/experimental-oidc-llm.md +53 -0
  37. package/docs/gateway-auth/litellm-setup.en.md +114 -0
  38. package/docs/gateway-auth/litellm-setup.md +114 -0
  39. package/docs/gateway-auth/oidc-llm-draft.en.md +141 -0
  40. package/docs/gateway-auth/oidc-llm-draft.md +141 -0
  41. package/docs/getting-started.en.md +17 -122
  42. package/docs/getting-started.md +16 -225
  43. package/docs/key-binding-protocol.en.md +10 -220
  44. package/docs/key-binding-protocol.md +10 -220
  45. package/docs/oidc-interoperability.en.md +1 -1
  46. package/docs/oidc-interoperability.md +1 -1
  47. package/docs/open-integration.en.md +11 -0
  48. package/docs/open-integration.md +11 -0
  49. package/docs/public-resource-protocol.en.md +8 -44
  50. package/docs/public-resource-protocol.md +8 -43
  51. package/docs/release-checklist.en.md +7 -61
  52. package/docs/release-checklist.md +7 -61
  53. package/docs/security-model.en.md +10 -132
  54. package/docs/security-model.md +10 -132
  55. package/docs/server-integration-contract.en.md +12 -811
  56. package/docs/server-integration-contract.md +12 -823
  57. package/examples/ecnu.enterprise-profile.example.json +14 -19
  58. package/examples/enterprise-profile.example.json +73 -78
  59. package/examples/identity-only.example.json +1 -1
  60. package/examples/litellm.enterprise-profile.example.json +9 -0
  61. package/examples/oidc-llm.enterprise-profile.example.json +12 -0
  62. package/examples/resources-discovery.example.json +6 -9
  63. package/lib/callback-page.js +5 -5
  64. package/lib/client.js +31 -48
  65. package/lib/desktop-oidc.js +7 -11
  66. package/lib/gateway-backend.js +443 -0
  67. package/lib/gateway-protocol.js +6 -0
  68. package/lib/index.js +14 -15
  69. package/lib/litellm-protocol.js +117 -0
  70. package/lib/model-resource-transport.js +19 -0
  71. package/lib/oidc-llm-protocol.js +88 -0
  72. package/lib/oidc.js +37 -323
  73. package/lib/profile.js +34 -40
  74. package/lib/provider/authorization-scope.js +46 -0
  75. package/lib/provider/index.js +11 -2
  76. package/lib/provider/transforms.js +0 -1
  77. package/lib/typert-schemas.js +1 -1
  78. package/lib/typert.host.js +38 -38
  79. package/lib/typert.remote-client.js +37 -37
  80. package/package.json +7 -6
  81. package/protocol/openapi.yaml +33 -240
  82. package/protocol/resources.openapi.yaml +8 -9
  83. package/schema/enterprise-profile.v1alpha1.schema.json +595 -193
  84. package/README.en.md +0 -186
@@ -1,822 +1,23 @@
1
- # EduWork identity and resource integration (RFC EW-IDENTITY-1)
2
-
3
- > Scope update in .20260910.6: quota and activity heartbeat are institution-only extensions. The public package never requests /quota and its resources RPC contains only model metadata. No capability string or configuration flag enables quota in the public package. ECNU retains its existing server wire in a separate package.
4
-
5
- > Current candidate supports two modes: identity-only OIDC can omit all resource interfaces and use personal API keys. The complete organization-resource mode below adds managed credentials and models; see [Public resource protocol](public-resource-protocol.en.md). The mandatory requirements below apply to complete integration, not identity-only login.
1
+ # Server integration contract
6
2
 
7
3
  [简体中文](server-integration-contract.md) | **English**
8
4
 
9
- This document is written for identity-platform, model-platform, and integration-test teams. It defines, in API-reference form, the server capabilities required by `dsh-oidc`.
10
-
11
- Status: implemented client wire specification, not an IETF standard. Source code and HTTP contract tests determine current behavior. Section 10 provides curl examples; section 11 contains **unimplemented optional proposals**. Every client is a local, single-user Runtime; this is not a shared Web-login architecture.
12
-
13
- | Capability | Required interfaces | Optionality and implemented behavior |
14
- | --- | --- | --- |
15
- | Identity | Discovery, Authorization, Token, JWKS, UserInfo | No resource API needed; personal model keys remain available |
16
- | Managed models | Identity, Bootstrap, Key lifecycle, Chat Completions | Enabled by keyBinding/provider together; provisioning requires user consent |
17
- | Model discovery | GET /models | Required only for modelSource=discovery |
18
- | Token refresh/revocation | Refresh grant / Discovery revocation_endpoint | Optional; missing refresh means signing in again after expiry |
19
- | Remote Profile discovery, remote model-key logout | Section 11 proposals | Not automatically requested by the public client |
20
-
21
- Optional interfaces still require valid fields when implemented. Quota belongs to the ECNU institution extension and is not a public integration requirement. An identity-only integration must not be represented as complete model integration. Trusted local configuration owns branding, issuer, service origins and model policy. Bootstrap does not overwrite configuration or load executable extensions. No school-specific service is required.
22
-
23
- Servers must implement the complete fields and constraints in this document and OpenAPI. Client tolerance for some legacy missing values does not waive server security requirements.
24
-
25
- “MUST”, “MUST NOT”, “SHOULD”, and “MAY” are normative. The OIDC interfaces follow OpenID Connect and OAuth 2.0; Key Binding is the fixed protocol defined by this project; the model gateway uses an OpenAI Chat Completions-compatible interface.
26
-
27
- ## 1. Integration scope
28
-
29
- A complete **institutional enterprise-model integration service** MUST provide all three interface groups:
30
-
31
- | Interface group | Purpose | Enterprise Profile configuration | Requirement |
32
- | --- | --- | --- | --- |
33
- | OIDC Provider | Login, user identity, and OIDC Access Tokens | `oidc.issuer`, `oidc.clientId`, `oidc.scopes` | Complete implementation required |
34
- | Key Binding | Bind an authenticated user to a revocable model runtime credential | `keyBinding.baseURL` | All four operations required |
35
- | Model gateway | Invoke enterprise models with the bound runtime credential | `provider.baseURL`, `provider.id`, `provider.models` | Chat Completions required |
36
-
37
- “One service” means one complete product-delivery, versioning, security, and operational boundary. It does not require every endpoint to share a hostname, gateway, or process. An institution may internally separate its identity platform, user center, and model platform, but it cannot deliver only one part and claim a complete `dsh-oidc` integration.
38
-
39
- The following arrangements do not conform:
40
-
41
- - OIDC alone, followed by distribution of one global model API key to all users;
42
- - Key Binding that trusts an employee number, name, or subject supplied by the client;
43
- - a model URL and catalog without user authorization and credential lifecycle management;
44
- - a Key Binding service missing any of `bootstrap`, `provision`, `resolve`, or `renew`.
45
-
46
- ## 2. Addresses and client registration
47
-
48
- ### 2.1 Three service entry points
49
-
50
- An Enterprise Profile declares the three entry points separately:
51
-
52
- ```json
53
- {
54
- "oidc": {
55
- "issuer": "https://id.example.edu/oidc",
56
- "clientId": "dsh-web-public-client",
57
- "scopes": ["openid", "profile", "offline_access"]
58
- },
59
- "keyBinding": {
60
- "baseURL": "https://ai.example.edu/api/worker/v1"
61
- },
62
- "provider": {
63
- "id": "example-ai",
64
- "baseURL": "https://ai.example.edu/open/api/v1",
65
- "adapter": "openai-compatible",
66
- "models": [
67
- { "id": "example-model", "input": ["text"] }
68
- ]
69
- }
70
- }
71
- ```
72
-
73
- In production, `issuer`, `keyBinding.baseURL`, and `provider.baseURL` MUST use HTTPS. URLs MUST NOT contain credentials or fragments, and base URLs MUST NOT depend on query parameters to alter routing behavior.
74
-
75
- ### 2.2 OIDC Public Client
76
-
77
- The identity platform MUST register a Public Client without a Client Secret and enable:
78
-
79
- - Authorization Code Flow;
80
- - PKCE `S256`;
81
- - the `openid` and `profile` scopes;
82
- - `offline_access` and the corresponding authorization policy when Refresh Tokens are required.
83
-
84
- Desktop callbacks use an available random loopback port by default:
85
-
86
- ```text
87
- http://127.0.0.1:<actual-port>/oauth/callback
88
- ```
89
-
90
- Constraints:
91
-
92
- - the host is fixed to `127.0.0.1`, not `localhost`, a LAN address, or a public hostname;
93
- - the path is fixed to `/oauth/callback`;
94
- - Desktop uses an available loopback port chosen for each attempt (RFC 8252). The authorization server MUST support this native-client registration pattern; `3080` below is only an example. `desktop.callbackPort` optionally fixes the port and fails clearly on conflicts. Web validation uses its actual WebServer port;
95
- - this is the browser's return address to the user's local DSH process, not an institutional server address;
96
- - the current implementation is a local single-user client and does not support a shared public-Web callback.
97
-
98
- ## 3. API summary
99
-
100
- | ID | Method | Address | Authentication | Required |
101
- | --- | --- | --- | --- | --- |
102
- | OIDC-01 | `GET` | `{issuer}/.well-known/openid-configuration` | None | Yes |
103
- | OIDC-02 | `GET` | Discovery `authorization_endpoint` | User login session | Yes |
104
- | OIDC-03 | `POST` | Discovery `token_endpoint` | Authorization Code + PKCE | Yes |
105
- | OIDC-04 | `GET` | Discovery `jwks_uri` | None | Yes |
106
- | OIDC-05 | `GET` | Discovery `userinfo_endpoint` | OIDC Bearer Access Token | Yes |
107
- | OIDC-06 | `POST` | Discovery `revocation_endpoint` | OIDC Token | Recommended |
108
- | KEY-01 | `GET` | `{keyBinding.baseURL}/bootstrap` | OIDC Bearer Access Token | Yes |
109
- | KEY-02 | `POST` | `{keyBinding.baseURL}/runtime-credential/provision` | OIDC Bearer Access Token | Yes |
110
- | KEY-03 | `POST` | `{keyBinding.baseURL}/runtime-credential/resolve` | OIDC Bearer Access Token | Yes |
111
- | KEY-04 | `POST` | `{keyBinding.baseURL}/runtime-credential/renew` | OIDC Bearer Access Token | Yes |
112
- | MODEL-01 | `POST` | `{provider.baseURL}/chat/completions` | Runtime API Key | Yes |
113
- | MODEL-02 | `GET` | `{provider.baseURL}/models` | Runtime API Key | If `modelSource=discovery` |
114
-
115
- The loopback callback and local `oidcAccounts.*` RPC methods belong to the client, not the enterprise server.
116
-
117
- ## 4. OIDC interface group
118
-
119
- ### OIDC-01: Discovery
120
-
121
- Reads OIDC Provider metadata.
122
-
123
- #### Request
124
-
125
- ```http
126
- GET /oidc/.well-known/openid-configuration HTTP/1.1
127
- Host: id.example.edu
128
- Accept: application/json
129
- ```
130
-
131
- When the issuer itself has a path, the Discovery address is:
132
-
133
- ```text
134
- {issuer-without-trailing-slash}/.well-known/openid-configuration
135
- ```
136
-
137
- #### Success response
138
-
139
- ```http
140
- HTTP/1.1 200 OK
141
- Content-Type: application/json
142
-
143
- {
144
- "issuer": "https://id.example.edu/oidc",
145
- "authorization_endpoint": "https://id.example.edu/oidc/authorize",
146
- "token_endpoint": "https://id.example.edu/oidc/token",
147
- "jwks_uri": "https://id.example.edu/oidc/jwks",
148
- "userinfo_endpoint": "https://id.example.edu/oidc/userinfo",
149
- "revocation_endpoint": "https://id.example.edu/oidc/revoke",
150
- "code_challenge_methods_supported": ["S256"],
151
- "id_token_signing_alg_values_supported": ["RS256"]
152
- }
153
- ```
154
-
155
- #### Field requirements
156
-
157
- | Field | Required | Requirement |
158
- | --- | --- | --- |
159
- | `issuer` | Yes | Exact string match with Enterprise Profile `oidc.issuer` |
160
- | `authorization_endpoint` | Yes | Absolute HTTPS URL |
161
- | `token_endpoint` | Yes | Absolute HTTPS URL |
162
- | `jwks_uri` | Yes | Absolute HTTPS URL |
163
- | `userinfo_endpoint` | Yes | Required by this interoperability profile |
164
- | `revocation_endpoint` | No | When present, the client attempts revocation during logout |
165
- | `code_challenge_methods_supported` | Yes | MUST contain `S256` |
166
- | `id_token_signing_alg_values_supported` | Should | SHOULD contain `RS256`; the actual ID Token MUST use RS256 |
167
-
168
- Discovered endpoints may use different HTTPS origins. Development HTTP is accepted only for the single explicitly configured development origin or a loopback origin.
169
-
170
- ### OIDC-02: Authorization
171
-
172
- The browser starts Authorization Code Flow with PKCE.
173
-
174
- #### Request
175
-
176
- ```http
177
- GET /oidc/authorize?
178
- response_type=code&
179
- client_id=dsh-web-public-client&
180
- redirect_uri=http%3A%2F%2F127.0.0.1%3A3080%2Foauth%2Fcallback&
181
- scope=openid%20profile%20offline_access&
182
- state=<random-state>&
183
- nonce=<random-nonce>&
184
- code_challenge=<base64url-sha256>&
185
- code_challenge_method=S256 HTTP/1.1
186
- Host: id.example.edu
187
- ```
188
-
189
- #### Query parameters
190
-
191
- | Parameter | Requirement |
192
- | --- | --- |
193
- | `response_type` | Fixed to `code` |
194
- | `client_id` | Equal to Profile `oidc.clientId` |
195
- | `redirect_uri` | Exact match with the registered loopback callback |
196
- | `scope` | At least `openid profile` |
197
- | `state` | Generated by the client and returned unchanged |
198
- | `nonce` | Generated by the client and included in the corresponding ID Token |
199
- | `code_challenge` | PKCE challenge |
200
- | `code_challenge_method` | Fixed to `S256` |
201
-
202
- #### Success redirect
203
-
204
- ```http
205
- HTTP/1.1 302 Found
206
- Location: http://127.0.0.1:3080/oauth/callback?code=<authorization-code>&state=<original-state>
207
- ```
208
-
209
- An RFC 9207 `iss` response parameter MAY also be returned. When present, it MUST equal the Profile issuer.
210
-
211
- #### Error redirect
212
-
213
- ```text
214
- http://127.0.0.1:3080/oauth/callback?error=access_denied&error_description=...&state=<original-state>
215
- ```
216
-
217
- The server MUST NOT omit `state`. An Authorization Code MUST be single-use, short-lived, and bound to the client, redirect URI, and PKCE challenge.
218
-
219
- ### OIDC-03: Token
220
-
221
- The client exchanges an Authorization Code and PKCE verifier for tokens. The Public Client does not send a Client Secret.
222
-
223
- #### Authorization Code request
224
-
225
- ```http
226
- POST /oidc/token HTTP/1.1
227
- Host: id.example.edu
228
- Content-Type: application/x-www-form-urlencoded
229
- Accept: application/json
230
-
231
- grant_type=authorization_code&
232
- client_id=dsh-web-public-client&
233
- code=<authorization-code>&
234
- redirect_uri=http%3A%2F%2F127.0.0.1%3A3080%2Foauth%2Fcallback&
235
- code_verifier=<pkce-verifier>
236
- ```
237
-
238
- #### Success response
239
-
240
- ```http
241
- HTTP/1.1 200 OK
242
- Content-Type: application/json
243
- Cache-Control: no-store
244
-
245
- {
246
- "access_token": "<oidc-access-token>",
247
- "token_type": "Bearer",
248
- "expires_in": 3600,
249
- "id_token": "<signed-id-token>",
250
- "refresh_token": "<optional-refresh-token>",
251
- "scope": "openid profile offline_access"
252
- }
253
- ```
254
-
255
- | Field | Required | Requirement |
256
- | --- | --- | --- |
257
- | `access_token` | Yes | Non-empty; used for UserInfo and Key Binding |
258
- | `token_type` | Yes | `Bearer`, case-insensitive |
259
- | `expires_in` | Should | Positive seconds; the client uses 3600 seconds when absent |
260
- | `id_token` | Yes | RS256-signed JWT |
261
- | `refresh_token` | No | Recommended when `offline_access` is enabled |
262
- | `scope` | No | Actually granted scopes |
263
-
264
- The ID Token MUST contain at least:
265
-
266
- | Claim | Requirement |
267
- | --- | --- |
268
- | `iss` | Equal to Profile `oidc.issuer` |
269
- | `aud` | Contains `oidc.clientId`; with multiple audiences, `azp` MUST equal that client ID |
270
- | `sub` | Non-empty, stable user identifier |
271
- | `iat`, `exp` | Valid NumericDate values |
272
- | `nonce` | Exact match with the Authorization request |
273
- | `nbf` | Optional; valid when present |
274
- | `at_hash` | Optional; MUST match the Access Token when present |
275
-
276
- The JWT header MUST use `alg=RS256` and contain a non-empty `kid`.
277
-
278
- #### Refresh Token request
279
-
280
- ```http
281
- POST /oidc/token HTTP/1.1
282
- Content-Type: application/x-www-form-urlencoded
283
- Accept: application/json
284
-
285
- grant_type=refresh_token&
286
- client_id=dsh-web-public-client&
287
- refresh_token=<refresh-token>
288
- ```
289
-
290
- The response MUST contain at least a new `access_token`, `token_type=Bearer`, and valid `expires_in`. When Refresh Token rotation is used, it SHOULD also return a new `refresh_token`. An `invalid_grant` response causes the client to clear its local OIDC session and model runtime credential.
291
-
292
- #### Error response
293
-
294
- Token errors follow OAuth 2.0:
295
-
296
- ```http
297
- HTTP/1.1 400 Bad Request
298
- Content-Type: application/json
299
- Cache-Control: no-store
300
-
301
- {
302
- "error": "invalid_grant",
303
- "error_description": "Authorization code is invalid or expired"
304
- }
305
- ```
306
-
307
- ### OIDC-04: JWKS
308
-
309
- Publishes the public signing keys needed to verify ID Tokens.
310
-
311
- #### Request
312
-
313
- ```http
314
- GET /oidc/jwks HTTP/1.1
315
- Accept: application/json
316
- ```
317
-
318
- #### Success response
319
-
320
- ```json
321
- {
322
- "keys": [
323
- {
324
- "kty": "RSA",
325
- "kid": "signing-key-2026-01",
326
- "use": "sig",
327
- "alg": "RS256",
328
- "n": "<base64url-modulus>",
329
- "e": "AQAB"
330
- }
331
- ]
332
- }
333
- ```
334
-
335
- The `kid` used by an ID Token MUST identify exactly one RSA signing key. During rotation, the old public key SHOULD remain available until every valid Token signed by it has expired.
336
-
337
- ### OIDC-05: UserInfo
338
-
339
- Returns the standard user identity represented by the current Access Token.
340
-
341
- #### Request
342
-
343
- ```http
344
- GET /oidc/userinfo HTTP/1.1
345
- Authorization: Bearer <oidc-access-token>
346
- Accept: application/json
347
- ```
348
-
349
- #### Success response
350
-
351
- ```http
352
- HTTP/1.1 200 OK
353
- Content-Type: application/json
354
-
355
- {
356
- "sub": "user-12345",
357
- "name": "Example User"
358
- }
359
- ```
360
-
361
- | Field | Required | Purpose |
362
- | --- | --- | --- |
363
- | `sub` | Yes | Exact match with ID Token `sub` |
364
- | `name` | Should | Human-readable UI label; the client falls back to `sub` when absent or empty |
365
-
366
- The server SHOULD provide the standard `name` claim directly. `dsh-oidc` does not call private institutional directory APIs or support JSONPath name mapping. Other standard claims may be returned, but they cannot replace `sub` for binding.
367
-
368
- ### OIDC-06: Revocation (recommended)
369
-
370
- When Discovery publishes a `revocation_endpoint`, the client submits its Refresh Token during logout, or its Access Token when no Refresh Token exists.
371
-
372
- ```http
373
- POST /oidc/revoke HTTP/1.1
374
- Content-Type: application/x-www-form-urlencoded
375
-
376
- token=<refresh-or-access-token>&client_id=dsh-web-public-client
377
- ```
378
-
379
- Remote revocation is best-effort during logout. The local OIDC session and runtime credential are cleared whether or not the remote call succeeds. Remote revocation of the model runtime API key remains the responsibility of Key Binding and model-platform lifecycle policy.
380
-
381
- ## 5. Key Binding interface group
382
-
383
- ### 5.1 Common conventions
384
-
385
- The relative paths, HTTP methods, and wire field names of all four operations are fixed and cannot be configured in the Profile.
386
-
387
- Every request MUST include:
388
-
389
- ```http
390
- Authorization: Bearer <oidc-access-token>
391
- Accept: application/json
392
- ```
393
-
394
- The server MUST validate token issuer, audience, expiry, not-before, scopes, subject, account state, and institutional authorization. The subject MUST be derived from the validated Token. A subject, employee number, or name supplied in the request body MUST NOT be trusted as identity.
395
-
396
- Recommended scopes:
397
-
398
- | Interface | Scope |
399
- | --- | --- |
400
- | KEY-01 Bootstrap | `worker.bootstrap.read` |
401
- | KEY-02 Provision | `worker.credential.provision` |
402
- | KEY-03 Resolve | `worker.credential.read` |
403
- | KEY-04 Renew | `worker.credential.renew` |
404
-
405
- An institution may use different scope names, but the actual names MUST be included in Profile `oidc.scopes` and enforced by the server.
406
-
407
- ### KEY-01: Bootstrap
408
-
409
- Reads the Provider, server capabilities, user entitlement, and runtime-credential state. This operation MUST NOT return a model API key.
410
-
411
- #### Request
412
-
413
- ```http
414
- GET /api/worker/v1/bootstrap HTTP/1.1
415
- Host: ai.example.edu
416
- Authorization: Bearer <oidc-access-token>
417
- Accept: application/json
418
- ```
419
-
420
- #### Success response
421
-
422
- ```http
423
- HTTP/1.1 200 OK
424
- Content-Type: application/json
425
- Cache-Control: no-store
426
-
427
- {
428
- "protocol_version": "worker-user-center/v1",
429
- "provider": {
430
- "id": "example-ai"
431
- },
432
- "capabilities": [
433
- "worker.bootstrap.read",
434
- "worker.credential.provision",
435
- "worker.credential.read",
436
- "worker.credential.renew"
437
- ],
438
- "runtime_credential": {
439
- "status": "missing",
440
- "provisioning": {
441
- "allowed": true
442
- }
443
- }
444
- }
445
- ```
446
-
447
- #### Response fields
448
-
449
- | Field | Required | Description |
450
- | --- | --- | --- |
451
- | `protocol_version` | Optional for legacy; new servers SHOULD provide it | Accepted aliases with identical wire: `worker.user-center.v1`, `worker-user-center/v1`, `eduwork-resources/v1`; unknown versions are rejected |
452
- | `provider.id` | Yes | Exact match with Profile `provider.id` |
453
- | `capabilities` | Yes | String array with at most 256 entries |
454
- | `runtime_credential.status` | Yes | Current credential state |
455
- | `runtime_credential.api_key_id` | Conditional | Recommended for an existing credential; required by Renew |
456
- | `runtime_credential.expires_at` | No | RFC 3339 timestamp |
457
- | `runtime_credential.provisioning.allowed` | No | Whether credential creation is allowed |
458
- | `runtime_credential.provisioning.reason` | No | Non-sensitive user-facing reason, at most 500 characters |
459
- | `subject` | No | Management-plane display only; cannot override OIDC identity |
460
-
461
- `runtime_credential.status` values:
462
-
463
- | State | Client behavior |
464
- | --- | --- |
465
- | `missing` | Wait for explicit user consent, then call Provision |
466
- | `active` | Call Resolve |
467
- | `expiring` | Call Renew when `api_key_id` exists, otherwise Resolve |
468
- | `expired` | Call Renew when `api_key_id` exists, otherwise Resolve |
469
- | `suspended` | Clear the local runtime credential and do not invoke a model |
470
- | `unavailable` | Clear the local runtime credential and show service unavailable |
471
-
472
- ### KEY-02: Provision
473
-
474
- Creates or binds a runtime credential for the authenticated and authorized subject. Completing the OIDC callback alone does not call this operation; the user must explicitly consent in the UI.
475
-
476
- #### Request
477
-
478
- ```http
479
- POST /api/worker/v1/runtime-credential/provision HTTP/1.1
480
- Authorization: Bearer <oidc-access-token>
481
- Content-Type: application/json
482
- Accept: application/json
483
- Idempotency-Key: 2b9b6c80-7758-4d3f-94c6-b72d91ca8c53
484
-
485
- {
486
- "provider_id": "example-ai"
487
- }
488
- ```
489
-
490
- | Field/header | Required | Requirement |
491
- | --- | --- | --- |
492
- | `Idempotency-Key` | Yes | One UUID for one logical create operation |
493
- | `provider_id` | Yes | Exact match with Profile `provider.id` |
494
-
495
- The idempotency scope MUST include authenticated subject, Provider, and Idempotency-Key. A retry with the same key and payload MUST produce the same logical result. Reusing the same key with a different payload MUST return `409 Conflict`.
496
-
497
- #### Success response
498
-
499
- See [Common runtime-credential response](#55-common-runtime-credential-response). A first creation MAY return `201 Created`; an existing or completed binding MAY return `200 OK`.
500
-
501
- ### KEY-03: Resolve
502
-
503
- Resolves the active runtime credential already bound to the current subject. Resolve MUST NOT create a new entitlement.
504
-
505
- #### Request
506
-
507
- ```http
508
- POST /api/worker/v1/runtime-credential/resolve HTTP/1.1
509
- Authorization: Bearer <oidc-access-token>
510
- Content-Type: application/json
511
- Accept: application/json
512
-
513
- {
514
- "provider_id": "example-ai",
515
- "api_key_id": "key-123"
516
- }
517
- ```
518
-
519
- | Field | Required | Description |
520
- | --- | --- | --- |
521
- | `provider_id` | Yes | Provider ID declared in the Profile |
522
- | `api_key_id` | No | Known credential ID returned by Bootstrap |
523
-
524
- Return `404` when no credential can be resolved. The server may rotate opaque secret material while preserving a logical `api_key_id`, but MUST NOT return a credential owned by another subject or Provider.
525
-
526
- ### KEY-04: Renew
527
-
528
- Rotates an expiring or expired runtime credential.
529
-
530
- #### Request
531
-
532
- ```http
533
- POST /api/worker/v1/runtime-credential/renew HTTP/1.1
534
- Authorization: Bearer <oidc-access-token>
535
- Content-Type: application/json
536
- Accept: application/json
537
- Idempotency-Key: 2f94dc48-201a-475e-a61a-69d7880e1186
538
-
539
- {
540
- "provider_id": "example-ai",
541
- "api_key_id": "key-123"
542
- }
543
- ```
544
-
545
- `provider_id`, `api_key_id`, and `Idempotency-Key` are all required. The same idempotency rules as Provision apply. After the new credential becomes active, the server SHOULD invalidate the old secret as soon as safely possible and record any necessary overlap window.
546
-
547
- ### 5.5 Common runtime-credential response
548
-
549
- Provision, Resolve, and Renew use the same success structure:
550
-
551
- ```http
552
- HTTP/1.1 200 OK
553
- Content-Type: application/json
554
- Cache-Control: no-store
555
- Pragma: no-cache
556
-
557
- {
558
- "provider_id": "example-ai",
559
- "api_key": "<secret-runtime-api-key>",
560
- "api_key_id": "key-123",
561
- "status": "active",
562
- "expires_at": "2026-09-01T00:00:00Z"
563
- }
564
- ```
565
-
566
- | Field | Required | Requirement |
567
- | --- | --- | --- |
568
- | `provider_id` | Yes | Exact match with the request and Profile |
569
- | `api_key` | Yes | Non-empty, at most 16 KiB; used only for the model gateway |
570
- | `api_key_id` | No | Recommended for lifecycle management and audit correlation |
571
- | `status` | Yes | Fixed to `active` |
572
- | `expires_at` | No | RFC 3339 timestamp |
573
-
574
- A response containing `api_key` MUST use `Cache-Control: no-store` and SHOULD use `Pragma: no-cache`. Reverse proxies, WAFs, APM agents, application logs, and audit logs MUST redact the Authorization header and `api_key`.
575
-
576
- ### 5.6 Key Binding error response
577
-
578
- Errors SHOULD use RFC 9457 `application/problem+json`:
579
-
580
- ```http
581
- HTTP/1.1 403 Forbidden
582
- Content-Type: application/problem+json
583
-
584
- {
585
- "type": "https://ai.example.edu/problems/model-entitlement-denied",
586
- "title": "Model entitlement denied",
587
- "status": 403,
588
- "detail": "The current account cannot use this provider",
589
- "code": "model_entitlement_denied"
590
- }
591
- ```
592
-
593
- | HTTP status | Meaning |
594
- | --- | --- |
595
- | `400` | Invalid request structure or Provider ID |
596
- | `401` | Missing, invalid, expired, or wrong-audience Access Token; SHOULD include `WWW-Authenticate` |
597
- | `403` | Scope, institutional membership, model entitlement, account state, or provisioning policy denial |
598
- | `404` | No resolvable or renewable credential |
599
- | `409` | Idempotency or credential-lifecycle conflict |
600
- | `429` | Rate limit; SHOULD include `Retry-After` |
601
- | `5xx` | Server failure; response MUST NOT contain a Token or API Key |
602
-
603
- `code` SHOULD remain stable for UI and log classification. `detail` may vary but MUST NOT disclose sensitive information.
604
-
605
- The machine-readable Key Binding contract is [`protocol/openapi.yaml`](../protocol/openapi.yaml).
606
-
607
- ## 6. Model-gateway interface group
608
-
609
- ### MODEL-01: Chat Completions
610
-
611
- `dsh-oidc` currently invokes an OpenAI Chat Completions-compatible interface through DSH's official Pi adapter.
612
-
613
- #### Request
614
-
615
- ```http
616
- POST /open/api/v1/chat/completions HTTP/1.1
617
- Host: ai.example.edu
618
- Authorization: Bearer <runtime-api-key>
619
- Content-Type: application/json
620
- Accept: text/event-stream
621
-
622
- {
623
- "model": "example-model",
624
- "messages": [
625
- { "role": "user", "content": "Hello" }
626
- ],
627
- "stream": true,
628
- "max_tokens": 1024
629
- }
630
- ```
631
-
632
- | Item | Requirement |
633
- | --- | --- |
634
- | Authentication | Accept the `api_key` returned by Key Binding; do not accept an OIDC Token as a substitute |
635
- | `model` | Implement every model ID declared in Profile `provider.models` |
636
- | `messages` | Support the OpenAI Chat Completions message structure |
637
- | `stream` | Streaming responses are required |
638
- | Image input | Required only when the Profile declares `input: ["text", "image"]` for that model |
639
- | Reasoning parameters | Match Profile `reasoning`, `reasoningEfforts`, and `compat` declarations |
640
- | Tools | MUST support `tools`, `tool_choice`, and streamed `tool_calls` for DSH Agent tool use |
641
-
642
- #### Streaming success response
643
-
644
- ```http
645
- HTTP/1.1 200 OK
646
- Content-Type: text/event-stream
647
- Cache-Control: no-cache
648
-
649
- data: {"id":"chatcmpl-1","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Hel"}}]}
650
-
651
- data: {"id":"chatcmpl-1","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"lo"},"finish_reason":null}]}
652
-
653
- data: {"id":"chatcmpl-1","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
654
-
655
- data: [DONE]
656
- ```
657
-
658
- For every runtime API key, the server MUST enforce subject binding, Provider scope, credential state, expiry, revocation, and quota policy when enabled. This server-side policy does not require public quota UI or a `/quota` endpoint. A revoked or rotated old key MUST NOT retain indefinite access.
659
-
660
- #### Error response
661
-
662
- Errors SHOULD interoperate with OpenAI-compatible clients, for example:
663
-
664
- ```json
665
- {
666
- "error": {
667
- "message": "Runtime credential has expired",
668
- "type": "authentication_error",
669
- "code": "credential_expired"
670
- }
671
- }
672
- ```
673
-
674
- The gateway SHOULD distinguish at least: `400` invalid request, `401` invalid credential, `403` access denied, `404` unknown model, `429` rate/quota limit, and `5xx` server failure.
675
-
676
- Static `modelSource=profile` deployments may omit `/models`; `modelSource=discovery` deployments implement MODEL-02. A missing discovery catalog must not be replaced with invented model IDs.
677
-
678
- ### MODEL-02: Model catalog
679
-
680
- `GET {provider.baseURL}/models`, authenticated with `Authorization: Bearer <runtime-api-key>`, returns HTTP 200 JSON:
681
-
682
- ```json
683
- {"data":[{"id":"example-model","object":"model","owned_by":"example-ai"}]}
684
- ```
685
-
686
- `data` contains at most 128 distinct IDs, each 1–256 characters, available to that key. The client consumes IDs only. Profile-reviewed models retain their declared context, input modalities and reasoning effort levels; new IDs default to text input with no reasoning_effort. Additional OpenAI-style fields are ignored. Empty/invalid/unavailable discovery retains the last valid catalog and reports `models_unavailable`. Remote capabilities, arbitrary URLs and executable configuration are not accepted. Accurate advanced capabilities currently belong in the trusted Profile.
687
-
688
- ## 7. Cross-interface invariants
689
-
690
- The service MUST guarantee:
691
-
692
- 1. UserInfo `sub` exactly equals ID Token `sub`.
693
- 2. Key Binding derives the same subject from the Access Token and does not trust request-body identity.
694
- 3. Profile `provider.id`, Bootstrap `provider.id`, and runtime-credential `provider_id` are exactly equal.
695
- 4. A runtime API key is authorized only for its subject and Provider.
696
- 5. Model IDs, input modalities, and reasoning capabilities actually served by the gateway match the Profile.
697
- 6. OIDC Tokens are used only for UserInfo and Key Binding; runtime API keys are used only for the model gateway. They are not interchangeable.
698
- 7. Account suspension, authorization revocation, and credential rotation propagate to runtime credentials and the model gateway.
699
- 8. Logs may contain a correlation ID, opaque subject reference, Provider ID, and `api_key_id`, but never a Token or `api_key`.
700
- 9. Bootstrap `subject` data is display-only and cannot override OIDC `sub` or `name`.
701
-
702
- ## 8. Standard call sequence
703
-
704
- ```text
705
- Client -> OIDC-01 Discovery
706
- Client -> OIDC-02 Authorization (browser + PKCE)
707
- OIDC -> 127.0.0.1:<port>/oauth/callback
708
- Client -> OIDC-03 Token
709
- Client -> OIDC-04 JWKS (verify ID Token)
710
- Client -> OIDC-05 UserInfo
711
- Client -> KEY-01 Bootstrap
712
- Client -> KEY-02 Provision (first use with explicit consent)
713
- or KEY-03 Resolve (active credential)
714
- or KEY-04 Renew (expiring/expired credential)
715
- Client -> MODEL-01 Chat Completions
716
- ```
717
-
718
- When the Access Token is close to expiry, the client uses the Refresh Token grant on OIDC-03. Logout clears the local OIDC session and runtime credential and calls OIDC-06 when available.
719
-
720
- ## 9. Integration acceptance checklist
721
-
722
- A complete integration MUST pass at least these tests:
723
-
724
- - exact Discovery issuer match, all required endpoints present, and PKCE `S256` advertised;
725
- - callback uses only `127.0.0.1:<actual-port>/oauth/callback`;
726
- - state, nonce, PKCE verifier, ID Token signature, audience, and expiry are validated;
727
- - UserInfo returns a non-empty `sub` matching the ID Token, and `name` displays correctly;
728
- - all four Key Binding operations exist, with Provision, Resolve, or Renew selected from lifecycle state;
729
- - Provision and Renew are idempotent, and incompatible replay returns `409`;
730
- - Provider ID is identical across Profile, Bootstrap, and credential response;
731
- - a Key Binding Access Token cannot call the model gateway, and a model API key cannot call UserInfo or Key Binding;
732
- - expired/revoked runtime credentials, suspended accounts, and unauthorized access are rejected;
733
- - Chat Completions completes at least one streaming text conversation; declared multimodal and reasoning capabilities pass their corresponding tests;
734
- - Tokens, Authorization headers, and `api_key` never appear in URLs, logs, APM, error details, or audit data;
735
- - every failure response has a stable, diagnosable, non-secret error structure.
736
-
737
- See [Enterprise Profile](enterprise-profile.en.md) for configuration fields, [OIDC interoperability](oidc-interoperability.en.md) for additional OIDC details, and [Key Binding protocol](key-binding-protocol.en.md) for lifecycle and security semantics.
738
-
739
- ## 10. Refresh, logout and HTTP request examples
740
-
741
- The client refreshes access tokens within 90 seconds of expiry and coalesces concurrent refreshes. A management `401` refreshes and retries once with the same body and idempotency key. `invalid_grant` clears local identity and only a model key still owned by it; transient failures preserve valid credentials. `status` projects state; `reconcile` checks the server.
742
-
743
- Normal OIDC/management/resource timeouts are 20 seconds, revocation 10 seconds. Management/resource requests reject redirects. There is no scheduled `Retry-After` retry for management operations; a later user action creates a new idempotency key. Provision must also prevent duplicate active credentials for the same subject/provider.
744
-
745
- Logout cancels pending desktop login and clears the profile's identity and model snapshot. It deletes a local managed key only while its saved fingerprint still owns that value, preserving another login's key and personal keys. Revocation is best effort when advertised; browser-wide SSO logout and remote model-key revocation are not implemented. The server remains responsible for denying expired, revoked or disabled credentials.
746
-
747
- The following POSIX shell examples contain placeholders; Windows can use WSL. Requests originate in the user's local Runtime, so browser CORS is not needed. Do not put real secrets into shared logs or tickets. `ACCESS_TOKEN` and `RUNTIME_KEY` have different purposes. All OIDC endpoints must come from verified Discovery.
748
-
749
- ```bash
750
- ISSUER='https://id.example.edu/oidc'
751
- MANAGEMENT='https://ai.example.edu/api/worker/v1'
752
- MODEL_BASE='https://ai.example.edu/open/api/v1'
753
- CLIENT_ID='eduwork-public-client'
754
- REDIRECT_URI='http://127.0.0.1:3080/oauth/callback'
755
- curl --fail-with-body --max-time 20 "$ISSUER/.well-known/openid-configuration"
756
- # Copy these URLs from verified Discovery; their paths are not fixed.
757
- AUTHORIZATION_ENDPOINT='https://id.example.edu/oidc/authorize'
758
- TOKEN_ENDPOINT='https://id.example.edu/oidc/token'
759
- JWKS_URI='https://id.example.edu/oidc/jwks'
760
- USERINFO_ENDPOINT='https://id.example.edu/oidc/userinfo'
761
- REVOCATION_ENDPOINT='https://id.example.edu/oidc/revoke'
762
- curl --fail-with-body --max-time 20 "$JWKS_URI"
763
- ```
764
-
765
- Authorization must complete in the system browser. The GET below illustrates the request, not a way to bypass institutional login. Generate state, nonce and the verifier with a secure random source; the S256 challenge is BASE64URL(SHA256(verifier)). The original client owns and validates the callback and ID Token.
766
-
767
- ```bash
768
- curl --get --max-time 20 "$AUTHORIZATION_ENDPOINT" \
769
- --data-urlencode 'response_type=code' --data-urlencode "client_id=$CLIENT_ID" \
770
- --data-urlencode "redirect_uri=$REDIRECT_URI" --data-urlencode 'scope=openid profile offline_access' \
771
- --data-urlencode "state=$STATE" --data-urlencode "nonce=$NONCE" \
772
- --data-urlencode "code_challenge=$PKCE_CHALLENGE" --data-urlencode 'code_challenge_method=S256'
773
- curl --fail-with-body --max-time 20 "$TOKEN_ENDPOINT" \
774
- --data-urlencode 'grant_type=authorization_code' --data-urlencode "client_id=$CLIENT_ID" \
775
- --data-urlencode "code=$CODE" --data-urlencode "redirect_uri=$REDIRECT_URI" --data-urlencode "code_verifier=$PKCE_VERIFIER"
776
- curl --fail-with-body --max-time 20 -H "Authorization: Bearer $ACCESS_TOKEN" "$USERINFO_ENDPOINT"
777
- curl --fail-with-body --max-time 20 -H "Authorization: Bearer $ACCESS_TOKEN" "$MANAGEMENT/bootstrap"
778
- ```
779
-
780
- Perform only the lifecycle operation selected by Bootstrap. Provision needs user consent. Reuse one idempotency key for retries of a logical write, with a separate key for Renew. `api_key_id` is the string identifier returned by the server. Supply the actual resource scopes described in section 5.1 when the deployment requires them.
781
-
782
- ```bash
783
- curl --fail-with-body --max-time 20 "$MANAGEMENT/runtime-credential/provision" \
784
- -H "Authorization: Bearer $ACCESS_TOKEN" -H 'Content-Type: application/json' \
785
- -H "Idempotency-Key: $IDEMPOTENCY_KEY" --data '{"provider_id":"example-ai"}'
786
- curl --fail-with-body --max-time 20 "$MANAGEMENT/runtime-credential/resolve" \
787
- -H "Authorization: Bearer $ACCESS_TOKEN" -H 'Content-Type: application/json' \
788
- --data '{"provider_id":"example-ai","api_key_id":"key-123"}'
789
- curl --fail-with-body --max-time 20 "$MANAGEMENT/runtime-credential/renew" \
790
- -H "Authorization: Bearer $ACCESS_TOKEN" -H 'Content-Type: application/json' \
791
- -H "Idempotency-Key: $RENEW_IDEMPOTENCY_KEY" --data '{"provider_id":"example-ai","api_key_id":"key-123"}'
792
- curl --fail-with-body --max-time 20 -H "Authorization: Bearer $RUNTIME_KEY" "$MODEL_BASE/models"
793
- curl --no-buffer --fail-with-body "$MODEL_BASE/chat/completions" \
794
- -H "Authorization: Bearer $RUNTIME_KEY" -H 'Content-Type: application/json' \
795
- --data '{"model":"example-model","messages":[{"role":"user","content":"Reply with OK"}],"stream":true}'
796
- curl --fail-with-body --max-time 20 "$TOKEN_ENDPOINT" \
797
- --data-urlencode 'grant_type=refresh_token' --data-urlencode "client_id=$CLIENT_ID" --data-urlencode "refresh_token=$REFRESH_TOKEN"
798
- curl --fail-with-body --max-time 10 "$REVOCATION_ENDPOINT" \
799
- --data-urlencode "client_id=$CLIENT_ID" --data-urlencode "token=$REFRESH_TOKEN"
800
- ```
801
-
802
- Skip revocation when Discovery omits the endpoint. The OIDC paths shown are examples, while management suffixes, `/models` and `/chat/completions` are fixed. A code from another browser attempt cannot be exchanged without that attempt's verifier. Validate every ID Token requirement in section 4; base64 decoding alone proves nothing.
803
-
804
- ## 11. Optional extension proposals — not enabled or called by this release
805
-
806
- ### 11.1 Remote profile discovery
5
+ The client consumes two explicit gateway contracts. Discovery declarations must match endpoint behavior. It does not infer token semantics from branding or URLs or fall back to legacy model-key provisioning.
807
6
 
808
- A future `GET /.well-known/eduwork-configuration` could declare `protocol_version: "eduwork-discovery/v1alpha1"`, an OIDC issuer and resource capabilities. The current client reads its trusted local configuration and does not request this endpoint. Any later import must require explicit trust before adding origins, issuer or permissions; a discovery response must never install code, plugins or arbitrary commands. The existing OIDC Discovery endpoint remains implemented and required for identity; it is a different mechanism.
7
+ - [LiteLLM native contract 1](gateway-auth/README_EN.md): dynamic registration, Code + PKCE, token-authorized models, refresh and revocation.
8
+ - [Experimental oidc-llm 0.1](gateway-auth/experimental-oidc-llm.en.md): explicit registration, identity mode, model resource, API base and scopes; still a draft.
9
+ - [Standard identity-only OIDC](oidc-interoperability.en.md): identity profiles do not provide model resources.
809
10
 
810
- ### 11.2 Institution activity heartbeat
11
+ ## OIDC requirements
811
12
 
812
- Heartbeat is outside this public protocol. Institution plugins own its private wire and explicit enablement; it is never a login, model, or token-refresh requirement. The public package does not schedule telemetry.
13
+ Provide matching issuer, authorization, token, JWKS and UserInfo endpoints with public-client S256 PKCE. The client checks state, ID Token signature/claims and UserInfo subject binding. When discovery advertises authorization response iss support, the callback must include the matching issuer.
813
14
 
814
- ### 11.3 Other separate future capabilities
15
+ Catalogs and inference accept the same authorized Access Token. Validated discovery determines API/resource boundaries; the server enforces user permissions, model access and quota. Catalogs use data[].id and inference reuses OpenAI-compatible JSON/SSE.
815
16
 
816
- RP-initiated/global SSO logout, server model-key revocation, dynamic OIDC client registration and remotely advertised advanced model capabilities are not implemented by this release. Omission of these capabilities must not break basic identity or manually configured personal model access.
17
+ Token responses provide expiry and refresh credentials. Refresh cannot change the authorized subject or context. Immediate server-side Access Token invalidation on logout is not required; the client clears local access and attempts Refresh Token revocation.
817
18
 
818
- ## 12. Versioning and primary references
19
+ ## Quota and compatibility
819
20
 
820
- Profile schema (`v1alpha1`), resource wire (`v1`), npm package version and DSH runtime version are independent. The accepted Bootstrap aliases `worker.user-center.v1`, `worker-user-center/v1`, and `eduwork-resources/v1` describe the same deployed wire. Missing version is accepted for existing v1 servers; unknown explicit versions fail with a resource-connection error and do not invalidate an already verified identity. New servers SHOULD return an explicit supported version. Additive optional fields may be ignored; removing/renaming fields or changing credentials/scopes requires a new major wire version and reviewed client support. Institution quotas are not part of the public schema.
21
+ The public package defines no unified quota, team-management or Key Binding protocol. [Account extensions](account-extensions.en.md) can reuse authorized Host GET transport and existing quota parsers.
821
22
 
822
- Primary standards: [OpenID Connect Core](https://openid.net/specs/openid-connect-core-1_0.html), [OAuth for Native Apps / RFC 8252](https://www.rfc-editor.org/rfc/rfc8252), [OAuth Security Best Current Practice / RFC 9700](https://www.rfc-editor.org/rfc/rfc9700). This resource protocol complements OIDC; it does not redefine identity validation or infer a model credential from an ID Token.
23
+ Servers may retain Key Binding endpoints for old clients. New clients follow the [migration guide](key-binding-protocol.en.md) and never request model keys. The [catalog OpenAPI](../protocol/resources.openapi.yaml) describes resource operations, not the gateway authentication contract.