Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
d8cef07
Read Presentation Definition from local PDP backend
reinkrul Nov 28, 2025
eeb783a
build Docker image for branch
reinkrul Dec 1, 2025
811fc85
build docker image
reinkrul Dec 1, 2025
262a774
basic testing setup
reinkrul Dec 2, 2025
921821f
Issue mandaatcredential
reinkrul Dec 4, 2025
19f5960
#3953: add support for urn:ietf:params:oauth:grant-type:jwt-bearer fo…
reinkrul Dec 4, 2025
18d8023
Relax did:x509 certificate key usage validation
reinkrul Dec 16, 2025
dccb5ed
Enable RS256 support
reinkrul Dec 16, 2025
206e5e1
Add AortaGtK CA certs to OS trust bundle
reinkrul Dec 16, 2025
219635f
Add EV intermediate CA to trusted certs
reinkrul Dec 16, 2025
12f6e9e
Don't send presentation_submission
reinkrul Dec 16, 2025
14358d9
Introduce policy_id parameter
reinkrul Dec 16, 2025
708ad5a
Try to marshal VPs as JWT, not JSON-LD
reinkrul Dec 16, 2025
89527d3
Updated README
reinkrul Dec 16, 2025
f07d0f6
Updated README
reinkrul Dec 16, 2025
6254059
test for VP type
reinkrul Dec 16, 2025
68a5e21
write vps to temp file
reinkrul Dec 17, 2025
2572c09
revert VC JWT fix
reinkrul Dec 17, 2025
64cc71f
set fixed key ID
reinkrul Dec 17, 2025
724051f
fix vp.type to array
reinkrul Dec 17, 2025
50625ab
Made token response parsing lenient
reinkrul Dec 17, 2025
9bd652e
Reverted jwt ID
reinkrul Dec 17, 2025
1465cee
Merge branch 'master' into lspxnuts
reinkrul Jan 27, 2026
dac8036
#3980: Support validation of DeziIDTokenCredential
reinkrul Feb 2, 2026
2ebea32
implemented e2e test
reinkrul Feb 2, 2026
6552dfa
Update vcr/credential/validator.go
reinkrul Feb 2, 2026
ea7ffac
cleanup
reinkrul Feb 2, 2026
ea905dc
Merge branch 'lspxnuts' into project-gf
reinkrul Feb 2, 2026
ed58bc1
Merge branch 'iss3980-validate-idtoken-credential' into project-gf
reinkrul Feb 2, 2026
ca4005d
Push docker image
reinkrul Feb 3, 2026
3e58304
\#3978: Return credential/presentation verification errors to client
Copilot Jan 29, 2026
61bc2f7
Merge branch 'master' into project-gf
reinkrul Feb 5, 2026
b53043e
VCR: Allow configuration of revocation list max-age
reinkrul Feb 6, 2026
4ce4d99
Merge branch 'vcr-configure-revocation-maxage' into project-gf
reinkrul Feb 6, 2026
847841c
fix
reinkrul Feb 10, 2026
bcdb76b
Merge branch 'master' into project-gf
reinkrul Feb 10, 2026
3133cb2
Merge branch 'master' into iss3980-validate-idtoken-credential
reinkrul Feb 10, 2026
a71c194
demo-ing revocation: include revoked/expired VCs in wallet.List(), al…
reinkrul Feb 10, 2026
d739fa4
Merge branch 'master' into project-gf
reinkrul Feb 16, 2026
a87f97b
Merge remote-tracking branch 'origin/copilot/improve-client-error-mes…
reinkrul Feb 17, 2026
910dfaa
Merge branch 'master' into project-gf
reinkrul Feb 17, 2026
5fd918b
Disable client-side access token caching
reinkrul Feb 25, 2026
649b839
wipo
reinkrul Feb 26, 2026
a622b57
Update to 2026 version (wip)
reinkrul Feb 27, 2026
1f18ef3
Support both 2024 and v0.7 version
reinkrul Mar 2, 2026
2e67f69
wip
reinkrul Mar 3, 2026
e21acf4
Working Dezi 0.7 implementation
reinkrul Mar 11, 2026
92da21d
Fix
reinkrul Mar 11, 2026
bf8ac19
Merge branch 'master' into iss3980-validate-idtoken-credential
reinkrul Mar 11, 2026
523c6b2
Fix
reinkrul Mar 11, 2026
147219a
Merge iss3957-vp-jwt-type-marshalling into project-gf
reinkrul Mar 20, 2026
daeb4b4
Merge branch 'master' into iss3980-validate-idtoken-credential
reinkrul Mar 22, 2026
c6b5e47
Update to Dezi v0.7
reinkrul Mar 23, 2026
49e28c4
Merge branch 'iss3980-validate-idtoken-credential' into project-gf
reinkrul Mar 23, 2026
1114fcc
Fix passing Dezi attestation as extra credential
reinkrul Mar 23, 2026
8383d51
Merge branch 'iss3980-validate-idtoken-credential' into project-gf
reinkrul Mar 23, 2026
3dc7c61
Fix test
reinkrul Mar 23, 2026
7c74d55
fix
reinkrul Mar 23, 2026
9ceed0d
Merge branch 'iss3980-validate-idtoken-credential' into project-gf
reinkrul Mar 23, 2026
ef4b3df
Add vcr.dezi.allowedjku
reinkrul Mar 23, 2026
b2e73c2
Merge branch 'iss3980-validate-idtoken-credential' into project-gf
reinkrul Mar 23, 2026
5bd7657
Merge branch 'master' into project-gf
reinkrul Apr 8, 2026
d646c43
LSP changed their certificate from Sectigo Public to PKIoverheid Priv…
reinkrul Apr 8, 2026
51b29b1
rename to CRT
reinkrul Apr 8, 2026
3952703
re-add sectigo
reinkrul Apr 8, 2026
c1b9fa9
Merge branch 'master' into project-gf
reinkrul Apr 8, 2026
3da4505
work
reinkrul Jan 14, 2026
ccd00b9
fix compoilcation error
reinkrul Feb 2, 2026
efd2670
implement JWT bearer token server-side
reinkrul Mar 10, 2026
71aff84
wip
reinkrul Mar 10, 2026
c57d598
Resolve rebase conflicts with master: combine policyId + credentialSe…
reinkrul Apr 13, 2026
5e97272
Fix broken tests in bearer_token_test.go
reinkrul Apr 13, 2026
934f2bb
Rename CredentialProfileValidatorFunc to CredentialProfileFunc
reinkrul Apr 13, 2026
fd446d0
Revert rename: handleAuthzCodeTokenRequest -> handleAccessTokenRequest
reinkrul Apr 13, 2026
e779a6e
Fix review findings; revert handleRFC021VPTokenRequest rename
reinkrul Apr 13, 2026
2d59d5f
Rename CredentialProfile(Func) to PresentationEvaluatorFunc
reinkrul Apr 13, 2026
233a334
Rename SubmissionCredentialProfile and BasicCredentialProfile
reinkrul Apr 13, 2026
d15f020
Add e2e test for JWT bearer grant type (RFC7523)
reinkrul Apr 13, 2026
0964b88
make grant types configurable
reinkrul Apr 13, 2026
af3824c
Move Jaeger tracing e2e test to standalone single-node test
reinkrul Apr 13, 2026
df42112
Fix tracing e2e test: disable strictmode, bind internal HTTP external…
reinkrul Apr 13, 2026
4c05da1
Address shellcheck issues
reinkrul Apr 13, 2026
e74e0f7
Fix tracing e2e test CI failure: use nuts.yaml, disable IRMA
reinkrul Apr 13, 2026
758dd48
Merge branch 'e2e-test/tracing' into support-jwtbearer
reinkrul Apr 13, 2026
b45694f
Merge remote-tracking branch 'origin/master' into support-jwtbearer
reinkrul Apr 13, 2026
cd2d9f8
improve
reinkrul Apr 13, 2026
306a2fb
Merge branch 'master' into project-gf
reinkrul Apr 14, 2026
ed2e3d2
Merge branch 'support-jwtbearer' into project-gf
reinkrul Apr 14, 2026
797504f
Populate InputDescriptorConstraintIdMap in OpenID4VP access token
reinkrul Apr 14, 2026
d16b738
Merge branch 'master' into project-gf
reinkrul Apr 16, 2026
6d694ec
Add OAuth2 params to OTEL span and log context
reinkrul Apr 17, 2026
20c7d3e
Set OAuth2 span attributes on outbound token request
reinkrul Apr 17, 2026
f16c2a3
Merge branch 'support-jwtbearer' into project-gf
reinkrul Apr 17, 2026
b1be090
Start dedicated span for outbound OAuth2 token request
reinkrul Apr 17, 2026
22ceb75
Merge branch 'support-jwtbearer' into project-gf
reinkrul Apr 17, 2026
fa6be1e
Add authorization_server_endpoint to at-request (#4214)
stevenvegt Apr 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/build-images.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ on:
push:
branches:
- master
- project-gf
tags:
- 'v*'
pull_request:
branches:
- master
- project-gf

# cancel build action if superseded by new commit on same branch
concurrency:
Expand Down Expand Up @@ -51,7 +53,7 @@ jobs:
images: nutsfoundation/nuts-node
tags: |
# generate 'master' tag for the master branch
type=ref,event=branch,enable={{is_default_branch}},prefix=
type=ref,event=branch,enable=true,prefix=
# generate 5.2.1 tag
type=semver,pattern={{version}}
flavor: |
Expand Down
5 changes: 4 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
COPY go.sum .
RUN go mod download && go mod verify

COPY . .

Check warning on line 20 in Dockerfile

View workflow job for this annotation

GitHub Actions / docker

Attempting to Copy file that is excluded by .dockerignore

CopyIgnoredFile: Attempting to Copy file "." that is excluded by .dockerignore More info: https://docs.docker.com/go/dockerfile/rule/copy-ignored-file/

Check warning on line 20 in Dockerfile

View workflow job for this annotation

GitHub Actions / docker

Attempting to Copy file that is excluded by .dockerignore

CopyIgnoredFile: Attempting to Copy file "." that is excluded by .dockerignore More info: https://docs.docker.com/go/dockerfile/rule/copy-ignored-file/

Check warning on line 20 in Dockerfile

View workflow job for this annotation

GitHub Actions / e2e-test

Attempting to Copy file that is excluded by .dockerignore

CopyIgnoredFile: Attempting to Copy file "." that is excluded by .dockerignore More info: https://docs.docker.com/go/dockerfile/rule/copy-ignored-file/
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags="-w -s -X 'github.com/nuts-foundation/nuts-node/core.GitCommit=${GIT_COMMIT}' -X 'github.com/nuts-foundation/nuts-node/core.GitBranch=${GIT_BRANCH}' -X 'github.com/nuts-foundation/nuts-node/core.GitVersion=${GIT_VERSION}'" -o /opt/nuts/nuts

# alpine
Expand All @@ -25,7 +25,10 @@
RUN apk update \
&& apk add --no-cache \
tzdata \
curl
curl \
ca-certificates
COPY pki/cacerts/* /usr/local/share/ca-certificates/
RUN update-ca-certificates
COPY --from=builder /opt/nuts/nuts /usr/bin/nuts

HEALTHCHECK --start-period=30s --timeout=5s --interval=10s \
Expand Down
16 changes: 16 additions & 0 deletions LSPxNuts_README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# LSPxNuts Proof of Concept

This is a branch that for the Proof of Concept of the LSPxNuts project.

It adds or alters the following functionality versus the mainstream Nuts node:

- OAuth2 `vp_bearer` token exchange: read presentation definition from local definitions instead of fetching it from the remote authorization server.
LSP doesn't support presentation definitions, meaning that we need to look it up locally.
- Add support for JWT bearer grant type. If the server supports this, it uses this grant type instead of the Nuts-specific vp_token-bearer grant type.
- Add CA certificates of Sectigo (root CA, OV and EV intermediate CA) to Docker image's OS CA bundle, because they're used by AORTA-LSP.
- Fix marshalling of Verifiable Presentations in JWT format; `type` was marshalled as JSON-LD (single-entry-array was replaced by string)
- Add `policy_id` field to access token request to specify the Presentation Definition that should be used.
The `scope` can then be specified as whatever the use case requires (e.g. SMART on FHIR-esque scopes).
- Relax `did:x509` key usage check: the certificate from UZI smart cards that is used to sign credentials, doesn't have `serverAuth` key usage, only `digitalSignature`.
This broke, since we didn't specify the key usage, but `x509.Verify()` expects key usage `serverAuth` to be present by default.
- Add support for `RS256` (RSA 2048) signatures, since that's what UZI smart cards produce.
4 changes: 4 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ The following options can be configured on the server:
verbosity info Log level (trace, debug, info, warn, error)
httpclient.timeout 30s Request time-out for HTTP clients, such as '10s'. Refer to Golang's 'time.Duration' syntax for a more elaborate description of the syntax.
**Auth**
auth.granttypes [authorization_code,vp_token-bearer,urn:ietf:params:oauth:grant-type:jwt-bearer] enables OAuth2 grant types for the Authorization Server, options: authorization_code, urn:ietf:params:oauth:grant-type:pre-authorized_code, vp_token-bearer, urn:ietf:params:oauth:grant-type:jwt-bearer
auth.authorizationendpoint.enabled false enables the v2 API's OAuth2 Authorization Endpoint, used by OpenID4VP and OpenID4VCI. This flag might be removed in a future version (or its default become 'true') as the use cases and implementation of OpenID4VP and OpenID4VCI mature.
**Crypto**
crypto.storage Storage to use, 'fs' for file system (for development purposes), 'vaultkv' for HashiCorp Vault KV store, 'azure-keyvault' for Azure Key Vault, 'external' for an external backend (deprecated).
Expand Down Expand Up @@ -233,6 +234,9 @@ The following options can be configured on the server:
tracing.endpoint OTLP collector endpoint for OpenTelemetry tracing (e.g., 'localhost:4318'). When empty, tracing is disabled.
tracing.insecure false Disable TLS for the OTLP connection.
tracing.servicename Service name reported to the tracing backend. Defaults to 'nuts-node'.
**VCR**
vcr.dezi.allowedjku [] List of allowed JKU URLs for fetching Dezi attestation keys. If not set, defaults to production (https://auth.dezi.nl/dezi/jwks.json), and in non-strict mode also acceptance (https://acceptatie.auth.dezi.nl/dezi/jwks.json).
vcr.verifier.revocation.maxage 15m0s Max age of revocation information. If the revocation information is older than this, it will be refreshed from the issuer. If set to 0 or negative, revocation information will always be refreshed.
**policy**
policy.directory ./config/policy Directory to read policy files from. Policy files are JSON files that contain a scope to PresentationDefinition mapping.
======================================== =================================================================================================================================================================================================================================================================================================================================================================================================================================================================== ============================================================================================================================================================================================================================================================================================================================================
Expand Down
40 changes: 27 additions & 13 deletions auth/api/auth/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,13 +260,9 @@ func (w Wrapper) DrawUpContract(ctx context.Context, request DrawUpContractReque

// CreateJwtGrant handles the http request (from the vendor's EPD/XIS) for creating a JWT bearer token which can be used to retrieve an access token from a remote Nuts node.
func (w Wrapper) CreateJwtGrant(ctx context.Context, request CreateJwtGrantRequestObject) (CreateJwtGrantResponseObject, error) {

req := services.CreateJwtGrantRequest{
Requester: request.Body.Requester,
Authorizer: request.Body.Authorizer,
IdentityVP: request.Body.Identity,
Service: request.Body.Service,
Credentials: request.Body.Credentials,
req, err := toCreateJwtGrantRequest(request.Body)
if err != nil {
return nil, err
}

response, err := w.Auth.RelyingParty().CreateJwtGrant(ctx, req)
Expand All @@ -279,12 +275,9 @@ func (w Wrapper) CreateJwtGrant(ctx context.Context, request CreateJwtGrantReque

// RequestAccessToken handles the HTTP request (from the vendor's EPD/XIS) for creating a JWT grant and using it as authorization grant to get an access token from the remote Nuts node.
func (w Wrapper) RequestAccessToken(ctx context.Context, request RequestAccessTokenRequestObject) (RequestAccessTokenResponseObject, error) {
req := services.CreateJwtGrantRequest{
Requester: request.Body.Requester,
Authorizer: request.Body.Authorizer,
IdentityVP: request.Body.Identity,
Service: request.Body.Service,
Credentials: request.Body.Credentials,
req, err := toCreateJwtGrantRequest(request.Body)
if err != nil {
return nil, err
}

jwtGrant, err := w.Auth.RelyingParty().CreateJwtGrant(ctx, req)
Expand Down Expand Up @@ -430,6 +423,27 @@ func (w *Wrapper) resolveCredential(credentialID string) (*vc.VerifiableCredenti
return w.CredentialResolver.Resolve(*id, nil)
}

// toCreateJwtGrantRequest translates the generated API request body into the internal
// services.CreateJwtGrantRequest. If authorization_server_endpoint is set, it is validated
// as a URL; an invalid URL is surfaced as a 400 InvalidInputError so we don't produce a
// signed JWT with a malformed audience.
func toCreateJwtGrantRequest(body *JwtGrantBaseRequest) (services.CreateJwtGrantRequest, error) {
req := services.CreateJwtGrantRequest{
Requester: body.Requester,
Authorizer: body.Authorizer,
IdentityVP: body.Identity,
Service: body.Service,
Credentials: body.Credentials,
}
if body.AuthorizationServerEndpoint != nil && *body.AuthorizationServerEndpoint != "" {
if _, err := url.Parse(*body.AuthorizationServerEndpoint); err != nil {
return services.CreateJwtGrantRequest{}, core.InvalidInputError("invalid authorization_server_endpoint: %w", err)
}
req.AuthorizationServerEndpoint = *body.AuthorizationServerEndpoint
}
return req, nil
}

// convertToMap converts an object to a map[string]interface{} using json conversion
func convertToMap(obj interface{}, target interface{}) error {
jsonStr, err := json.Marshal(obj)
Expand Down
62 changes: 57 additions & 5 deletions auth/api/auth/v1/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ package v1
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"time"

ssi "github.com/nuts-foundation/go-did"
"github.com/nuts-foundation/go-did/did"
"github.com/nuts-foundation/go-did/vc"
Expand All @@ -40,11 +46,6 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"time"
)

type TestContext struct {
Expand Down Expand Up @@ -98,6 +99,10 @@ func (m *mockAuthClient) SupportedDIDMethods() []string {
return m.supportedDIDMethods
}

func (m *mockAuthClient) GrantTypes() []string {
return oauth2.SupportedGrantTypes()
}

func createContext(t *testing.T) *TestContext {
t.Helper()
ctrl := gomock.NewController(t)
Expand Down Expand Up @@ -439,6 +444,53 @@ func TestWrapper_CreateJwtGrant(t *testing.T) {
assert.Equal(t, expectedResponse, response)
assert.Nil(t, err)
})

t.Run("AuthorizationServerEndpoint is plumbed through", func(t *testing.T) {
ctx := createContext(t)
endpoint := "https://as.example.nl/ura/12345678/token"
body := CreateJwtGrantRequest{
Requester: vdr.TestDIDA.String(),
Authorizer: vdr.TestDIDB.String(),
Service: "service",
AuthorizationServerEndpoint: &endpoint,
}

expectedRequest := services.CreateJwtGrantRequest{
Requester: body.Requester,
Authorizer: body.Authorizer,
Service: "service",
AuthorizationServerEndpoint: endpoint,
}

ctx.relyingPartyMock.EXPECT().CreateJwtGrant(gomock.Any(), expectedRequest).Return(&services.JwtBearerTokenResult{
BearerToken: "jwt",
AuthorizationServerEndpoint: endpoint,
}, nil)

response, err := ctx.wrapper.CreateJwtGrant(ctx.audit, CreateJwtGrantRequestObject{Body: &body})

assert.NoError(t, err)
assert.Equal(t, CreateJwtGrant200JSONResponse{BearerToken: "jwt", AuthorizationServerEndpoint: endpoint}, response)
})

t.Run("invalid AuthorizationServerEndpoint returns 400", func(t *testing.T) {
ctx := createContext(t)
badEndpoint := "%invalid"
body := CreateJwtGrantRequest{
Requester: vdr.TestDIDA.String(),
Authorizer: vdr.TestDIDB.String(),
Service: "service",
AuthorizationServerEndpoint: &badEndpoint,
}

response, err := ctx.wrapper.CreateJwtGrant(ctx.audit, CreateJwtGrantRequestObject{Body: &body})

assert.Nil(t, response)
require.Error(t, err)
assert.Contains(t, err.Error(), "invalid authorization_server_endpoint")
require.Implements(t, new(core.HTTPStatusCodeError), err)
assert.Equal(t, http.StatusBadRequest, err.(core.HTTPStatusCodeError).StatusCode())
})
}

func TestWrapper_RequestAccessToken(t *testing.T) {
Expand Down
49 changes: 26 additions & 23 deletions auth/api/auth/v1/client/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading