Skip to content

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

Draft
reinkrul wants to merge 1 commit into
masterfrom
feature/openid4vci-client-auth
Draft

feat(auth): configurable OAuth client authentication for OpenID4VCI auth-code flow#4327
reinkrul wants to merge 1 commit into
masterfrom
feature/openid4vci-client-auth

Conversation

@reinkrul
Copy link
Copy Markdown
Member

@reinkrul reinkrul commented Jun 4, 2026

Implements #4316.

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

What

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).

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.

Already merged into project-gf-pilot; this PR tracks the same change against master.

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

…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
@qltysh
Copy link
Copy Markdown
Contributor

qltysh Bot commented Jun 4, 2026

Qlty


Coverage Impact

⬆️ Merging this pull request will increase total coverage on master by 0.02%.

Modified Files with Diff Coverage (5)

RatingFile% DiffUncovered Line #s
Coverage rating: C Coverage rating: C
auth/auth.go93.5%206-207
Coverage rating: C Coverage rating: C
core/server_config.go100.0%
Coverage rating: B Coverage rating: B
auth/api/iam/openid4vp.go100.0%
Coverage rating: C Coverage rating: C
auth/client/iam/openid4vp.go100.0%
Coverage rating: A Coverage rating: A
auth/api/iam/openid4vci.go100.0%
Total96.4%
🤖 Increase coverage with AI coding...
In the `feature/openid4vci-client-auth` branch, add test coverage for this new code:

- `auth/auth.go` -- Line 206-207

🚦 See full report on Qlty Cloud »

🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

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