Skip to content

feat(auth): configurable OAuth client authentication for OpenID4VCI auth-code flow#4320

Merged
reinkrul merged 2 commits into
project-gf-pilotfrom
feature/openid4vci-client-auth
Jun 4, 2026
Merged

feat(auth): configurable OAuth client authentication for OpenID4VCI auth-code flow#4320
reinkrul merged 2 commits into
project-gf-pilotfrom
feature/openid4vci-client-auth

Conversation

@reinkrul

@reinkrul reinkrul commented Jun 4, 2026

Copy link
Copy Markdown
Member

Implements #4316.

EXPERIMENTAL — this configuration may change or be removed without further notice (flagged in code doc comments and nested under auth.experimental).

What

Adds per-server OAuth client configuration so the node can authenticate to external authorization servers in the OpenID4VCI authorization code flow with a registered client_id + client_secret, instead of the Nuts-specific did:web + client_id_scheme=entity_id defaults that third-party servers (e.g. AET/OpenIddict) reject.

auth:
  experimental:
    clients:
      - serverurl: https://issuer.example.com/oauth   # OAuth AS issuer
        clientid: nuts-wallet-123
        clientsecret: s3cr3t        # optional

Env: NUTS_AUTH_EXPERIMENTAL_CLIENTS_0_SERVERURL / _CLIENTID / _CLIENTSECRET (single-token keys round-trip through the _. env delimiter).

Behavior

  • Match by OAuth AS issuer (authzServerMetadata.Issuer), trailing-slash-normalized — not credential_issuer (AET splits them: .../aet vs .../aet-oauth).
  • Authorization request: configured client_id, drop client_id_scheme.
  • Token request: configured client_id; client_secret via client_secret_post (TODO: derive method from AS metadata + support client_secret_basic). Empty secret → public client (PKCE only).
  • No matching entry → unchanged (did:web + entity_id). Fully additive.
  • Validated at startup: serverurl required + valid URL (HTTPS in strict mode), clientid required, no duplicate serverurl.
  • clientsecret redacted from logged/printed config (whole auth.experimental.clients subtree, covering both YAML-slice and env-indexed key forms).

Scope

Only the OpenID4VCI auth-code flow is wired; the config shape is protocol-neutral for later reuse (plain OAuth2, OpenID4VP). Not a pflag (no []struct flag type), so it doesn't appear in server_options.rst; documented via experimental doc comments + #4316.

Tests

  • auth: lookup match/normalize/miss, validation cases.
  • auth/api/iam: authorization request (configured client_id, no scheme) and token request (configured client_id + secret) vs unchanged default path.
  • auth/client/iam: client_secret_post on the wire; no secret for public client.
  • core: config redaction of descendant/array keys.

Verified empirically against AET: public client_idinvalid_client; client_secret_post accepted.

🤖 Assisted by AI

reinkrul added 2 commits June 4, 2026 09:46
* test(storage): fix flaky minimemcached nil-pointer panic

The minimemcached test server spawns a goroutine that calls Accept() on
its listener, while Close() nils that listener. On very short tests the
cleanup's Close() could run before the goroutine entered Accept(),
dereferencing a nil listener inside the dependency and crashing the test
binary with a panic that no test can recover (it runs in a foreign
goroutine).

Block in the test helper until the server answers a probe connection,
guaranteeing the accept loop is parked in Accept() before the test
proceeds. Close() then unblocks it cleanly instead of racing startup.

Assisted by AI

* test(storage): link upstream issue for the minimemcached workaround

Reference daangn/minimemcached#14 (fix in PR
#13) so the workaround can be removed once an upstream release ships the
fix.

Assisted by AI
…uth-code flow

Add experimental auth.experimental.clients config so the node can present a
configured client_id (and client_secret via client_secret_post) to external
authorization servers that do not understand the Nuts-specific entity_id
client_id scheme. Entries are matched against the OAuth Authorization Server
issuer; when no entry matches, behavior is unchanged (did:web + entity_id).

Client secrets are redacted from the logged/printed configuration.

Implements #4316.

Assisted by AI
@reinkrul
reinkrul marked this pull request as ready for review June 4, 2026 09:39
@reinkrul
reinkrul marked this pull request as draft June 4, 2026 09:39
@reinkrul
reinkrul merged commit c4e9976 into project-gf-pilot Jun 4, 2026
1 check passed
@reinkrul
reinkrul deleted the feature/openid4vci-client-auth branch June 4, 2026 13:22
@reinkrul
reinkrul restored the feature/openid4vci-client-auth branch June 4, 2026 14:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant