rodauth-ciba 0.1.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +34 -0
- data/LICENSE +21 -0
- data/README.md +106 -0
- data/docs/adr/0003-follow-rodauth-hook-conventions.md +9 -0
- data/docs/adr/0004-retain-atomic-issuance-and-stage-alignment.md +16 -0
- data/docs/alignment-closeout.md +136 -0
- data/docs/alignment-roadmap.md +160 -0
- data/docs/api.md +337 -0
- data/docs/authentication-claims.md +80 -0
- data/docs/authorization-details.md +50 -0
- data/docs/claims.md +46 -0
- data/docs/failure-contract.md +34 -0
- data/docs/id-token-encryption.md +288 -0
- data/docs/id-token-hint.md +174 -0
- data/docs/login-hint-token.md +35 -0
- data/docs/max-age.md +65 -0
- data/docs/node-alignment.md +336 -0
- data/docs/operations.md +130 -0
- data/docs/outbound-http.md +52 -0
- data/docs/pairwise.md +76 -0
- data/docs/ping.md +118 -0
- data/docs/protocol-coverage.md +114 -0
- data/docs/refresh-tokens.md +151 -0
- data/docs/release-readiness.md +75 -0
- data/docs/release-validation.md +498 -0
- data/docs/request-context.md +51 -0
- data/docs/requirements-audit-current.json +3221 -0
- data/docs/requirements-audit-current.md +424 -0
- data/docs/requirements-audit.json +3190 -0
- data/docs/requirements-audit.md +175 -0
- data/docs/research/access-token-revocation-reference.md +81 -0
- data/docs/research/alignment-partial-triage.md +50 -0
- data/docs/research/claims-reference-contract.md +33 -0
- data/docs/research/client-assertion-algorithm-contract.md +177 -0
- data/docs/research/completion-error-boundaries.md +56 -0
- data/docs/research/consent-api-evolution.md +59 -0
- data/docs/research/dpop-reference-contract.md +423 -0
- data/docs/research/id-token-hint-reference-contract.md +254 -0
- data/docs/research/mtls-reference-contract.md +449 -0
- data/docs/research/node-ciba-lifecycle.md +30 -0
- data/docs/research/node-oidc-provider-comparison.md +45 -0
- data/docs/research/pairwise-reference-contract.md +264 -0
- data/docs/research/ping-reference-contract.md +165 -0
- data/docs/research/rar-reference-contract.md +55 -0
- data/docs/research/refresh-reference-contract.md +85 -0
- data/docs/research/registration-reference-contract.md +567 -0
- data/docs/research/request-parameter-inventory.md +42 -0
- data/docs/research/required-parameter-inventory.md +39 -0
- data/docs/research/resource-reference-contract.md +40 -0
- data/docs/research/signed-request-reference-contract.md +113 -0
- data/docs/resources.md +55 -0
- data/docs/security-review.md +294 -0
- data/docs/signed-requests.md +162 -0
- data/docs/user-code.md +73 -0
- data/docs/validation/node-alignment-tests.txt +10 -0
- data/examples/client.rb +39 -0
- data/examples/demo_app.rb +121 -0
- data/examples/dpop_smoke.rb +169 -0
- data/examples/ecdh_id_token_smoke.rb +84 -0
- data/examples/edwards_id_token_smoke.rb +48 -0
- data/examples/edwards_smoke.rb +87 -0
- data/examples/edwards_tls_smoke.rb +73 -0
- data/examples/encrypted_id_token_smoke.rb +73 -0
- data/examples/encryption_tls_smoke.rb +123 -0
- data/examples/extensions_smoke.rb +226 -0
- data/examples/fixtures/edwards-requests.json +30 -0
- data/examples/hmac_id_token_smoke.rb +72 -0
- data/examples/migration.rb +18 -0
- data/examples/mtls_smoke.rb +395 -0
- data/examples/ping_smoke.rb +226 -0
- data/examples/registration_smoke.rb +73 -0
- data/examples/server.rb +14 -0
- data/examples/smoke.rb +46 -0
- data/examples/symmetric_id_token_smoke.rb +83 -0
- data/lib/rodauth/ciba/authorization_details.rb +60 -0
- data/lib/rodauth/ciba/dpop.rb +250 -0
- data/lib/rodauth/ciba/http.rb +69 -0
- data/lib/rodauth/ciba/id_token_encryption.rb +175 -0
- data/lib/rodauth/ciba/jwks_cache.rb +149 -0
- data/lib/rodauth/ciba/mtls.rb +174 -0
- data/lib/rodauth/ciba/pairwise.rb +105 -0
- data/lib/rodauth/ciba/registration.rb +489 -0
- data/lib/rodauth/ciba/schema.rb +182 -0
- data/lib/rodauth/ciba/version.rb +6 -0
- data/lib/rodauth/ciba.rb +4 -0
- data/lib/rodauth/features/ciba.rb +2185 -0
- metadata +231 -0
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
# CIBA mutual TLS: measured reference and unresolved Ruby integration
|
|
2
|
+
|
|
3
|
+
> Latest policy supersedes the historical minimum-floor notes below: use explicit
|
|
4
|
+
> HTTP freshness, and permit failed-fetch reuse only with publisher stale-if-error,
|
|
5
|
+
> capped at60 seconds without renewing deadlines. See [operations](../operations.md#remote-jwks-cache-lifetime).
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
> Cache policy update: the user approved a minimum60-second successful JWKS
|
|
9
|
+
> lifetime shared by CIBA authentication and encryption, without a new policy
|
|
10
|
+
> switch. Earlier shorter-TTL comparisons below describe the previous runtime.
|
|
11
|
+
> Failed-refresh reuse remains excluded; expiry followed by failed retrieval
|
|
12
|
+
> does not renew freshness. See the current alignment closeout and Jev review.
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## Real TLS reference
|
|
16
|
+
|
|
17
|
+
Node-oidc-provider 9.12.2 is configured with both `tls_client_auth` and
|
|
18
|
+
`self_signed_tls_client_auth`, certificate-bound access tokens, CIBA poll,
|
|
19
|
+
refresh and introspection. Its TLS listener requests a client certificate.
|
|
20
|
+
Application callbacks obtain the certificate from the TLS socket, use the
|
|
21
|
+
socket's chain-verification result for PKI clients, and compare the subject DN.
|
|
22
|
+
No HTTP header supplies certificate identity. The client also verifies the local
|
|
23
|
+
server certificate. The temporary self-signed test certificates are not
|
|
24
|
+
production credentials. The in-memory adapter and development OP signing keys
|
|
25
|
+
are used; protocol handlers, TLS and cryptography are not stubbed.
|
|
26
|
+
|
|
27
|
+
Run with the installed reference dependencies and Ruby/OpenSSL/Docker:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
mise exec ruby -- sh bin/test-mtls-reference
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The runner creates and removes temporary certificates. It uses the pinned Node
|
|
34
|
+
image with no external network. [Harness](../../test/reference/full-op/mtls.mjs),
|
|
35
|
+
[certificate generator](../../test/reference/full-op/mtls-certificates.rb),
|
|
36
|
+
[reference log](../validation/node-mtls-reference.txt).
|
|
37
|
+
|
|
38
|
+
Measured outcomes for both client authentication methods:
|
|
39
|
+
|
|
40
|
+
- CIBA initiation, approved token collection and confidential refresh succeed
|
|
41
|
+
with the registered certificate.
|
|
42
|
+
- Missing certificates fail client authentication. Supplying certificate/verify
|
|
43
|
+
HTTP headers without a TLS certificate also fails.
|
|
44
|
+
- Introspection reports `cnf["x5t#S256"]` equal to base64url SHA256 of the **entire
|
|
45
|
+
certificate DER**, not the JWK thumbprint of its public key.
|
|
46
|
+
- UserInfo requires the bound certificate. A different certificate with the
|
|
47
|
+
same public key is insufficient. Matching certificate returns the customer.
|
|
48
|
+
- Self-signed client authentication likewise rejects a distinct certificate
|
|
49
|
+
with the same public key; registered x5c identifies the certificate.
|
|
50
|
+
|
|
51
|
+
This fixture covers a DN-based PKI callback and static x5c registration. It does
|
|
52
|
+
not cover every SAN policy, remote JWKS rotation, dynamic registration, proxy
|
|
53
|
+
deployment or multi-process replay/lifecycle behavior.
|
|
54
|
+
|
|
55
|
+
## Upstream Ruby characterization: not a passing support claim
|
|
56
|
+
|
|
57
|
+
The historical `test/reference/upstream_mtls_probe.rb` ran the actual
|
|
58
|
+
gem Rack stack with upstream `oauth_tls_client_auth` enabled. Rack server
|
|
59
|
+
variables model a verified TLS peer; unlike the node fixture this probe does not
|
|
60
|
+
perform a TLS handshake. It is deliberately outside the normal regression
|
|
61
|
+
suite and records current defects rather than codifying them as desired behavior.
|
|
62
|
+
[Observed log](../validation/upstream-mtls-probe.txt).
|
|
63
|
+
|
|
64
|
+
- PKI CIBA initiation/issuance succeeds with matching DN and `SSL_CLIENT_VERIFY`.
|
|
65
|
+
- The stored certificate thumbprint is **not** SHA256 of certificate DER.
|
|
66
|
+
Source inspection shows upstream hashes the public JWK instead.
|
|
67
|
+
- The resulting bound token accesses UserInfo with no certificate (HTTP200).
|
|
68
|
+
Upstream's UserInfo path does not enforce the certificate binding here.
|
|
69
|
+
- Header-only `HTTP_X_SSL_CLIENT_CERT` plus `HTTP_X_SSL_CLIENT_VERIFY=SUCCESS`
|
|
70
|
+
is accepted. This is safe only under an explicit trusted TLS terminator that
|
|
71
|
+
removes/replaces client-supplied headers; there is no such trust configuration
|
|
72
|
+
in this probe. It is not evidence that every deployment is exposed.
|
|
73
|
+
- A registered self-signed x5c client returns HTTP500. Upstream's JWKS iteration
|
|
74
|
+
and comparison use incompatible shapes/values for this registration.
|
|
75
|
+
|
|
76
|
+
## Required correction boundary
|
|
77
|
+
|
|
78
|
+
Do not advertise CIBA mTLS support based on enabling the upstream feature alone.
|
|
79
|
+
The default gem configuration does not enable mTLS. Implement an explicit trusted
|
|
80
|
+
certificate/verification boundary for CIBA, correct full-certificate identity
|
|
81
|
+
and digest handling, and connect binding enforcement to the actual UserInfo
|
|
82
|
+
entry point, refresh and introspection. Keep other OAuth grant behavior separate.
|
|
83
|
+
Preserve the distinction between PKI chain/subject validation and registered
|
|
84
|
+
self-signed certificate matching. Then replace characterization expectations
|
|
85
|
+
with rejection/positive regression tests and real TLS installed-artifact checks.
|
|
86
|
+
The initial correction below implements part of this boundary; integration work remains.
|
|
87
|
+
|
|
88
|
+
## Initial CIBA correction
|
|
89
|
+
|
|
90
|
+
The extension now requires an explicit `ciba_tls_client_certificate` callback
|
|
91
|
+
when upstream `oauth_tls_client_auth` is enabled, plus the configured grant
|
|
92
|
+
certificate-thumbprint column. The callback returns the TLS peer certificate as
|
|
93
|
+
an OpenSSL certificate or PEM String, or nil. It must derive that value from a
|
|
94
|
+
trusted server/terminator integration that already proved possession of the
|
|
95
|
+
certificate key. No forwarded certificate or verification header is read by the
|
|
96
|
+
CIBA path. Other OAuth routes retain upstream behavior.
|
|
97
|
+
|
|
98
|
+
For PKI authentication, configure `ciba_tls_client_certificate_authorized?` and
|
|
99
|
+
`ciba_tls_client_certificate_subject_matches?(property, expected)`. Both default
|
|
100
|
+
to false. The first reports actual chain verification; the second checks the
|
|
101
|
+
single registered DN/SAN value. These are application-owned TLS integration
|
|
102
|
+
policies like the reference callbacks. Multiple registered identity properties
|
|
103
|
+
or mixed TLS authentication method lists are rejected. For self-signed clients,
|
|
104
|
+
the first x5c certificate of a registered key must match the full peer certificate
|
|
105
|
+
DER digest; a same-key/different-certificate presentation fails. Remote JWKS uses
|
|
106
|
+
the existing guarded HTTP path, but TLS-specific remote rotation remains untested.
|
|
107
|
+
|
|
108
|
+
Certificate-bound CIBA issuance now stores SHA256(DER) and exposes it in signed
|
|
109
|
+
JWT cnf and opaque introspection. UserInfo verifies the live saved issuance and
|
|
110
|
+
requires the matching certificate. Confidential refresh authenticates again and
|
|
111
|
+
binds the new access token; it does not add public-client refresh-token binding.
|
|
112
|
+
A missing required certificate or simultaneous DPoP binding is rejected before
|
|
113
|
+
upstream's nested persistence transaction. Rejecting the combination later had
|
|
114
|
+
consumed approval; validation was moved before proof reservation/persistence and
|
|
115
|
+
the regression now verifies unchanged approval and zero token/proof rows.
|
|
116
|
+
|
|
117
|
+
The old upstream-only characterization probe has been replaced by
|
|
118
|
+
`test/ciba_mtls_test.rb`; its historical output above is retained as failure
|
|
119
|
+
evidence, not current behavior. The new tests cover both authentication methods
|
|
120
|
+
and opaque/JWT tokens, default/header-only rejection, wrong certificate, wrong
|
|
121
|
+
PKI authorization/subject, configuration checks, issuance-signing rollback,
|
|
122
|
+
UserInfo expiry/revocation, introspection, refresh and DPoP coexistence rejection.
|
|
123
|
+
[Local regression run](../validation/ciba-mtls-current.txt).
|
|
124
|
+
|
|
125
|
+
This is not complete mTLS alignment. DCR metadata/management,
|
|
126
|
+
remote certificate-key rotation, all SAN
|
|
127
|
+
callback policies, aliases/discovery and additional client-method combinations
|
|
128
|
+
still need work. The reference real-TLS fixture does not prove the new Ruby
|
|
129
|
+
callbacks are correctly wired in a deployment.
|
|
130
|
+
|
|
131
|
+
## Installed gem with a Ruby TLS server
|
|
132
|
+
|
|
133
|
+
`bin/verify-package` now runs the packaged `examples/mtls_smoke.rb` from its
|
|
134
|
+
temporary gem installation. It checks the loaded mTLS module's filesystem path
|
|
135
|
+
against that installation. A WEBrick TLS listener invokes the actual Rack app;
|
|
136
|
+
the requests are sent over HTTPS with Net::HTTP, not Rack mock dispatch. Rack's
|
|
137
|
+
environment helper is used only to adapt the incoming HTTP request. The trusted
|
|
138
|
+
certificate value is assigned from WEBrick's TLS peer certificate after all
|
|
139
|
+
HTTP headers have been mapped, so caller headers cannot populate that value.
|
|
140
|
+
|
|
141
|
+
The listener requests peer certificates and allows self-signed chains to reach
|
|
142
|
+
the application, like the node fixture. For PKI authentication the application
|
|
143
|
+
callback independently verifies the peer against the test trust store and
|
|
144
|
+
checks the registered DN. A foreign certificate with the same DN fails that
|
|
145
|
+
verification. Self-signed authentication matches registered x5c. The HTTP client
|
|
146
|
+
verifies the server's certificate and hostname against a separate local trust
|
|
147
|
+
store. Keys and certificates are generated in memory for each run.
|
|
148
|
+
|
|
149
|
+
All four combinations (PKI/self-signed, opaque/JWT) pass CIBA initiation,
|
|
150
|
+
approved token collection, UserInfo and confidential refresh. Absent certificates
|
|
151
|
+
and forwarded-header-only certificate claims fail authentication; missing and
|
|
152
|
+
same-key/different certificates cannot use bound UserInfo tokens. A new access
|
|
153
|
+
token after refresh still requires its certificate. ID Token signatures,
|
|
154
|
+
issuer/audience and customer subject are checked; JWT access-token cnf and opaque
|
|
155
|
+
introspection cnf match SHA256 of the certificate DER.
|
|
156
|
+
[Installed TLS run](../validation/mtls-installed-tls.txt).
|
|
157
|
+
|
|
158
|
+
This verifies one in-process Ruby TLS adapter and application trust policy with
|
|
159
|
+
static client registration on SQLite/Ruby4.0.6. It does not cover a production
|
|
160
|
+
reverse proxy, intermediate-chain policies, all SAN policies, dynamic certificate
|
|
161
|
+
registration/rotation or multiple application processes. No gem is published and
|
|
162
|
+
no network dependency resolution is performed by this verification.
|
|
163
|
+
|
|
164
|
+
## Dynamic registration and management
|
|
165
|
+
|
|
166
|
+
The CIBA metadata allowlist now includes the five PKI subject properties and
|
|
167
|
+
`tls_client_certificate_bound_access_tokens`. When the TLS feature is enabled,
|
|
168
|
+
binding accepts only JSON booleans and defaults to false. Explicit properties
|
|
169
|
+
require their configured storage columns; missing storage fails registration.
|
|
170
|
+
PKI clients must supply exactly one nonempty string subject DN/SAN property.
|
|
171
|
+
Other authentication methods discard subject properties, as the reference does.
|
|
172
|
+
Self-signed clients require registered `jwks` or `jwks_uri`. Requiring both DPoP
|
|
173
|
+
and TLS token binding fails registration. With TLS disabled, TLS properties are
|
|
174
|
+
ignored instead of passed through as arbitrary DB fields.
|
|
175
|
+
|
|
176
|
+
Full management replacement resets omitted binding to false and preserves false
|
|
177
|
+
in its response. Invalid updates preserve the saved row and the management
|
|
178
|
+
credential. Changing issuance policy does not remove existing access-token
|
|
179
|
+
binding: the gem regression dynamically registers a self-signed client, issues a
|
|
180
|
+
bound token, disables future binding through management, and verifies certificate
|
|
181
|
+
requirements on the old token while the new token is unbound.
|
|
182
|
+
[Local cases](../validation/mtls-registration-current.txt).
|
|
183
|
+
|
|
184
|
+
The real-TLS node harness now obtains its PKI and self-signed clients through
|
|
185
|
+
authenticated DCR and uses the generated client IDs for the complete CIBA flow.
|
|
186
|
+
It measures boolean validation, one-subject validation, required self-signed keys,
|
|
187
|
+
default false, incompatible dual binding and failed/successful management
|
|
188
|
+
replacement. [Reference run](../validation/node-mtls-registration.txt).
|
|
189
|
+
|
|
190
|
+
The static-registration installed-gem evidence above is historical; the fixture
|
|
191
|
+
now uses DCR as described below. Remote certificate rotation,
|
|
192
|
+
all SAN callback semantics and aliases remain to be aligned. The global binding
|
|
193
|
+
policy/capability distinction is addressed below.
|
|
194
|
+
|
|
195
|
+
## Installed DCR and management over TLS
|
|
196
|
+
|
|
197
|
+
The packaged TLS fixture now creates both PKI and self-signed clients through the
|
|
198
|
+
actual authenticated registration endpoint. It uses the returned client ID for
|
|
199
|
+
initiation, token collection, introspection and refresh, and checks that ID Token
|
|
200
|
+
audience matches that generated ID. All existing TLS certificate rejection and
|
|
201
|
+
opaque/JWT binding checks still run in all four combinations.
|
|
202
|
+
|
|
203
|
+
An invalid management PUT leaves policy and the credential usable. A successful
|
|
204
|
+
full replacement omitting the binding flag returns false and rotates the
|
|
205
|
+
management credential: the old credential is rejected and the new one can read
|
|
206
|
+
the client. Old access tokens still require their original certificate. A later
|
|
207
|
+
refresh authenticates through mTLS but, under the new policy, issues an unbound
|
|
208
|
+
access token which can use UserInfo without a certificate. This distinguishes
|
|
209
|
+
TLS client authentication from access-token certificate binding.
|
|
210
|
+
|
|
211
|
+
[Installed DCR TLS run](../validation/mtls-dcr-installed-tls.txt). This is the
|
|
212
|
+
temporarily installed artifact on Ruby4.0.6/SQLite using the same explicit TLS
|
|
213
|
+
trust boundary described above. It does not prove certificate rotation or
|
|
214
|
+
arbitrary deployment/trust-policy behavior. Existing package smoke checks also
|
|
215
|
+
pass; no production code changed in this verification increment.
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
## Capability versus client binding policy
|
|
219
|
+
|
|
220
|
+
The node TLS harness advertises binding capability true while a client with
|
|
221
|
+
binding false obtains unbound new access tokens. Existing bound tokens still
|
|
222
|
+
require their certificate. This is measured after management replacement and
|
|
223
|
+
refresh, not inferred from metadata alone.
|
|
224
|
+
[Reference run](../validation/node-mtls-capability.txt).
|
|
225
|
+
|
|
226
|
+
The gem previously let upstream's global binding flag override a CIBA client's
|
|
227
|
+
false policy; both opaque and JWT regressions reproduced it.
|
|
228
|
+
[Failing cases](../validation/mtls-capability-red.txt). CIBA issuance and refresh
|
|
229
|
+
now use only client binding metadata. The upstream global setting is not mutated
|
|
230
|
+
and its ordinary OAuth grant handling remains delegated to upstream. Enabling
|
|
231
|
+
`oauth_tls_client_auth` advertises capability true in both metadata documents,
|
|
232
|
+
regardless of that global policy. A prepended metadata method avoids feature
|
|
233
|
+
load order overwriting the value; the OIDC allowlist preserves it.
|
|
234
|
+
|
|
235
|
+
The related mTLS/Discovery suite passes **21 tests / 311 assertions** on each of
|
|
236
|
+
SQLite, PostgreSQL and MySQL, with no failures/errors/skips.
|
|
237
|
+
[Matrix](../validation/mtls-capability-matrix.txt). The installed TLS fixture now
|
|
238
|
+
sets upstream's global policy true and still verifies management replacement to
|
|
239
|
+
false, certificate-free use of new refreshed tokens, and certificate enforcement
|
|
240
|
+
on old tokens in all four PKI/self-signed × opaque/JWT combinations.
|
|
241
|
+
[Package](../validation/mtls-capability-package.txt). This does not establish
|
|
242
|
+
identical configuration APIs, remote rotation, endpoint aliases or arbitrary
|
|
243
|
+
proxy/SAN policy equivalence.
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
## Endpoint aliases are application deployment configuration
|
|
247
|
+
|
|
248
|
+
Inspection of the pinned node9.12.2 `lib/actions/discovery.js` finds no automatic
|
|
249
|
+
`mtls_endpoint_aliases` generation. The real TLS fixture now first verifies that
|
|
250
|
+
absence, then adds aliases through application middleware and serves the same
|
|
251
|
+
provider on a second HTTPS port. Discovered aliases for backchannel authentication,
|
|
252
|
+
token, introspection and UserInfo are used in the complete existing CIBA flow.
|
|
253
|
+
Canonical endpoint metadata and issuer remain unchanged; ID Token issuer remains
|
|
254
|
+
canonical too. [Reference run](../validation/node-mtls-aliases.txt).
|
|
255
|
+
|
|
256
|
+
The installed gem fixture follows the same deployment boundary: application
|
|
257
|
+
Discovery middleware publishes the four aliases and two WEBrick TLS listeners
|
|
258
|
+
serve the same Rack application. `base_url` and `authorization_server_url` remain
|
|
259
|
+
pinned to the canonical issuer. Request environments use the actual listener
|
|
260
|
+
origin, while the peer certificate comes from the TLS socket. The client follows
|
|
261
|
+
the discovered alias URLs; absent/header-only/foreign certificates still fail,
|
|
262
|
+
UserInfo still checks certificate binding, and management still changes only
|
|
263
|
+
future issuance policy. The ID Token signature, issuer and audience are verified.
|
|
264
|
+
All PKI/self-signed × opaque/JWT combinations pass.
|
|
265
|
+
[Installed package](../validation/mtls-aliases-package.txt).
|
|
266
|
+
|
|
267
|
+
No gem routing API is needed for this measured deployment. Aliases are not
|
|
268
|
+
created automatically and the application owns both published URLs and actual
|
|
269
|
+
TLS routing. This is evidence for distinct ports and unchanged paths, not for
|
|
270
|
+
arbitrary path rewrites, reverse proxies, alias-only access enforcement, DPoP
|
|
271
|
+
proof URLs on aliases, or certificate rotation. Earlier references to aliases as
|
|
272
|
+
an unimplemented parity feature were too broad: automatic alias routing is not a
|
|
273
|
+
behavior of the pinned reference. Deployment-specific cases remain unverified.
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
## Remote x5c rotation and retrieval failures
|
|
277
|
+
|
|
278
|
+
The node TLS fixture now registers a self-signed client with a remote `jwks_uri`.
|
|
279
|
+
Its locally owned HTTP JWKS receiver is explicitly allowed through the provider's
|
|
280
|
+
fetch policy; inbound authentication still uses real TLS peer certificates.
|
|
281
|
+
Both reference and gem tests verify: a cache with `max-age=3600` retains the old
|
|
282
|
+
certificate; a new certificate with the same public key does not force a fetch;
|
|
283
|
+
after forced cache expiry, only the newly published certificate authenticates.
|
|
284
|
+
Tests expire the actual cache directly rather than waiting an hour. This proves
|
|
285
|
+
replacement semantics, not a wall-clock TTL or distributed-cache guarantee.
|
|
286
|
+
[Reference run](../validation/node-mtls-remote.txt).
|
|
287
|
+
|
|
288
|
+
The gem had reused the signed-request key-lookup flag during TLS authentication.
|
|
289
|
+
A remote 503 therefore returned `invalid_request`, incorrectly treating this as
|
|
290
|
+
key lookup after successful client authentication. A distinct TLS lookup flag
|
|
291
|
+
now selects guarded HTTP transport and returns `invalid_client_metadata` for
|
|
292
|
+
retrieval/JSON/structure failure, matching the observed reference response.
|
|
293
|
+
An empty key set is structurally valid but cannot authenticate, so returns
|
|
294
|
+
`invalid_client` with401. Failed requests do not create pending CIBA requests.
|
|
295
|
+
[Regression before fix](../validation/mtls-remote-red.txt).
|
|
296
|
+
|
|
297
|
+
There is a deliberate failure-cache difference: after refreshing a populated
|
|
298
|
+
reference key store fails with503, its new freshness timestamp retains old keys
|
|
299
|
+
and the next authentication can succeed without another fetch. The gem does not
|
|
300
|
+
restore an expired cached key set after failure and continues to reject until a
|
|
301
|
+
successful fetch. This stricter behavior is retained rather than copying stale
|
|
302
|
+
credential acceptance. Neither behavior instantly revokes already cached keys;
|
|
303
|
+
rotation/revocation must account for the configured cache lifetime. Node also
|
|
304
|
+
uses a minimum60-second freshness interval; the gem retains upstream's HTTP
|
|
305
|
+
cache policy, including `no-cache`, without claiming identical TTL behavior.
|
|
306
|
+
|
|
307
|
+
The final gem case checks repeated503 rejection, blocked destinations without
|
|
308
|
+
network requests, absence of pending records on failure and successful recovery.
|
|
309
|
+
[Final three-DB case](../validation/mtls-remote-final.txt). The broader mTLS/remote
|
|
310
|
+
JWKS subset passes17 tests /304 assertions per DB before those final additions.
|
|
311
|
+
[Related cases](../validation/mtls-remote-matrix.txt).
|
|
312
|
+
|
|
313
|
+
This increment does not yet exercise remote certificate rotation in the
|
|
314
|
+
installed artifact, simultaneous issuer processes, or a real HTTPS JWKS rotation
|
|
315
|
+
receiver. The installed package's existing TLS/aliases and other smoke flows
|
|
316
|
+
are separately rerun; outbound TLS transport has separate earlier JWKS evidence.
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
## Installed HTTPS certificate rotation and JWT introspection
|
|
320
|
+
|
|
321
|
+
The installed mTLS smoke now runs in a child process whose `SSL_CERT_FILE` trusts
|
|
322
|
+
only the locally generated fixture certificate file in addition to the process's
|
|
323
|
+
normal trust-path behavior. Verification is never disabled. This isolates the
|
|
324
|
+
fixture CA from the parent process and later smoke fixtures. Self-signed clients
|
|
325
|
+
use a dynamically registered HTTPS `jwks_uri`; the real outbound transport reads
|
|
326
|
+
`x5c` from the owned receiver, with only loopback explicitly allowed by the app.
|
|
327
|
+
|
|
328
|
+
Both opaque and JWT flows pass remote cache reuse and certificate mismatch
|
|
329
|
+
without refetch. After publishing a different certificate containing the same
|
|
330
|
+
key and explicitly expiring the cache, the old certificate cannot authenticate,
|
|
331
|
+
while the new one can refresh an existing confidential client's token. The new
|
|
332
|
+
access token's UserInfo and introspection binding refer to the new certificate.
|
|
333
|
+
Previously issued tokens still require the original certificate. Repeated503
|
|
334
|
+
responses produce invalid_client_metadata without adding pending requests;
|
|
335
|
+
restoring the receiver allows authentication again.
|
|
336
|
+
|
|
337
|
+
The expanded fixture exposed a runtime defect: JWT introspection discarded cnf
|
|
338
|
+
because the upstream serializer receives verified claims rather than a grant
|
|
339
|
+
row. The mTLS serializer now resolves the live CIBA issuance, checks client and
|
|
340
|
+
subject provenance plus the saved/claimed certificate digest, then includes
|
|
341
|
+
`cnf.x5t#S256`. Expired or revoked issuance returns inactive. This is scoped to
|
|
342
|
+
CIBA tokens with the TLS feature; unrelated JWT handling stays upstream-owned.
|
|
343
|
+
[Failing installed run](../validation/mtls-remote-installed-tls.txt).
|
|
344
|
+
|
|
345
|
+
The corrected package passes the entire installed smoke suite, including both
|
|
346
|
+
remote-rotation variants. [Final package](../validation/mtls-remote-installed-final.txt).
|
|
347
|
+
The mTLS subset passes **14 tests /307 assertions** on each of SQLite, PostgreSQL
|
|
348
|
+
and MySQL, including opaque/JWT introspection cnf, expiry and revocation.
|
|
349
|
+
[DB regression](../validation/mtls-jwt-introspection-matrix.txt).
|
|
350
|
+
|
|
351
|
+
These tests use forced cache expiry and one application process on Ruby4.0.6;
|
|
352
|
+
they do not establish real-time TTL behavior, distributed invalidation, arbitrary
|
|
353
|
+
proxy trust configuration, or all TLS subject-matching application policies.
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
## Pairwise self-signed TLS client authentication
|
|
357
|
+
|
|
358
|
+
The reference TLS harness dynamically registers a pairwise self-signed client
|
|
359
|
+
with remote jwks_uri and verifies CIBA issuance and matching UserInfo subject.
|
|
360
|
+
[Reference](../validation/node-pairwise-mtls.txt). The gem's private_key_jwt-only
|
|
361
|
+
pairwise gate rejected this combination ([regression](../validation/pairwise-mtls-red.txt)).
|
|
362
|
+
It now accepts self_signed_tls_client_auth when the TLS feature is enabled,
|
|
363
|
+
while still requiring remote JWKS and rejecting simultaneous inline keys. Actual
|
|
364
|
+
certificate verification remains in the mTLS authentication boundary.
|
|
365
|
+
|
|
366
|
+
The new gem cases cover opaque/JWT initiation, different-certificate refusal,
|
|
367
|
+
approval, pairwise subject snapshot, canonical stored account, UserInfo and
|
|
368
|
+
refresh. The installed fixture now combines DCR, pairwise self-signed clients,
|
|
369
|
+
HTTPS x5c rotation and mTLS aliases. It exposed a JWT introspection failure:
|
|
370
|
+
upstream tried to look up the pairwise subject as a local account identifier.
|
|
371
|
+
The mTLS serializer now supplies the already-validated issuance's internal
|
|
372
|
+
account ID to that lookup while keeping the public pairwise sub in the response.
|
|
373
|
+
[Local failure](../validation/pairwise-mtls-introspection-red.txt),
|
|
374
|
+
[initial installed failure](../validation/pairwise-mtls-package.txt).
|
|
375
|
+
|
|
376
|
+
The initial related matrix passes30 tests on each DB; final focused and installed
|
|
377
|
+
results are recorded separately after the introspection correction.
|
|
378
|
+
[Related](../validation/pairwise-mtls-matrix.txt),
|
|
379
|
+
[final DB](../validation/pairwise-mtls-final.txt),
|
|
380
|
+
[final package](../validation/pairwise-mtls-package-final.txt).
|
|
381
|
+
Signed-request-only proof of remote-key ownership remains a separate missing
|
|
382
|
+
pairwise path. These checks do not establish arbitrary app identifier policy or
|
|
383
|
+
multi-process key/sector rotation.
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
The installed pairwise JWT also exceeded Rodauth's default1024-byte parameter
|
|
387
|
+
limit. Its introspection token became empty before CIBA client-auth selection.
|
|
388
|
+
CIBA introspection now permits identified CIBA token input up to8192 bytes;
|
|
389
|
+
other parameters/routes and unrelated tokens retain upstream limits. This
|
|
390
|
+
unverified marker selects parsing/authentication handling only; signature and
|
|
391
|
+
live issuance validation still precede an active response. The final focused
|
|
392
|
+
case uses a254-character pairwise subject and asserts the JWT exceeds1024 bytes.
|
|
393
|
+
The final installed run passes remote certificate rotation and introspection
|
|
394
|
+
with the pairwise subject; no debug tracing remains in the fixture.
|
|
395
|
+
|
|
396
|
+
### Long JWT introspection security regression
|
|
397
|
+
|
|
398
|
+
The pairwise mTLS JWT fixture now rejects four otherwise plausible token inputs:
|
|
399
|
+
a foreign signing key, an unsigned JWT, an OP-signed changed subject, and an
|
|
400
|
+
OP-signed unknown CIBA issuance identifier. Each authenticated introspection
|
|
401
|
+
returns active=false. Missing and foreign caller certificates return 401 even
|
|
402
|
+
for the valid long token. These checks exercise actual HTTP routes and the
|
|
403
|
+
stored issuance boundary, not only JWT decoding.
|
|
404
|
+
|
|
405
|
+
A separate request-parameter check confirms that the CIBA marker only permits
|
|
406
|
+
up to 8192 bytes at introspection's token parameter. An 8193-byte candidate,
|
|
407
|
+
a non-CIBA 1025-byte string, the token parameter at another route, and an
|
|
408
|
+
oversized client_secret still yield no parameter value. The fabricated marker
|
|
409
|
+
in this parser test is not authenticated; the route tests above establish that
|
|
410
|
+
selecting the larger parsing limit does not authorize a token or caller.
|
|
411
|
+
|
|
412
|
+
[Local regression](../validation/pairwise-mtls-introspection-security.txt):
|
|
413
|
+
3 tests, 45 assertions, no failures/errors/skips. No runtime change was needed.
|
|
414
|
+
This is not a new three-database or full-suite execution.
|
|
415
|
+
|
|
416
|
+
## Real TLS aliases with certificate authentication and DPoP tokens
|
|
417
|
+
|
|
418
|
+
The previously unverified combination now has a complete real-TLS flow in the
|
|
419
|
+
pinned reference OP and installed gem. A separate registered client uses either
|
|
420
|
+
`tls_client_auth` or `self_signed_tls_client_auth`, requires DPoP-bound access
|
|
421
|
+
tokens, and explicitly does not require certificate-bound access tokens.
|
|
422
|
+
These are separate responsibilities: the certificate authenticates the client;
|
|
423
|
+
the DPoP key constrains access-token use.
|
|
424
|
+
|
|
425
|
+
Both fixtures discover application-owned aliases on a second TLS listener while
|
|
426
|
+
retaining the canonical issuer. They verify:
|
|
427
|
+
|
|
428
|
+
- initiation, approval and token collection through the alias;
|
|
429
|
+
- rejection of missing client certificates and missing DPoP proofs at collection;
|
|
430
|
+
- rejection of a proof whose htu names the canonical endpoint instead of the alias;
|
|
431
|
+
- signed ID Tokens with canonical issuer/client audience and no inherited cnf;
|
|
432
|
+
- introspection returning exactly the DPoP jkt, with no x5t#S256 binding;
|
|
433
|
+
- UserInfo rejecting a Bearer request even with the certificate, and accepting a
|
|
434
|
+
DPoP proof without a client certificate;
|
|
435
|
+
- refresh requiring both client certificate authentication and DPoP, and retaining
|
|
436
|
+
the exact expected DPoP-key confirmation in the new access token.
|
|
437
|
+
|
|
438
|
+
The gem fixture additionally runs both opaque and JWT access-token modes and keeps
|
|
439
|
+
upstream's global certificate-binding setting enabled, proving the explicit CIBA
|
|
440
|
+
client binding policy remains authoritative. Its four combinations use actual
|
|
441
|
+
WEBrick TLS peer certificates and trusted server certificates, not forwarded
|
|
442
|
+
certificate assertions. UserInfo uses GET in this integration fixture.
|
|
443
|
+
|
|
444
|
+
[Reference execution](../validation/node-mtls-dpop-alias.txt),
|
|
445
|
+
[installed gem execution](../validation/mtls-dpop-alias-installed.txt).
|
|
446
|
+
No runtime change was needed after the earlier DPoP endpoint-URI correction.
|
|
447
|
+
This closes the concrete combined-alias coverage gap. Production reverse proxies,
|
|
448
|
+
cluster nonce/key distribution and arbitrary application trust policies are not
|
|
449
|
+
established by this local integration test.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# CIBA lifecycle comparison against node-oidc-provider v9.12.2
|
|
2
|
+
|
|
3
|
+
2026-09-30. GitHub connector retrieval succeeded after shell DNS and Web cache failures. The earlier source-access limitation is resolved for the start route, grant handler and request model. The initial analysis below used source inspection and an isolated harness; a subsequent [complete HTTP OP test](../../test/reference/full-op/README.md) now verifies the listed lifecycle scenarios with actual signing and the built-in memory adapter on Node24 LTS.
|
|
4
|
+
|
|
5
|
+
The unchanged CIBA handler and source helper now also execute in [an isolated reference harness](../../test/reference/README.md). `node --experimental-vm-modules test/reference/lifecycle.mjs` passes on Node v26.5.0; [log](../validation/node-reference-lifecycle.txt). With non-transactional in-memory persistence, issuance failure leaves consumption intact and the next poll revokes the Grant and returns invalid_grant. Cryptography, actual token persistence and global HTTP middleware are stubbed/absent. This confirms local control-flow behavior only.
|
|
6
|
+
|
|
7
|
+
## Confirmed ordering
|
|
8
|
+
|
|
9
|
+
- [Start route](https://github.com/panva/node-oidc-provider/blob/v9.12.2/lib/actions/authorization/ciba.js): save the BackchannelAuthenticationRequest, prepare auth_req_id/expires_in, await triggerAuthenticationDevice. There is no compensating deletion in this function if the trigger throws. The gem now uses the same persistence-before-trigger boundary; application-owned outer transactions remain a Sequel-specific concern.
|
|
10
|
+
- [CIBA grant handler](https://github.com/panva/node-oidc-provider/blob/v9.12.2/lib/actions/grants/ciba.js): find a client-bound request; check expiry; return authorization_pending when neither grant nor error exists; consume; propagate a saved result error; validate the Grant; issue tokens.
|
|
11
|
+
- [Source helper](https://github.com/panva/node-oidc-provider/blob/v9.12.2/lib/helpers/grant_source.js): consuming an already consumed source revokes its Grant and throws invalid_grant. Grant lookup checks existence, expiry and client identity.
|
|
12
|
+
- [Request model](https://github.com/panva/node-oidc-provider/blob/v9.12.2/lib/models/backchannel_authentication_request.js): consume invokes adapter.consume and emits consumed. No consumable mixin is needed for this model; the previously guessed mixin path returned 404.
|
|
13
|
+
- [Common issuance](https://github.com/panva/node-oidc-provider/blob/v9.12.2/lib/helpers/grant_common.js): validates the account and Grant/account match, saves the access token, optionally saves a refresh token, then generates the ID Token and response.
|
|
14
|
+
|
|
15
|
+
## Implemented alignment
|
|
16
|
+
|
|
17
|
+
The gem now checks request expiry before consumed status. Replaying an unexpired consumed approved request revokes its saved CIBA consent and linked OAuth token rows, then returns invalid_grant. A denied result is consumed on first collection (access_denied); a second collection returns invalid_grant. Trusted Ruby completion retries remain idempotent and do not themselves replay token collection.
|
|
18
|
+
|
|
19
|
+
Tests: test_replayed_result_revokes_its_saved_consent_and_tokens, test_denied_result_is_consumed_on_first_collection, test_expiry_precedes_consumed_result_check.
|
|
20
|
+
|
|
21
|
+
Revocation is scoped by the saved consent ID: reusing one consent across multiple requests means replay can revoke all tokens linked to that consent. Offline JWT verification cannot observe a database revocation without an application/resource-server mechanism.
|
|
22
|
+
|
|
23
|
+
## Deliberate differences and remaining gaps
|
|
24
|
+
|
|
25
|
+
- The node start function omits interval and the CIBA grant handler has no slow_down branch. The gem advertises and enforces an increasing interval. This statement is limited to these functions; it does not prove absence of all application/server rate limiting.
|
|
26
|
+
- node consumes before result-error propagation and token generation. The gem deliberately rolls back consumption and token rows if issuance/signing fails, per [ADR 0004](../adr/0004-retain-atomic-issuance-and-stage-alignment.md). Atomicity of an application's node adapter is not established by reading these functions.
|
|
27
|
+
- node accepts all three hint mechanisms and invokes application user-code validation. Initial gem scope remains login_hint only; broader capabilities are tracked in the accepted [staged roadmap](../alignment-roadmap.md).
|
|
28
|
+
- node persists claims, nonce, resource, params and RAR-related state. The gem now persists nonce and optionally requested/approved/rejected claims. Full claims constraint policy, resource and RAR remain gaps.
|
|
29
|
+
- node propagates OIDC result errors; the gem now supports generic error codes with one-use collection and explicit storage migration (see completion-error-boundaries.md).
|
|
30
|
+
- Complete node HTTP lifecycle scenarios and the gem's SQLite/PostgreSQL/MySQL matrix now pass. Production node adapters, process crashes and application-specific response-loss recovery remain unverified.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# node-oidc-provider と rodauth-ciba の比較
|
|
2
|
+
|
|
3
|
+
後続調査で専用ソースをGitHub connectorから取得できた。最新の処理順序・差分は[ライフサイクル比較](node-ciba-lifecycle.md)を参照。以下は取得前の比較記録。
|
|
4
|
+
|
|
5
|
+
調査日: 2026-09-30。この比較は整合化作業前のスナップショット。以後の変更は[整合化作業](../node-alignment.md)を参照。結論: アプリが本人確認と承認UIを担当し、内部APIでOPへ結果を渡す分担は共通。node側は既存OIDCの認可モデル・拡張機能を広く利用し、gem側はpoll/login_hintと同一DB内の状態遷移に範囲を絞っている。機能数だけでは適合性・安全性を比較できない。
|
|
6
|
+
|
|
7
|
+
比較対象は公開時点でlatestと表示された [node-oidc-provider v9.12.2](https://github.com/panva/node-oidc-provider/releases/tag/v9.12.2) と、この作業ツリーのrodauth-ciba 0.1.0。`lib/rodauth/features/ciba.rb` のSHA-256は `f4336c948a135f3682d08eb292408eaaa168edfff13f8702a21c7af46e3d2121`。
|
|
8
|
+
|
|
9
|
+
これはソース・設定・APIの比較であり、相互接続試験ではない。shellからGitHubをcloneできず、Web経由で取得できたタグ固定の一次資料を読んだ。v9.12.2の `actions/authorization/ciba.js` と `actions/grants/ciba.js` は取得できていない。mainのgrantファイルも参照したが、タグとの差を保証できないため、そこだけからv9.12.2の挙動を断定しない。特にhint全種の検証詳細、slow_down、発行失敗後の再試行、JWT認証の全負例は今回の比較では未確定。
|
|
10
|
+
|
|
11
|
+
| 観点 | node-oidc-provider | このgem | 判断 |
|
|
12
|
+
|---|---|---|---|
|
|
13
|
+
| 配送 | poll / ping。既定poll、pushは設定検証で拒否 | pollのみ | poll初版を維持してよい。[設定検証](https://github.com/panva/node-oidc-provider/blob/v9.12.2/lib/helpers/configuration.js#L607) |
|
|
14
|
+
| アプリとの境界 | `triggerAuthenticationDevice` と `backchannelResult` | `ciba_request_accepted` と `approve_ciba_request` / `deny_ciba_request` | 内部APIという方向は共通。どちらも業務用の承認HTTP APIをこの境界で標準化するものではない。[node設定](https://github.com/panva/node-oidc-provider/blob/v9.12.2/lib/helpers/defaults.js#L1116) |
|
|
15
|
+
| 承認結果 | 保存済みGrantまたはエラー。account/client一致を確認 | account一致を確認し、要求を承認/拒否 | nodeのGrantはscope・claim・resourceごとの許可/拒否を表現できる。gemの承認APIにはscopeを減らす引数がない。[結果API](https://github.com/panva/node-oidc-provider/blob/v9.12.2/lib/provider.js#L239)、[Grant](https://github.com/panva/node-oidc-provider/blob/v9.12.2/lib/models/grant.js) |
|
|
16
|
+
| hint / user_code | login_hintとlogin_hint_tokenの解決、user_code検証をアプリ用callbackとして提供 | login_hintのみ解決を差替え可能。他は入口で拒否 | 対応範囲の差。nodeのcallbackもアプリ実装が必要で、任意のトークン形式を自動で検証するわけではない。[callback定義](https://github.com/panva/node-oidc-provider/blob/v9.12.2/lib/helpers/defaults.js#L527) |
|
|
17
|
+
| 署名付き要求 | request-object処理をCIBAにも接続 | 明示的に未対応 | JWTクライアント認証とは別機能。初版へ自動追加しない。[処理スタック](https://github.com/panva/node-oidc-provider/blob/v9.12.2/lib/actions/authorization/index.js#L90) |
|
|
18
|
+
| クライアント登録 / subject | 共通schemaにCIBA metadata・pairwise向け認証条件がある | 静的登録・public subjectのみ。CIBA DCR拒否 | 公開OSSとしての拡張余地だが初期用途の必須条件ではない。[client schema](https://github.com/panva/node-oidc-provider/blob/v9.12.2/lib/helpers/client_schema.js#L133) |
|
|
19
|
+
| Refresh Token | 共通発行処理にRefresh Token生成とpolicy判断がある | offline_accessを除外し、CIBA refreshを発行しない | 長期アクセスが必要になった時点で再検討。nodeの全CIBA設定で必ず発行されるとは言わない。[共通発行処理](https://github.com/panva/node-oidc-provider/blob/v9.12.2/lib/helpers/grant_common.js#L95) |
|
|
20
|
+
| 詳細な認可要求 | CIBA開始スタックにclaims / resource / RARの処理を接続 | CIBA専用処理ではscope等の固定項目を保存 | 業務操作の詳細は現在アプリが保持する必要がある。[処理スタック](https://github.com/panva/node-oidc-provider/blob/v9.12.2/lib/actions/authorization/index.js#L114) |
|
|
21
|
+
| binding_message | 既定は限定文字集合・1〜20文字。検証callbackあり | 制御文字を除くUTF-8、最大128bytes | ライブラリの方針差。nodeの文字数をCoreの必須条件として移植しない。[検証既定値](https://github.com/panva/node-oidc-provider/blob/v9.12.2/lib/helpers/defaults.js#L546) |
|
|
22
|
+
| 永続化 | モデル単位のadapter。既定in-memory、本番用adapterは導入側 | Sequel、既存OAuthと同じDB | gem側のDB選択は単純で妥当。汎用adapterを追加する理由にはならない。[adapter契約](https://github.com/panva/node-oidc-provider/blob/v9.12.2/example/my_adapter.js) |
|
|
23
|
+
| イベント | Provider event emitter。モデル保存後にsaved等をemit | transaction内のbefore/afterと、outer commit後の例外隔離した観測を分離 | nodeのイベントを「DB commit後・失敗しても認証へ無影響」と同一視しない。[保存・emit](https://github.com/panva/node-oidc-provider/blob/v9.12.2/lib/models/base_model.js#L71)、[イベント一覧](https://github.com/panva/node-oidc-provider/blob/v9.12.2/docs/events.md) |
|
|
24
|
+
| 適合性の証拠 | READMEにFAPI CIBA認定を記載 | 宣言した範囲のローカルテスト、認定なし | 実績には差がある。ただしnodeの任意設定が認定済みになるわけではない。[README](https://github.com/panva/node-oidc-provider/blob/v9.12.2/README.md#certification) |
|
|
25
|
+
|
|
26
|
+
gem側の根拠は [実装](../../lib/rodauth/features/ciba.rb)、[API契約](../api.md)、[テスト対応表](../protocol-coverage.md)、[直近のレビュー結果](../security-review.md)。最後の実行記録はRuby 4.0.6 / SQLiteで76 tests / 563 assertions。今回はコード変更・テスト再実行をしていない。
|
|
27
|
+
|
|
28
|
+
特に参考になるのは、nodeが「認証要求」と「許可した内容」を別のモデルで扱う点。gemは受付時に許可候補scopeを保存し、承認時には全体を承認する。これは初版の単純な承認には合う。一部承認が必要なら、まず `approved_scopes` 相当の小さなAPIを検討できるが、その際は受付scopeの部分集合・openid維持・保存後の変更禁止・再試行一致・Token応答のscopeを一緒に検証する必要がある。nodeのGrant体系全体の移植は不要。
|
|
29
|
+
|
|
30
|
+
今回の「誰が、どの顧客の、何の操作を承認したか」には、scopeだけでは足りない。これは両実装の機能数とは別のアプリ設計問題。現行gemの `after_ciba_request` で、内部request IDに紐付く操作対象・実行者・範囲・期限のスナップショットを同じDBへ保存する案が使える。承認画面と実行側が同じ内容を参照し、後から内容を差し替えられないことを確認する。RARを初版へ追加する前に、この既存フックで足りるかを試すのが妥当。
|
|
31
|
+
|
|
32
|
+
状態遷移は、単にnodeへ揃えるべきではない。nodeの `backchannelResult` はGrantとのclient/account一致を確認して要求を保存するが、読んだメソッド内にはgemと同じ「同一結果なら冪等、異なる結果ならConflict」という分岐はない。一方gemは期限・完了済み状態・認証contextを確認し、行ロックと状態versionで変更を直列化する。node側の全呼出経路・adapterを含めた競合試験はしておらず、これを脆弱性の認定には使わない。[node結果API](https://github.com/panva/node-oidc-provider/blob/v9.12.2/lib/provider.js#L239)
|
|
33
|
+
|
|
34
|
+
また、nodeの共通消費処理は消費済みsourceを拒否し、関連Grantのrevokeを呼ぶ。adapterにはfind/upsert/consume等の契約があるが、要求消費と複数トークン保存を一つのDB transactionで確定する保証は、この契約だけからは読み取れない。gemはそれを自前でまとめ、署名失敗時のrollbackと再試行をテストしている。nodeより安全という結論ではなく、保証を置く場所が異なるという比較である。[消費処理](https://github.com/panva/node-oidc-provider/blob/v9.12.2/lib/helpers/grant_source.js#L21)、[adapter契約](https://github.com/panva/node-oidc-provider/blob/v9.12.2/example/my_adapter.js#L194)
|
|
35
|
+
|
|
36
|
+
再利用防止でも、nodeの `ReplayDetection.unique` はfindしてからsaveするモデル。gemはclient IDとjtiの組をhash化してunique insertする。nodeのモデルをそのままコピーして、gemのDB unique制約を弱める理由はない。ただしnodeでの同時リクエストの可否はadapter・呼出経路を含めて未検証。[ReplayDetection](https://github.com/panva/node-oidc-provider/blob/v9.12.2/lib/models/replay_detection.js)
|
|
37
|
+
|
|
38
|
+
通知失敗の扱いにも注意する。nodeの設定ではAD起動helperを要求受付後・応答前に呼ぶ。gemの受付callbackはcommit後で、例外でも成立済みの受付を変えない。gemでは通知漏れが起きてもpending要求の走査で回復できるが、本番では同一DBへのoutbox保存と配送workerをアプリ側で用意するのが候補になる。観測イベント自体を配送保証とみなさない。pingについてnodeは結果保存後にHTTP通知し、200/204を受理する実装であり、読んだメソッドには永続的な再送queueはない。[ping処理](https://github.com/panva/node-oidc-provider/blob/v9.12.2/lib/models/client.js#L379)
|
|
39
|
+
|
|
40
|
+
次に行うなら、機能の横並び化より、次の順序を推奨する。
|
|
41
|
+
|
|
42
|
+
1. 初期アプリで「承認内容の固定」と「通知失敗からの復旧」を既存のtransactional hook・内部IDで確認する。
|
|
43
|
+
2. gemは現行のpoll/login_hint範囲を維持し、未実施のPostgreSQL/MySQL検証を完了する。
|
|
44
|
+
3. nodeを動かせる環境で、同じクライアント要求を両OPに送り、開始・拒否・期限・二重引換え・再送を比較する。返却文言の完全一致ではなく、仕様と各実装の宣言した方針に沿って判定する。
|
|
45
|
+
4. 一部scope承認や別hintの具体的な利用者が現れた場合に、その機能だけを追加検討する。
|