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,52 @@
|
|
|
1
|
+
# CIBA outbound HTTP boundary
|
|
2
|
+
|
|
3
|
+
Signed CIBA request JWKS fetching and protected CIBA-client assertion JWKS
|
|
4
|
+
fetching use a dedicated transport boundary while retaining upstream's
|
|
5
|
+
registered-key lookup and cache. Unrelated OAuth clients keep their existing
|
|
6
|
+
HTTP behavior. Optional ping uses the same boundary with its own
|
|
7
|
+
HTTPS endpoint validation.
|
|
8
|
+
|
|
9
|
+
The default `ciba_http_address_allowed?(address)` rejects special-use address
|
|
10
|
+
ranges, including private/loopback/link-local addresses, multicast and mapped
|
|
11
|
+
IPv4 equivalents. IPAddr parses addresses; text prefixes are not trusted.
|
|
12
|
+
Every DNS answer must pass policy. The connection is pinned to the first
|
|
13
|
+
validated address via Net::HTTP#ipaddr while retaining the original hostname
|
|
14
|
+
for Host and TLS verification. Ambient HTTP proxies are disabled so they cannot
|
|
15
|
+
bypass that destination check. The shared request primitive does not follow
|
|
16
|
+
redirects. Sector-document fetching explicitly follows up to 20 redirects,
|
|
17
|
+
repeating the same destination checks at every hop under one 2.5-second deadline.
|
|
18
|
+
Ping and JWKS fetching retain their existing refusal to follow redirects.
|
|
19
|
+
|
|
20
|
+
For JWKS and sector documents the transport bounds response bodies to 65536 bytes.
|
|
21
|
+
Ping needs only the response status: it closes the connection immediately after
|
|
22
|
+
headers without reading or draining the body. The transport uses a 2.5-second outer
|
|
23
|
+
Ruby timeout, as well as connection/read/write timeouts. Registered JWKS URIs
|
|
24
|
+
retain upstream's HTTP/HTTPS scheme support; production registrations should
|
|
25
|
+
use HTTPS. Userinfo, fragments and other URI schemes are rejected. Transport
|
|
26
|
+
validation failures are invalid_request for signed CIBA authentication requests
|
|
27
|
+
and invalid_client for JWT client authentication. They do not save pending work.
|
|
28
|
+
|
|
29
|
+
If an application deliberately needs an internal endpoint, override
|
|
30
|
+
`ciba_http_address_allowed?` narrowly for that deployment. Returning true allows
|
|
31
|
+
that resolved address. There is no default private-network exception. The
|
|
32
|
+
loopback JWKS fixture overrides only 127.0.0.1 on its test app; this is not a
|
|
33
|
+
production recommendation. Operators must account for the separate risk of
|
|
34
|
+
which clients may register destinations, and retain their egress controls.
|
|
35
|
+
|
|
36
|
+
Compared with node's connected-socket guard, the gem validates all DNS answers
|
|
37
|
+
and pins one before connection. It additionally rejects multicast and
|
|
38
|
+
IPv4-compatible IPv6. It does not implement multi-address connection fallback.
|
|
39
|
+
The allow-policy applies when a network lookup happens; an already cached JWKS
|
|
40
|
+
remains usable until upstream cache invalidation/expiry. Network refusal alone
|
|
41
|
+
is therefore not a key-revocation mechanism.
|
|
42
|
+
|
|
43
|
+
Tests cover IP ranges/mapped forms, default loopback refusal before any receiver
|
|
44
|
+
request, mixed DNS answers, hostname/TLS configuration and IP pinning, real
|
|
45
|
+
loopback cache/rotation/error behavior under a narrow test exception, and an
|
|
46
|
+
oversized response. The DNS pinning test inspects the configured Net::HTTP
|
|
47
|
+
client; it is not an adversarial live DNS-rebinding experiment. Installed-gem
|
|
48
|
+
ping tests directly verify untrusted TLS rejection and a stalled receiver's
|
|
49
|
+
timeout; saved approval survives. Hostname mismatch and expired certificates
|
|
50
|
+
remain unverified.
|
|
51
|
+
The independent node ping harness separately verifies real TLS notifications
|
|
52
|
+
and default loopback rejection; do not transfer that evidence to the gem.
|
data/docs/pairwise.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Optional pairwise CIBA subjects (integration in progress)
|
|
2
|
+
|
|
3
|
+
Pairwise is off by default. First run the explicit migration
|
|
4
|
+
`Rodauth::CibaSupport::Schema.add_pairwise_subject(db)` to add nullable
|
|
5
|
+
`oauth_grants.ciba_subject` (custom table/column supported; configure
|
|
6
|
+
`ciba_token_subject_column` for a renamed column). Enable `ciba_pairwise_enabled true` only together
|
|
7
|
+
with `ciba_pairwise_identifier(account_id, oauth_application:, sector_identifier:)`.
|
|
8
|
+
The callback must return a stable, nonempty ASCII subject of at most 255 bytes.
|
|
9
|
+
The application owns its secret/persistence lifecycle and sector/account mapping;
|
|
10
|
+
the gem does not supply a default hash or a reversible identifier store.
|
|
11
|
+
|
|
12
|
+
This integration supports private_key_jwt and self_signed_tls_client_auth clients
|
|
13
|
+
with registered jwks_uri, and no simultaneous inline jwks. Enable upstream
|
|
14
|
+
`:oauth_jwt_bearer_grant` and create its CIBA replay ledger with
|
|
15
|
+
`Schema.create_client_assertions(db)` if not already migrated. The standard client columns jwks_uri,
|
|
16
|
+
sector_identifier_uri, response_types and redirect_uri must be available where
|
|
17
|
+
used. Existing public clients retain their behavior. Self-signed TLS clients
|
|
18
|
+
require upstream `oauth_tls_client_auth` plus the explicit trusted certificate
|
|
19
|
+
callback described in the [mTLS contract](research/mtls-reference-contract.md).
|
|
20
|
+
The peer certificate must match x5c from the registered jwks_uri; PKI TLS
|
|
21
|
+
authentication alone is not a proof of that key ownership. Signed-request-only
|
|
22
|
+
proof with another client authentication method is rejected, matching the pinned
|
|
23
|
+
node-oidc-provider registration policy even when signed requests are enabled.
|
|
24
|
+
This is a shared policy restriction, not a remaining reference-alignment gap.
|
|
25
|
+
|
|
26
|
+
For CIBA-only clients with no browser response_types, absent sector_identifier_uri
|
|
27
|
+
means the sector is jwks_uri's host including a nondefault port. An explicit
|
|
28
|
+
sector_identifier_uri must be HTTPS. Browser response_types require that explicit
|
|
29
|
+
URI. Its HTTP 200 JSON array must include jwks_uri and, for browser clients, all
|
|
30
|
+
redirect_uris. Fetching uses the bounded address-checked outbound transport;
|
|
31
|
+
successful validation is cached per Rodauth application class using a digest of
|
|
32
|
+
the complete client row, with a 100-entry LRU bound and no time-based expiry.
|
|
33
|
+
Static and dynamically stored clients validate on a cache miss. Registration
|
|
34
|
+
and management replacement always fetch afresh, even for unchanged metadata.
|
|
35
|
+
Failures are not cached; an unsuccessful replacement does not erase the old
|
|
36
|
+
client's valid cache entry. Network I/O occurs outside the cache mutex, so
|
|
37
|
+
concurrent cold lookups may fetch twice. Processes do not share the cache.
|
|
38
|
+
This does not yet implement node's configurable sector-validation bypass policy.
|
|
39
|
+
|
|
40
|
+
Only the emitted subject is pairwise: saved requests, consent and OAuth grants
|
|
41
|
+
retain the canonical account ID. UserInfo must return the same pairwise subject
|
|
42
|
+
as ID Token. With pairwise enabled, new CIBA issuances snapshot their subject in that token
|
|
43
|
+
row. JWT UserInfo checks the signed subject against this snapshot and the bound
|
|
44
|
+
client before using the internal account for lookup. After a management sector
|
|
45
|
+
change, old tokens can therefore authenticate while UserInfo emits the new
|
|
46
|
+
sector subject, as measured with node opaque tokens. Refresh also uses the new
|
|
47
|
+
sector, and already-issued JWT bytes remain unchanged. Older rows with no
|
|
48
|
+
snapshot retain the prior current-subject check; a sector change can invalidate
|
|
49
|
+
those old JWTs. The migration does not invent historical subjects.
|
|
50
|
+
|
|
51
|
+
For id_token_hint, configure the existing `resolve_ciba_id_token_subject` callback
|
|
52
|
+
to recover the canonical account in the authenticated client's context. The gem
|
|
53
|
+
first verifies the ID Token's issuer, audience and signature; the hint then starts
|
|
54
|
+
a new request requiring approval. Do not treat a hashed subject as a new account.
|
|
55
|
+
|
|
56
|
+
Current tests cover opaque/JWT access tokens with verified ID Token, UserInfo,
|
|
57
|
+
app-resolved ID hint, internal identity, same/different sector derivation,
|
|
58
|
+
dynamic registration constraints and stubbed sector document membership. Opaque/JWT refresh also verifies subject and internal-account preservation,
|
|
59
|
+
rotation/replay, wrong-client refusal, and rollback on invalid identifier results.
|
|
60
|
+
Installed-gem tests now fetch sector documents over verified HTTPS and reject
|
|
61
|
+
untrusted certificates, invalid membership/JSON, oversize and HTTP errors without
|
|
62
|
+
persistence or forwarding the registration bearer. Sector fetching now follows 301/302/303/307/308 (up to 20 redirects), validating
|
|
63
|
+
each destination with the outbound policy and limiting the entire chain to
|
|
64
|
+
2.5 seconds. The measured same-origin HTTPS 302 case agrees with node. Existing-token UserInfo and refresh after a management sector change are now
|
|
65
|
+
verified for opaque/JWT tokens. Pending requests retain their canonical account and unapproved state across a
|
|
66
|
+
sector update; subsequent approved issuance uses the new sector subject. The
|
|
67
|
+
installed-gem TLS fixture now covers hybrid DCR, real remote-key authentication,
|
|
68
|
+
approval, verified ID Token, saved subject and matching UserInfo. Concurrent
|
|
69
|
+
sector management/issuance tests now verify one issuance subject across storage
|
|
70
|
+
and tokens, followed by current-sector UserInfo, on all three DBs. PostgreSQL and
|
|
71
|
+
MySQL force the update to commit while issuance is paused; SQLite tests writer
|
|
72
|
+
contention and either serial order. Concurrent refresh/key changes, arbitrary
|
|
73
|
+
application hooks and additional deployment combinations remain unverified.
|
|
74
|
+
This is not a completed pairwise parity or release-readiness claim.
|
|
75
|
+
|
|
76
|
+
[Reference contract](research/pairwise-reference-contract.md).
|
data/docs/ping.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Optional ping delivery
|
|
2
|
+
|
|
3
|
+
Run the explicit migration and enable the capability:
|
|
4
|
+
|
|
5
|
+
```ruby
|
|
6
|
+
Rodauth::CibaSupport::Schema.add_ping(db)
|
|
7
|
+
# Rodauth configuration:
|
|
8
|
+
ciba_ping_enabled true
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Register each ping client with backchannel_token_delivery_mode = ping and an
|
|
12
|
+
HTTPS backchannel_client_notification_endpoint. Poll clients remain supported.
|
|
13
|
+
Discovery advertises poll/ping only while enabled. The migration adds the client
|
|
14
|
+
endpoint column and a separate ciba_ping_deliveries table, leaving existing
|
|
15
|
+
requests and grants intact. Custom migration keywords: applications_table,
|
|
16
|
+
endpoint_column, table, requests_table, request_key. Configure the corresponding
|
|
17
|
+
Rodauth endpoint-column and ciba_ping_deliveries_table settings when renamed.
|
|
18
|
+
|
|
19
|
+
The initiation request must provide a nonempty client_notification_token using
|
|
20
|
+
Bearer token characters, at most 1024 bytes. Entropy is the client's responsibility.
|
|
21
|
+
Clients must generate at least128 bits of entropy (160 is recommended). The gem
|
|
22
|
+
validates syntax/length, not the randomness of a supplied token. The application
|
|
23
|
+
should verify that the registered notification endpoint is controlled by the
|
|
24
|
+
client; HTTPS validation and destination filtering alone do not prove ownership.
|
|
25
|
+
The receiving client must check the notification bearer and its association
|
|
26
|
+
with auth_req_id before collecting tokens. An OP-side send test does not prove
|
|
27
|
+
that receiver behavior. See [CIBA Core §§7.1,10.2,14](https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html).
|
|
28
|
+
Poll ignores this parameter. Signed requests use only its signed inner value.
|
|
29
|
+
Invalid metadata, userinfo/fragments or non-HTTPS endpoints cannot enable a ping
|
|
30
|
+
client. Network destination checks apply at send time using the
|
|
31
|
+
[CIBA HTTP boundary](outbound-http.md), which rejects special-use destinations
|
|
32
|
+
by default. There are no redirects, ambient proxies or automatic retries.
|
|
33
|
+
|
|
34
|
+
## Persistence and API behavior
|
|
35
|
+
|
|
36
|
+
The private delivery table stores request_id, original auth_req_id, the
|
|
37
|
+
notification token and the registered endpoint at acceptance. The endpoint column
|
|
38
|
+
is retained as acceptance-time data for schema compatibility; dispatch uses the
|
|
39
|
+
current client registration, not that saved endpoint. Unlike the
|
|
40
|
+
request table's hashed identifier, the original identifier is needed to send
|
|
41
|
+
the notification. These fields are recoverable plaintext database values;
|
|
42
|
+
protect access/backups and apply deployment encryption controls as appropriate.
|
|
43
|
+
They are excluded from public request APIs, state-change hook snapshots, device
|
|
44
|
+
snapshots and observation events. The normal request cleanup cascades deletion
|
|
45
|
+
to delivery data. Credentials remain until request cleanup, including after
|
|
46
|
+
successful delivery/collection, to avoid inventing a separate retention system.
|
|
47
|
+
|
|
48
|
+
Approval/denial commits before notification. An outer application transaction
|
|
49
|
+
delays notification until its outermost commit; rollback sends nothing. A
|
|
50
|
+
successful send returns normally from completion. Network/non-200-or-204 errors
|
|
51
|
+
raise PingDeliveryError **after** approval/denial is saved. If completion happens
|
|
52
|
+
inside an outer transaction, that error arises from the outer commit. It does
|
|
53
|
+
not mean the result rolled back. Observers remain independent best-effort hooks;
|
|
54
|
+
notification failures are not suppressed as observation failures.
|
|
55
|
+
|
|
56
|
+
`retry_ciba_ping(request_id)` sends separately from completion and returns
|
|
57
|
+
`:delivered` or `:not_deliverable` for pending, consumed, expired or non-ping
|
|
58
|
+
requests. Unknown IDs raise NotFound. It refuses execution inside a transaction.
|
|
59
|
+
Each send uses the client's current registered HTTPS notification endpoint,
|
|
60
|
+
matching node-oidc-provider's freshly loaded Client behavior. An authenticated
|
|
61
|
+
registration-management update can therefore redirect subsequent notifications,
|
|
62
|
+
including their saved notification credential, to the replacement endpoint.
|
|
63
|
+
A client that no longer supports ping or has invalid metadata raises Ineligible.
|
|
64
|
+
A request already constructed for dispatch keeps its selected endpoint even if
|
|
65
|
+
management updates the registration during delivery; updates do not cancel
|
|
66
|
+
in-flight network operations. Repeated identical completion retains the
|
|
67
|
+
existing idempotent completion behavior; use the retry API to resend.
|
|
68
|
+
|
|
69
|
+
Delivery is a POST with Bearer authentication and a JSON body containing only
|
|
70
|
+
auth_req_id. Approval and denial have the same notification shape. The client
|
|
71
|
+
authenticates separately to obtain tokens or the denial result. Failure to
|
|
72
|
+
deliver does not prevent retrieval of an already saved result.
|
|
73
|
+
|
|
74
|
+
There is no durable automatic retry scheduler or exactly-once claim. An
|
|
75
|
+
application may schedule explicit retries. Concurrent retries can send duplicate
|
|
76
|
+
notifications, and a result can be consumed/expire while a send is in flight.
|
|
77
|
+
The receiver must deduplicate notifications by identifier before collecting
|
|
78
|
+
tokens; fetching twice would trigger replay revocation. Process death after
|
|
79
|
+
commit and before send is another reason to use application-managed retries.
|
|
80
|
+
|
|
81
|
+
## Evidence and remaining work
|
|
82
|
+
|
|
83
|
+
Integration tests cover approval/denial, exact payload, private snapshots,
|
|
84
|
+
post-commit delivery, rollback, failed-send persistence, retry, expiry, metadata,
|
|
85
|
+
endpoint changes and cascading cleanup. The installed-gem ping smoke uses a
|
|
86
|
+
real HTTPS receiver with a temporary CA and an exact loopback policy exception;
|
|
87
|
+
TLS verification stays enabled. It tests 200/204, 503/302, a stalled response,
|
|
88
|
+
retry and collection. A separate process without trust in the test CA rejects
|
|
89
|
+
TLS before credentials reach the receiver while retaining the saved result.
|
|
90
|
+
The independent [node comparison](research/ping-reference-contract.md) establishes
|
|
91
|
+
the reference save-before-send behavior.
|
|
92
|
+
|
|
93
|
+
Barrier-based tests hold automatic delivery open while another DB connection
|
|
94
|
+
collects tokens, and hold two explicit retries open during collection. There is
|
|
95
|
+
no active DB transaction in the send callback; one token issuance occurs, both
|
|
96
|
+
in-flight notifications may finish, and subsequent retry is not_deliverable.
|
|
97
|
+
A duplicate token collection still revokes the grant. Signed requests with
|
|
98
|
+
user_code use the signed notification token only, ignoring outer replacements.
|
|
99
|
+
|
|
100
|
+
Process-crash recovery, multi-process delivery, hostname-mismatch/expired TLS
|
|
101
|
+
certificates and every feature combination remain unverified. These scenarios
|
|
102
|
+
do not establish full node parity or certification.
|
|
103
|
+
|
|
104
|
+
### Notification response bodies
|
|
105
|
+
|
|
106
|
+
A200 or204 status acknowledges delivery. The gem closes the response connection
|
|
107
|
+
without reading its body, so an ignored body cannot fail delivery because of its
|
|
108
|
+
size or a body-read timeout. TLS verification, endpoint/address validation,
|
|
109
|
+
redirect rejection and the header-receipt timeout remain enforced. Other HTTP
|
|
110
|
+
consumers (JWKS and sector documents) retain their bounded body reads.
|
|
111
|
+
|
|
112
|
+
[Protocol/transport tests](validation/ping-response-body-current.txt) include a
|
|
113
|
+
response declaring10MB but sending no body: the sender returns after headers
|
|
114
|
+
and the peer observes closure. The [installed TLS fixture](validation/ping-response-body-installed.txt)
|
|
115
|
+
and [reference TLS fixture](validation/node-ping-response-body.txt) both accept
|
|
116
|
+
a200 response containing140,000 ignored bytes. Response-header stalls and failed
|
|
117
|
+
statuses remain separate failures; successful headers do not require a complete
|
|
118
|
+
response body.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Protocol-to-test mapping
|
|
2
|
+
|
|
3
|
+
Push is not implemented by the selected node-oidc-provider 9.12.2 reference or
|
|
4
|
+
this gem. Its conditional requirements are retained as `reference_unsupported`,
|
|
5
|
+
not passing tests. [Measured boundary](research/push-reference-boundary.md).
|
|
6
|
+
|
|
7
|
+
Optional refresh/related revocation coverage is in `test/ciba_refresh_test.rb`,
|
|
8
|
+
`test/ciba_refresh_storage_test.rb`, `test/ciba_refresh_permissions_test.rb`,
|
|
9
|
+
`test/ciba_refresh_hooks_test.rb`, `test/ciba_access_revocation_test.rb` and
|
|
10
|
+
`test/ciba_lock_order_test.rb`. It includes scoped issuance, source persistence,
|
|
11
|
+
rotation/replay, current-consent filtering, transaction hooks, cleanup,
|
|
12
|
+
access-versus-refresh revocation, token provenance and legacy formats. The
|
|
13
|
+
installed extension smoke covers opaque/JWT combinations. See the
|
|
14
|
+
[reference boundaries](research/access-token-revocation-reference.md) before
|
|
15
|
+
interpreting these tests as full OAuth revocation or node behavior equivalence.
|
|
16
|
+
|
|
17
|
+
Implementation extension (not a CIBA Core requirement): optional nonce propagation is tested by `test_nonce_is_bound_to_start_request_not_poll_and_not_observed`, `test_nonce_is_optional_and_bounded`, `test_nonce_forward_migration_preserves_existing_request` and the complete node HTTP harness. These tests do not add a CIBA conformance requirement.
|
|
18
|
+
|
|
19
|
+
Additional optional capabilities have separate coverage: `test/ciba_claims_test.rb` tests explicit claim consent and UserInfo provenance; `test/ciba_resources_test.rb` tests per-resource scope/audience, omission callbacks, saved-consent and original-target binding, JWT/opaque UserInfo rejection, introspection, issuance rollback, concurrent target selection and claims composition. The full node OP harness independently covers comparable claims and resource scenarios. See [claims limits](claims.md) and [resource limits](resources.md); these are not a claim of full OIDC claims or RFC8707 conformance.
|
|
20
|
+
|
|
21
|
+
This is the release's requirement mapping, not a certification claim. Run `ruby test/run.rb`; method names below are in `test/ciba_*_test.rb` unless otherwise noted. The implementation covers the declared subset; omitted optional features are not counted as passing tests.
|
|
22
|
+
|
|
23
|
+
| Reference / concern | Test method(s) |
|
|
24
|
+
|---|---|
|
|
25
|
+
| CIBA §4: Discovery / poll metadata | `test_discovery_describes_poll_and_keeps_existing_oidc_metadata` |
|
|
26
|
+
| CIBA §7, §7.1: TLS, form POST, confidential authentication | `test_non_tls_and_json_requests_are_rejected`, `test_client_authentication_is_required_at_both_endpoints`, `test_post_client_authentication`, `test_basic_credentials_are_form_decoded_and_hashed_secrets_work`, `test_two_authentication_methods_are_rejected` |
|
|
27
|
+
| CIBA §7.1–7.2: hint, scope, unsupported features, unknown parameters | `test_request_validation_and_unknown_parameters` |
|
|
28
|
+
| Optional login_hint_token resolver, exclusive hints, account binding and raw-token exclusion | `test/ciba_hint_token_test.rb`; complete node HTTP comparison and installed-gem extension smoke |
|
|
29
|
+
| Optional id_token_hint signature/issuer/audience/time boundary, retained keys and new approval | `test/ciba_id_token_hint_test.rb`; complete node comparison; installed-gem extension smoke; [validation differences](id-token-hint.md) |
|
|
30
|
+
| Optional signed request metadata, client keys, required claims and inner-only parameters | `test/ciba_signed_request_test.rb`, `test/ciba_remote_jwks_test.rb`; complete node comparison and installed-gem signed requests; [limits](signed-requests.md) |
|
|
31
|
+
| Optional user_code application policy, missing/invalid errors, no raw persistence and new approval | `test/ciba_user_code_test.rb`; complete node comparison and installed-gem signed-request composition; [policy boundary](user-code.md) |
|
|
32
|
+
| Optional request_context application callback, persistence, signed input, failure boundaries and no automatic token claim | `test/ciba_request_context_test.rb`; complete node HTTP harness and installed extension smoke; [contract](request-context.md) |
|
|
33
|
+
| Optional ping: metadata, private delivery data, completion/notification ordering, retry, cleanup | `test/ciba_ping_test.rb`, installed-gem `examples/ping_smoke.rb`, independent node `test/reference/full-op/ping.mjs`; [limits](ping.md) |
|
|
34
|
+
| CIBA §7.1: binding / requested expiry / context | `test_expiry_binding_context_and_offline_access` |
|
|
35
|
+
| OAuth request parsing: repeated parameters / encoding / bounds | `test_duplicate_parameters_are_rejected`, `test_invalid_utf8_and_oversized_body_are_rejected`, `test_rack_inputs_without_rewind_work_on_both_endpoints` |
|
|
36
|
+
| CIBA §7.3: acknowledgement / no-store | `test_expiry_binding_context_and_offline_access`, `test_approved_request_issues_customer_id_token_once` |
|
|
37
|
+
| CIBA §10.1: client-bound request / subject | `test_other_identity_client_and_expiry_cannot_issue` |
|
|
38
|
+
| CIBA §10.1.1 / OIDC Core §2: signed ID Token, issuer/audience/context | `test_approved_request_issues_customer_id_token_once`, `test_id_token_audience_is_client_even_with_different_access_audience`, `test_missing_authentication_context_is_not_fabricated`, `test_unsigned_id_token_client_is_rejected` |
|
|
39
|
+
| CIBA §10.1.1: one successful redemption | `test_approved_request_issues_customer_id_token_once`, `test_concurrent_polls_issue_one_grant` |
|
|
40
|
+
| CIBA §11: pending / slow_down | `test_pending_poll_updates_survive_json_halt`, `test_concurrent_pending_polls_both_receive_retryable_results` |
|
|
41
|
+
| CIBA §11: denial / expiry | `test_denial_and_expired_completion`, `test_other_identity_client_and_expiry_cannot_issue`, `test_expiry_during_before_issue_rolls_back_and_returns_expired` |
|
|
42
|
+
| App completion contract / retry / identity | `test_approval_is_idempotent_and_context_conflicts_are_rejected`, `test_concurrent_approve_and_deny_choose_one_decision`, `test_client_and_account_changes_are_rechecked` |
|
|
43
|
+
| Signing and hook rollback / savepoints | `test_signing_failure_rolls_back_consumption_and_grant_then_allows_retry`, `test_hooks_see_before_and_after_states_and_failure_rolls_back`, `test_completion_hook_failure_rolls_back_and_retry_does_not_repeat_hooks`, `test_failure_reported_inside_outer_transaction_still_rolls_back_inner_work` |
|
|
44
|
+
| Commit observations / rollback / callback failure | `test_observation_waits_for_outer_commit_and_does_not_change_result`, `test_outer_rollback_discards_issuance_and_observation`, `test_savepoint_rollback_discards_event_even_when_outer_transaction_commits`, `test_request_callbacks_after_commit_and_observer_secrets`, `test_acceptance_hook_rollback_does_not_notify` |
|
|
45
|
+
| Reentrancy / immutable lookup / recovery / cleanup | `test_reentrant_completion_is_rejected_even_from_another_auth_instance`, `test_request_lookup_recovery_and_cleanup`, `test_expired_pending_cleanup_and_live_approved_retention` |
|
|
46
|
+
| Schema / migration / uniqueness | `test_custom_table_and_column_mapping`, `test_migration_client_metadata_helper`, `test_explicit_migration_up_and_down`, `test_schema_rejects_duplicate_public_identifier` |
|
|
47
|
+
| Upstream coexistence / initial restrictions | `test_authorization_code_flow_still_uses_oidc_wrapper`, `test_dcr_rejects_ciba_but_preserves_other_grant_validation`, `test_unavailable_or_unsupported_client_metadata_is_rejected`, `test_jwt_access_token_configuration_also_works` |
|
|
48
|
+
| Demo's browser transport | `DemoTest#test_browser_approval_checks_csrf_and_finishes_request`, `test_default_require_loads_jwt_backend_in_fresh_process` |
|
|
49
|
+
|
|
50
|
+
## Primary specifications
|
|
51
|
+
|
|
52
|
+
- [CIBA Core 1.0](https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html)
|
|
53
|
+
- [OIDC Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html)
|
|
54
|
+
- [RFC 6749](https://www.rfc-editor.org/rfc/rfc6749.html)
|
|
55
|
+
|
|
56
|
+
Product policies (size limits, offline_access removal, static/public restriction, internal API semantics) are distinguished in [the API documentation](api.md). The test suite verifies those policies; it does not imply they are universally required by the specifications.
|
|
57
|
+
|
|
58
|
+
## Conformance Suite cases consulted
|
|
59
|
+
|
|
60
|
+
The following files were read from the OpenID Foundation's read-only mirror at commit `fc6a65c4ac25a7f1fc8a92205ebc5836f321189d` on2026-09-29:
|
|
61
|
+
|
|
62
|
+
- [FAPICIBAID1AuthReqIdExpired](https://github.com/openid-certification/conformance-suite/blob/fc6a65c4ac25a7f1fc8a92205ebc5836f321189d/src/main/java/net/openid/conformance/fapiciba/FAPICIBAID1AuthReqIdExpired.java): requested lifetime followed by an expired collection attempt. The local tests use a controlled clock/DB deadline instead of sleeping.
|
|
63
|
+
- [FAPICIBAID1EnsureAuthorizationRequestWithMultipleHintsFails](https://github.com/openid-certification/conformance-suite/blob/fc6a65c4ac25a7f1fc8a92205ebc5836f321189d/src/main/java/net/openid/conformance/fapiciba/FAPICIBAID1EnsureAuthorizationRequestWithMultipleHintsFails.java): multiple hints are rejected. Local coverage exercises login_hint combined with the other hint parameters.
|
|
64
|
+
- [FAPICIBAID1EnsureAuthorizationRequestWithBindingMessageSucceeds](https://github.com/openid-certification/conformance-suite/blob/fc6a65c4ac25a7f1fc8a92205ebc5836f321189d/src/main/java/net/openid/conformance/fapiciba/FAPICIBAID1EnsureAuthorizationRequestWithBindingMessageSucceeds.java): ordinary binding-message success. The local suite validates storage and the demo displays the message with HTML escaping.
|
|
65
|
+
|
|
66
|
+
These are references for overlapping behaviors only. Their FAPI profile setup is not implemented here. No Suite code is copied into the gem, and neither the complete Suite nor certification tests have been run.
|
|
67
|
+
|
|
68
|
+
## Client authentication compatibility follow-up
|
|
69
|
+
|
|
70
|
+
CIBA Core §4 shares the OP's `token_endpoint_auth_methods_supported` metadata; it does not require basic/post-only OP configuration. §7.1 requires registered client authentication and accepts issuer/token/backchannel audiences for JWT assertions at the Backchannel Endpoint.
|
|
71
|
+
|
|
72
|
+
- `test_upstream_jwt_authentication_coexists_with_ciba_and_discovery`: private_key_jwt CIBA start/poll, all three start audiences, existing authorization_code, Basic CIBA on the same OP, unchanged Discovery support.
|
|
73
|
+
- `test_ciba_jwt_authentication_rejects_wrong_audience_signature_and_mixed_credentials`: wrong audience/signature and duplicate authentication rejected.
|
|
74
|
+
- `test_upstream_client_secret_jwt_works_for_ciba`: upstream shared-secret JWT start/poll.
|
|
75
|
+
- `test_public_clients_can_exist_but_cannot_use_ciba`: public-client configuration is allowed for the OP, excluded from CIBA.
|
|
76
|
+
|
|
77
|
+
These tests do not claim all upstream authentication mechanisms are validated. JWT client authentication is distinct from the opt-in signed CIBA authentication requests covered above. See the following additional integration boundaries.
|
|
78
|
+
|
|
79
|
+
## Additional authentication and registration coverage
|
|
80
|
+
|
|
81
|
+
| Boundary | Current evidence |
|
|
82
|
+
|---|---|
|
|
83
|
+
| JWT assertion issuer audience at refresh, wrong audience and replay | `test/ciba_assertion_refresh_test.rb`, `test/reference/full-op/assertion-refresh.mjs` |
|
|
84
|
+
| Discovery retention of configured introspection, DPoP algorithms and mTLS capability | `test/ciba_discovery_extensions_test.rb`; installed smoke follows discovered URLs |
|
|
85
|
+
| DPoP proof, nonce policy, replay, UserInfo, introspection and refresh | `test/ciba_dpop_test.rb`, `test/ciba_dpop_registration_test.rb`, `examples/dpop_smoke.rb`, `test/reference/full-op/dpop.mjs`; [contract](research/dpop-reference-contract.md) |
|
|
86
|
+
| mTLS trust callbacks, exact certificate binding, opaque/JWT UserInfo and introspection, refresh | `test/ciba_mtls_test.rb`, `examples/mtls_smoke.rb`, `test/reference/full-op/mtls.mjs`; [contract](research/mtls-reference-contract.md) |
|
|
87
|
+
| mTLS remote JWKS rotation, retrieval failure and recovery | `test/ciba_mtls_remote_test.rb`; installed `examples/mtls_smoke.rb` uses verified HTTPS and forced cache expiry |
|
|
88
|
+
| mTLS registration policy and management replacement | `test/ciba_mtls_registration_test.rb`; node and installed TLS fixtures |
|
|
89
|
+
| Dynamic registration/management, metadata, credentials and concurrency | `test/ciba_registration_test.rb`, `test/ciba_registration_remote_test.rb`, `examples/registration_smoke.rb`; [contract](research/registration-reference-contract.md) |
|
|
90
|
+
| Pairwise subjects, hints, refresh, sector documents and management | `test/ciba_pairwise_test.rb`, installed `examples/ping_smoke.rb`; [contract](research/pairwise-reference-contract.md) |
|
|
91
|
+
|
|
92
|
+
These rows map integration boundaries, not every normative requirement in the
|
|
93
|
+
referenced RFCs. Application-owned endpoint aliases have distinct-port TLS
|
|
94
|
+
coverage; arbitrary proxy/path rewriting and distributed cache behavior do not.
|
|
95
|
+
Consult [release validation](release-validation.md) for the tested Ruby/DB
|
|
96
|
+
combinations; file presence alone is not proof of a successful run.
|
|
97
|
+
|
|
98
|
+
## Manual requirements and security review
|
|
99
|
+
|
|
100
|
+
The historical 2026-09-29, 159-item initial-scope classification is in [requirements-audit.md](requirements-audit.md). Its excluded optional features do not describe the subsequently implemented extensions. An incremental [current audit overlay](requirements-audit-current.md) now records reviewed and pending IDs; the full normative re-audit remains incomplete; [the alignment roadmap](alignment-roadmap.md) tracks current evidence and gaps. Classification separates mandatory, conditional, recommended and optional behavior and identifies gem/upstream/application/client responsibilities. The Jev scores are historical inputs, not compliance evidence.
|
|
101
|
+
|
|
102
|
+
Additional direct tests in `test/ciba_security_test.rb` cover OIDC Core §9 required assertion claims, issuer/subject/client binding, malformed assertions, missing client keys, short HMAC keys, replay across endpoints/flows, concurrent replay, bounded ledger cleanup and persistent slow_down growth. `test/ciba_requirements_test.rb` covers acknowledgement identifier format, exact expiry boundary, HTTP401 authentication errors, missing-vs-invalid scope and optional error-description ASCII constraints. See [security-review.md](security-review.md) for findings and test limits.
|
|
103
|
+
|
|
104
|
+
## node-oidc-provider alignment
|
|
105
|
+
|
|
106
|
+
`test/ciba_alignment_test.rb` verifies the v9.12.2 binding-message default and application replacement policy. The demo XSS/CSRF test explicitly relaxes that policy to continue checking output escaping. See [alignment progress](node-alignment.md); this is not a claim that the remaining API or protocol differences are resolved.
|
|
107
|
+
|
|
108
|
+
`test/ciba_grant_test.rb` verifies persisted consent, client/account binding, requested/approved scope intersection, untrusted snapshot-field rejection, consent expiry/revocation at collection, issuance/revocation races, revocation after request cleanup, completion retries and transactional rollback. That file alone does not cover node-specific claim/resource/RAR consent. Separate tests cover those extensions; executed PostgreSQL/MySQL evidence is recorded in [release validation](release-validation.md).
|
|
109
|
+
|
|
110
|
+
- AD initiation integration: `test/ciba_alignment_test.rb` verifies saved request handoff before response, synchronous completion, missing implementation/dispatch failure, and rejection before dispatch. This application callback is an implementation boundary, not a standardized CIBA approval HTTP API.
|
|
111
|
+
|
|
112
|
+
- Shared OIDC issuance: `test_common_oidc_claim_provider_only_receives_approved_scopes` verifies account claims come from the upstream provider using effective consent scopes, without browser-session authentication-time lookup. Full claims-parameter consent and resource/RAR support remain outside this evidence.
|
|
113
|
+
|
|
114
|
+
- Completion consistency: `test_grant_expiry_during_before_approve_does_not_record_approval` and `test_grant_revocation_during_before_approve_rolls_back_the_completion` check revalidation after transactional hooks. Demo tests cover saved consent reuse, conflicting decision rollback, and another customer’s request rejection.
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# CIBA refresh tokens — implementation in progress
|
|
2
|
+
|
|
3
|
+
Disabled by default. This branch implements the basic flow, but it is not yet a
|
|
4
|
+
release-ready refresh implementation. The implemented revocation boundaries and
|
|
5
|
+
legacy-format differences below still need the overall alignment review. Do not
|
|
6
|
+
infer full reference parity from the existence of the flag.
|
|
7
|
+
|
|
8
|
+
For development tests, explicitly create storage with
|
|
9
|
+
`Rodauth::CibaSupport::Schema.create_refresh_tokens(db)` and configure
|
|
10
|
+
`ciba_refresh_tokens_enabled true`. Register both the CIBA grant and
|
|
11
|
+
`refresh_token` for the client, and allow `offline_access` in both OP and client
|
|
12
|
+
scopes. Initial issuance requires `offline_access` in both the CIBA source and
|
|
13
|
+
its saved consent, as well as the
|
|
14
|
+
refresh grant registration, matching the reference default policy.
|
|
15
|
+
|
|
16
|
+
`ciba_refresh_tokens_table` defaults to `:ciba_refresh_tokens` and
|
|
17
|
+
`ciba_refresh_token_lifetime` to 14 days. Only the digest of an opaque, namespaced
|
|
18
|
+
refresh token is stored. The source retains original scope/resources/claims/RAR,
|
|
19
|
+
account/client, consent ID and authentication context separately from each access
|
|
20
|
+
token. Short-lived CIBA request cleanup does not remove it.
|
|
21
|
+
|
|
22
|
+
Updates validate the authenticated client, current grant registration, account,
|
|
23
|
+
saved consent and enabled optional capabilities. Removing `offline_access` from
|
|
24
|
+
the saved consent rejects subsequent refreshes with `invalid_grant` before
|
|
25
|
+
rotation or issuance. Scope narrowing applies to the
|
|
26
|
+
current access token, not the saved refresh source. Resource selection is computed
|
|
27
|
+
against the current consent. Each update creates a new access-token record.
|
|
28
|
+
|
|
29
|
+
When upstream JWT client authentication is enabled, `private_key_jwt` and
|
|
30
|
+
`client_secret_jwt` accept the OP issuer as the assertion audience for CIBA
|
|
31
|
+
refresh, as they do for initial CIBA token collection. The current token URL
|
|
32
|
+
remains accepted. This extension applies only to the CIBA grant and reserved
|
|
33
|
+
`ciba_rt_` refresh namespace; ordinary OAuth token requests retain their existing
|
|
34
|
+
audience policy. Wrong audiences and assertion replay remain authentication
|
|
35
|
+
failures. [Measured comparison](research/refresh-reference-contract.md#jwt-client-assertion-audience).
|
|
36
|
+
|
|
37
|
+
For confidential clients the initial implementation follows node's default
|
|
38
|
+
rotation policy: rotate after 70% of token lifetime, until one year after initial
|
|
39
|
+
issuance. A fresh token can be reused. Rotation preserves consumed digests; reuse
|
|
40
|
+
by the correct client revokes the saved consent and its linked access tokens, and
|
|
41
|
+
therefore also invalidates successor refresh tokens. A different client's request
|
|
42
|
+
does not revoke the rightful client's consent. Simultaneous rotation can produce
|
|
43
|
+
one success followed by a replay response that invalidates the returned successor.
|
|
44
|
+
|
|
45
|
+
Token consumption, rotation and issuance share the DB transaction. Signing failure
|
|
46
|
+
rolls them all back, preserving the project's accepted atomic-issuance contract.
|
|
47
|
+
This differs from node's nontransactional memory adapter. Existing non-CIBA OAuth
|
|
48
|
+
refresh tokens continue through upstream's implementation and rotation policy.
|
|
49
|
+
|
|
50
|
+
The flag can be disabled without querying the optional table for presented CIBA
|
|
51
|
+
refresh tokens; their namespace causes `invalid_grant`. This namespace is reserved
|
|
52
|
+
for the extension. Changing ordinary OAuth token generators to use `ciba_rt_`
|
|
53
|
+
would conflict with that dispatch boundary.
|
|
54
|
+
|
|
55
|
+
See the [reference contract](research/refresh-reference-contract.md) for measured
|
|
56
|
+
reference behavior and the remaining tests. Current authentication context output
|
|
57
|
+
follows the gem's CIBA ID Token behavior; it is not identical to node's optional
|
|
58
|
+
claim masking.
|
|
59
|
+
|
|
60
|
+
## Refresh-token revocation
|
|
61
|
+
|
|
62
|
+
Enable upstream `:oauth_token_revocation` to expose its advertised endpoint.
|
|
63
|
+
The CIBA extension also adds its URL to OIDC Discovery when that feature is enabled.
|
|
64
|
+
Presenting a `ciba_rt_` token authenticates the OAuth client (never the browser
|
|
65
|
+
account), requires the CIBA form/TLS transport and revokes the entire saved
|
|
66
|
+
consent and linked access tokens. This remains possible with refresh issuance
|
|
67
|
+
disabled when the refresh table exists. The standard upstream before/after revoke
|
|
68
|
+
hooks run in the transaction; an exception rolls the revocation back.
|
|
69
|
+
|
|
70
|
+
Like the measured reference, missing, wrong or unknown `token_type_hint` does not
|
|
71
|
+
prevent finding a CIBA refresh token. Success is HTTP 200 with an empty body.
|
|
72
|
+
Unknown, expired and already-revoked tokens are successful no-ops. An authenticated
|
|
73
|
+
different client presenting a live token receives `invalid_request`; it cannot
|
|
74
|
+
revoke the owner's consent. After revocation, its retry is also a successful no-op.
|
|
75
|
+
|
|
76
|
+
The endpoint also recognizes saved opaque CIBA access tokens independently of the
|
|
77
|
+
hint. Their revocation invalidates related tokens and deletes old request/refresh
|
|
78
|
+
sources while retaining saved consent, matching the [reference's distinct AT
|
|
79
|
+
policy](research/access-token-revocation-reference.md). Standard transactional
|
|
80
|
+
revoke hooks still apply. New opaque CIBA tokens use the reserved `ciba_at_`
|
|
81
|
+
namespace, so unknown or cleaned-up tokens return empty 200 after client
|
|
82
|
+
authentication. Legacy unprefixed tokens need a saved row to be recognized; after
|
|
83
|
+
row cleanup they retain upstream behavior. Calling the consent-revocation API
|
|
84
|
+
instead would not preserve consent.
|
|
85
|
+
|
|
86
|
+
New CIBA JWT access tokens always carry the signed private issuance ID, even with
|
|
87
|
+
explicit claims disabled. It is removed from ID Tokens. At revocation the marker
|
|
88
|
+
selects a rejecting path, then OP signature/issuer/type are verified. Proven CIBA
|
|
89
|
+
JWTs return `unsupported_token_type`, including expired ones; forged markers
|
|
90
|
+
return `invalid_request`. This provenance-only verifier never authorizes token use
|
|
91
|
+
and does not alter the normal access-token time checks. Node instead rejects all
|
|
92
|
+
structured JWTs by header shape; unrelated and unmarked legacy JWTs keep upstream
|
|
93
|
+
behavior here. No JWT revocation path mutates saved consent.
|
|
94
|
+
|
|
95
|
+
The issuance marker also binds new JWT UserInfo access to its saved token row.
|
|
96
|
+
Deleting that row invalidates UserInfo access. Unmarked historical JWTs retain the
|
|
97
|
+
old scope-based path without borrowing explicit claims from another issuance.
|
|
98
|
+
|
|
99
|
+
## Claims, authorization details and cleanup
|
|
100
|
+
|
|
101
|
+
Opaque and JWT access-token tests cover claims/RAR with rotation. The original
|
|
102
|
+
request fields are preserved in the successor source; the application's RAR
|
|
103
|
+
policy is re-evaluated against saved consent for each update. A token-time claims
|
|
104
|
+
parameter does not grant additional attributes. Current rejected claims suppress
|
|
105
|
+
attribute reads and output, and disabled capabilities reject updates instead of
|
|
106
|
+
silently dropping restrictions. Invalid RAR narrowing rolls rotation back.
|
|
107
|
+
|
|
108
|
+
The reference test application's policy emits `source.rar`, explicitly supplied
|
|
109
|
+
at completion; it ignores token-time expansion. The gem's test policy intersects
|
|
110
|
+
the saved request and current consent, with optional narrowing. These are different
|
|
111
|
+
application-owned policies, not proof of identical arbitrary RAR semantics.
|
|
112
|
+
|
|
113
|
+
Run `cleanup_ciba_refresh_tokens(limit: 100)` from an application-owned maintenance
|
|
114
|
+
job after creating the refresh table. It deletes at most the limit of sources whose
|
|
115
|
+
expiry is at or before the current time, and returns the deleted count. Unexpired
|
|
116
|
+
consumed sources must remain for replay detection. Cleanup does not delete saved
|
|
117
|
+
consent or access tokens. It participates in an outer DB transaction and does not
|
|
118
|
+
schedule itself.
|
|
119
|
+
|
|
120
|
+
## Update hooks and events
|
|
121
|
+
|
|
122
|
+
`before_ciba_refresh` and `after_ciba_refresh` run inside the token transaction.
|
|
123
|
+
They can use `ciba_current_refresh`, an immutable snapshot of the presented refresh
|
|
124
|
+
source with its digest removed. This is not a CIBA request snapshot: its `id` is
|
|
125
|
+
the refresh-source row ID. The after snapshot reflects consumption of that source
|
|
126
|
+
when rotation occurred. Neither snapshot includes a raw token. It is cleared on
|
|
127
|
+
exit, and same-source reentrant updates are rejected.
|
|
128
|
+
|
|
129
|
+
The source, account, client registration, saved consent, expiry and enabled
|
|
130
|
+
capabilities are revalidated after the before hook. Hook exceptions roll back
|
|
131
|
+
source consumption, successor creation and access-token issuance. As with other
|
|
132
|
+
transactional hooks, external side effects cannot be rolled back by the gem.
|
|
133
|
+
|
|
134
|
+
A successful update schedules an immutable `token_refreshed` observation event
|
|
135
|
+
after the outermost commit. It has `version`, `event_id`, `type`, `occurred_at`,
|
|
136
|
+
`refresh_token_id`, `grant_id`, `account_id`, `oauth_application_id` and `rotated`.
|
|
137
|
+
It has no `request_id` because refresh can outlive request cleanup. Tokens, token
|
|
138
|
+
digests, claims and authorization details are not included. Observer exceptions
|
|
139
|
+
go through `report_ciba_observer_error` and do not change the committed result.
|
|
140
|
+
This event describes a committed issuance, not proof of delivery to the client.
|
|
141
|
+
|
|
142
|
+
## Installed artifact verification
|
|
143
|
+
|
|
144
|
+
`bin/verify-package` builds and installs the gem in a temporary directory, clears
|
|
145
|
+
checkout load-path variables and verifies that the feature is loaded from that
|
|
146
|
+
installation. The extension smoke runs both opaque and JWT modes with signed
|
|
147
|
+
authentication requests, claims/resources/RAR and refresh enabled. It verifies
|
|
148
|
+
original identity context, rotation, grant replay revocation invalidating a
|
|
149
|
+
successor, Discovery-based refresh revocation and expiry cleanup. Existing basic
|
|
150
|
+
and real HTTPS ping smoke also run. Dependencies are reused from local installed
|
|
151
|
+
gems; fresh network dependency resolution and publication are not tested.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# 0.1.0 release readiness
|
|
2
|
+
|
|
3
|
+
RubyGems publication is pending. This document separates the supported release
|
|
4
|
+
scope from historical development notes and opt-in experiments.
|
|
5
|
+
|
|
6
|
+
## Scope
|
|
7
|
+
|
|
8
|
+
The default integration uses poll, login_hint, public subjects and static
|
|
9
|
+
confidential client registration. The README and individual feature contracts
|
|
10
|
+
specify optional capabilities and application responsibilities. Refresh tokens,
|
|
11
|
+
pairwise subjects and dynamic CIBA registration remain experimental, disabled by
|
|
12
|
+
default and excluded from the initial supported release scope. Push and encrypted
|
|
13
|
+
ID Token hints are not implemented. This is not OpenID Certified.
|
|
14
|
+
|
|
15
|
+
The gem requires Ruby >=3.3 and the dependency versions in its gemspec. Application
|
|
16
|
+
DB adapters, account/OAuth schemas, trusted identity resolution, approval UI,
|
|
17
|
+
notification transport and business authorization are application responsibilities.
|
|
18
|
+
|
|
19
|
+
## Published baseline evidence
|
|
20
|
+
|
|
21
|
+
The [authentication-fix commit](https://github.com/kajisha/rodauth-ciba/commit/3791a53caae7ab6fbe6793fdb1646d7e5074c951)
|
|
22
|
+
passed [hosted CI](https://github.com/kajisha/rodauth-ciba/actions/runs/36710533803):
|
|
23
|
+
Ruby 3.3/3.4/4.0 SQLite and the Docker SQLite/PostgreSQL/MySQL job.
|
|
24
|
+
Its local full run passed410 tests /10,844 assertions with two existing SQLite
|
|
25
|
+
row-lock skips. The latest pre-release cleanup verification is recorded below.
|
|
26
|
+
|
|
27
|
+
Jev's3.95/4 score (confidence0.96) concerned only the missing-client-authentication
|
|
28
|
+
fix. It is not an overall conformance score or a release gate.
|
|
29
|
+
|
|
30
|
+
## Cleanup review
|
|
31
|
+
|
|
32
|
+
- README, CHANGELOG and the release goal now distinguish the current release
|
|
33
|
+
scope from intermediate implementation restrictions. Original changelog notes
|
|
34
|
+
are preserved in the repository's pre-release development history.
|
|
35
|
+
- The gemspec declares homepage, source, issue tracker and changelog URLs.
|
|
36
|
+
- Static method, constant and instance-variable reference searches were checked
|
|
37
|
+
against Rodauth's dynamic hooks, upstream overrides and public migration APIs.
|
|
38
|
+
A single textual occurrence alone is not evidence of dead code.
|
|
39
|
+
- Removed an unreachable recipient-nil check and safe navigation in ID Token
|
|
40
|
+
encryption. The candidate array is local and checked for emptiness before
|
|
41
|
+
min_by, so successful selection always returns a pair.
|
|
42
|
+
- No broad authentication/state-machine refactor was justified for this release.
|
|
43
|
+
Existing security boundaries and separate extension modules are retained.
|
|
44
|
+
|
|
45
|
+
Test execution information is used to identify review candidates, not to prove
|
|
46
|
+
that every unused branch is dead or every feature combination is covered.
|
|
47
|
+
|
|
48
|
+
## Current cleanup verification
|
|
49
|
+
|
|
50
|
+
The full suite before the one-line recipient-selection simplification passed410
|
|
51
|
+
tests /10,844 assertions, zero failures/errors, with two existing SQLite row-lock
|
|
52
|
+
skips. Runtime line coverage recorded2369 of2449 executable library lines;
|
|
53
|
+
13 uncalled method definitions were application-overridden callbacks or default
|
|
54
|
+
error reporters, not unused APIs. All other recorded library method definitions
|
|
55
|
+
were exercised. This is execution evidence from Ruby4.0.6/SQLite, not branch
|
|
56
|
+
coverage or proof of absence of dead code.
|
|
57
|
+
|
|
58
|
+
[Full test log](https://github.com/kajisha/rodauth-ciba/blob/main/docs/validation/release-code-review-tests.txt)
|
|
59
|
+
and [per-file execution summary](https://github.com/kajisha/rodauth-ciba/blob/main/docs/validation/release-code-coverage.json)
|
|
60
|
+
are kept in the repository. Line locations refer to the pre-cleanup source.
|
|
61
|
+
|
|
62
|
+
After removing the redundant check, the encryption regression selection passed
|
|
63
|
+
76 tests /4,873 assertions, zero failures/errors, with one existing SQLite skip
|
|
64
|
+
([log](https://github.com/kajisha/rodauth-ciba/blob/main/docs/validation/release-encryption-cleanup-tests.txt)).
|
|
65
|
+
The selection includes the shared integration tests loaded by these test files.
|
|
66
|
+
|
|
67
|
+
The updated gem passed `bin/verify-package`: build, temporary installation outside
|
|
68
|
+
the checkout, and all installed HTTP/TLS smoke flows
|
|
69
|
+
([log](https://github.com/kajisha/rodauth-ciba/blob/main/docs/validation/release-cleanup-package.txt)).
|
|
70
|
+
Dependencies were reused from local installations; this does not test fresh
|
|
71
|
+
network dependency resolution. After recording these results, the artifact was
|
|
72
|
+
rebuilt with identical runtime code and updated documentation; its final SHA256
|
|
73
|
+
is in `pkg/rodauth-ciba-0.1.0.gem.sha256` (local build output, not committed).
|
|
74
|
+
The cleanup has not rerun the full Ruby/DB matrix locally; the hosted results
|
|
75
|
+
above are for the preceding commit.
|