[java-idp-plugin-vci] 03/03: Openid VCI version of Oauth2 authentication flow to add method 'attest_jwt_client_auth'. Idea is to later merge it with parent project flow

Codeberg noreply at shibboleth.net
Thu Sep 24 09:43:08 UTC 2026


This is an automated email from the git hooks/post-receive script.

codeberg pushed a commit to branch main
in repository java-idp-plugin-vci.

View the commit online:
https://codeberg.org/Shibboleth/java-idp-plugin-vci/commit/5cd81e8e6f229dca49019225d67eec16703beb7c

commit 5cd81e8e6f229dca49019225d67eec16703beb7c
Author: Janne Lauros <janne.lauros at csc.fi>
AuthorDate: Thu Sep 24 12:42:38 2026 +0300

    Openid VCI version of Oauth2 authentication flow to add method 'attest_jwt_client_auth'. Idea is to later merge it with parent project flow
---
 README.md                                          |  54 ++++-
 .../impl/ClientAttestationPresentPredicate.java    |  56 ++++++
 .../impl/ValidateClientAuthenticationType.java     | 217 +++++++++++++++++++++
 .../META-INF/net.shibboleth.idp/postconfig.xml     |  23 ++-
 .../OpenIDVCIClient/OpenIDVCIClient-beans.xml      |  23 +++
 .../authn/OpenIDVCIClient/OpenIDVCIClient-flow.xml |  74 +++++++
 6 files changed, 436 insertions(+), 11 deletions(-)

diff --git a/README.md b/README.md
index 764ef0c..f3474d0 100644
--- a/README.md
+++ b/README.md
@@ -1096,7 +1096,44 @@ in *conf/global.xml*:
 </util:list>
 ```
 
-To authenticate the wallet with a Wallet Attestation, add the validator to the login flow in
+To have the wallet authenticate with a Wallet Attestation rather than as a public client,
+register the method in *metadata/oidc-client.json*:
+
+```json
+{
+  "client_id": "https://wallet.example.org",
+  "client_name": "Example Wallet",
+  "redirect_uris": ["https://wallet.example.org/callback"],
+  "scope": "openid GeantIncubatorDiploma_SDJWT GeantIncubatorDiploma_W3C",
+  "audience": "credentials",
+  "token_endpoint_auth_method": "attest_jwt_client_auth",
+  "response_types": ["code"],
+  "grant_types": ["authorization_code"]
+}
+```
+
+and enable the method on the profiles of that wallet in *conf/relying-party.xml*, the
+`client_id` above being the `sub` of its Wallet Attestation:
+
+```xml
+<bean parent="RelyingPartyByName" c:relyingPartyIds="#{{'https://wallet.example.org'}}">
+  <property name="profileConfigurations">
+      <list>
+          <bean parent="OIDC.SSO"
+                p:authorizationCodeClaimsSetManipulationStrategy-ref="openidvci.TokenManipulationStrategy"
+                p:forcePKCE="true" p:requirePushedAuthorizationRequest="true"
+                p:includeIssuerInResponse="true" />
+          <bean parent="OpenID.VCI.Token"
+                p:accessTokenClaimsSetManipulationStrategy-ref="openidvci.TokenManipulationStrategy"
+                p:tokenEndpointAuthMethods="#{{'attest_jwt_client_auth'}}" />
+          <bean parent="OpenID.VCI.Credentials" />
+          <bean parent="OAUTH2.PAR" p:tokenEndpointAuthMethods="#{{'attest_jwt_client_auth'}}" />
+      </list>
+  </property>
+</bean>
+```
+
+and add the validator to the login flow in
 *conf/authn/oauth2client-authn-config.xml*, before the validator that authenticates a public
 client:
 
@@ -1109,15 +1146,12 @@ client:
 </util:list>
 ```
 
-The same bean serves the PAR endpoint of the OP when you wire it there.
-
-To name this deployment in the authorization response, set it on the wallet's **OIDC.SSO** in
-*conf/relying-party.xml*, beside the PKCE and PAR settings of the
-[Authorization code flow](#authorization-code-flow):
+The PAR endpoint is the OP's, and it refuses a client registered with a method its own login
+flow does not know. Give the OAuth endpoints of the OP the login flow of this plugin, in
+*conf/oidc.properties*:
 
-```xml
-<bean parent="OIDC.SSO" p:authorizationCodeClaimsSetManipulationStrategy-ref="openidvci.TokenManipulationStrategy"
-    p:forcePKCE="true" p:requirePushedAuthorizationRequest="true" p:includeIssuerInResponse="true" />
+```properties
+idp.oauth2.authn.flows = OpenIDVCIClient
 ```
 
 To advertise all of it, add the members in *static/oauth-authorization-server.json*:
@@ -2165,7 +2199,7 @@ plugin and is loaded automatically, you do not need to touch `idp.additionalProp
 | Name | Type | Default | Description |
 |---|---|---|---|
 | `openidvci.issuer` | String | | Credential Issuer value used in credentials and in Status List Tokens. Empty and `did:jwk` both mean the did:jwk of the signing key, `did:web` the did:web of this deployment and `url` the issuer of the OP. Any other value is the identifier itself. A Credential Configuration is able to state an `issuer` of its own and override this per credential. See [Credential Issuer identifiers](#credential-issuer-identifiers). |
-| `openidvci.authn.flows` | String | `OAuth2Client` | Regular expression matching the login flows to enable for VCI endpoints. |
+| `openidvci.authn.flows` | String | `OpenIDVCIClient` | Regular expression matching the login flows to enable for VCI endpoints. `OpenIDVCIClient` is the `OAuth2Client` flow of the OP with `attest_jwt_client_auth` as a client authentication method. |
 | `openidvci.StorageService` | Bean ID | Bean named **shibboleth.StorageService** | Storage for Credential Offers. Requires server-side storage. |
 | `openidvci.preAuthorizedCode.defaultLifetime` | Duration | `PT10M` | Lifetime of the pre-authorized code. |
 | `openidvci.preAuthorizedCode.defaultLength` | Integer | `0` | Length of pre-authorized codes. `0` means untruncated and self-contained; `10` or more means a stored offer. |
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/authn/impl/ClientAttestationPresentPredicate.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/authn/impl/ClientAttestationPresentPredicate.java
new file mode 100644
index 0000000..78fe6e1
--- /dev/null
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/authn/impl/ClientAttestationPresentPredicate.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2025, GÉANT
+ *
+ * Licensed under the Apache License, Version 2.0 (the “License”); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an “AS IS” BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.geant.shibboleth.plugin.openidvci.authn.impl;
+
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import jakarta.servlet.http.HttpServletRequest;
+import net.shibboleth.shared.logic.Constraint;
+
+/** Condition signaling that the request carries a Wallet Attestation. */
+public class ClientAttestationPresentPredicate implements Predicate<ProfileRequestContext> {
+
+    /** Supplier of the servlet request the headers are read from. */
+    @Nonnull
+    private final Supplier<HttpServletRequest> httpServletRequestSupplier;
+
+    /**
+     * Constructor.
+     *
+     * @param supplier supplier of the servlet request
+     */
+    public ClientAttestationPresentPredicate(@Nonnull final Supplier<HttpServletRequest> supplier) {
+        httpServletRequestSupplier = Constraint.isNotNull(supplier, "Servlet request supplier cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean test(@Nullable final ProfileRequestContext profileRequestContext) {
+
+        final HttpServletRequest request = httpServletRequestSupplier.get();
+        return request != null
+                && request.getHeader(ClientAttestationCredentialValidator.ATTESTATION_HEADER) != null
+                && request.getHeader(ClientAttestationCredentialValidator.POP_HEADER) != null;
+    }
+
+}
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/authn/impl/ValidateClientAuthenticationType.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/authn/impl/ValidateClientAuthenticationType.java
new file mode 100644
index 0000000..4a995b7
--- /dev/null
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/authn/impl/ValidateClientAuthenticationType.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) 2025, GÉANT
+ *
+ * Licensed under the Apache License, Version 2.0 (the “License”); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an “AS IS” BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.geant.shibboleth.plugin.openidvci.authn.impl;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
+
+import jakarta.servlet.http.HttpServletRequest;
+import net.shibboleth.idp.authn.AbstractAuthenticationAction;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.oidc.authn.context.OAuth2ClientAuthenticationContext;
+import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
+import net.shibboleth.oidc.profile.oauth2.config.OAuth2ClientAuthenticableProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Validates the client authentication method of a request against the method the client registered and
+ * the methods the profile configuration enables, the Wallet Attestation of OpenID4VCI included as
+ * {@link #ATTEST_JWT_CLIENT_AUTH}.
+ */
+public class ValidateClientAuthenticationType extends AbstractAuthenticationAction {
+
+    /** Method of a request authenticated with a Wallet Attestation. */
+    @Nonnull
+    public static final ClientAuthenticationMethod ATTEST_JWT_CLIENT_AUTH =
+            new ClientAuthenticationMethod("attest_jwt_client_auth");
+
+    /** Class logger. */
+    @Nonnull
+    private final Logger log = LoggerFactory.getLogger(ValidateClientAuthenticationType.class);
+
+    /** Strategy used to locate the {@link OIDCMetadataContext}. */
+    @Nonnull
+    private Function<ProfileRequestContext, OIDCMetadataContext> oidcMetadataContextLookupStrategy;
+
+    /** Strategy used to locate the methods the profile configuration enables. */
+    @Nonnull
+    private Function<ProfileRequestContext, Set<ClientAuthenticationMethod>> tokenEndpointAuthMethodsLookupStrategy;
+
+    /** Metadata of the client. */
+    @Nullable
+    private OIDCMetadataContext oidcMetadataContext;
+
+    /** Client authentication of the request. */
+    @Nullable
+    private ClientAuthentication clientAuthentication;
+
+    /** Methods the profile configuration enables. */
+    @Nonnull
+    private Set<ClientAuthenticationMethod> enabledMethods;
+
+    /**
+     * Constructor.
+     */
+    public ValidateClientAuthenticationType() {
+        final Function<ProfileRequestContext, OIDCMetadataContext> strategy =
+                new ChildContextLookup<>(OIDCMetadataContext.class).compose(new InboundMessageContextLookup());
+        assert strategy != null;
+        oidcMetadataContextLookupStrategy = strategy;
+        tokenEndpointAuthMethodsLookupStrategy = new EnabledMethodsLookupFunction();
+        enabledMethods = Collections.emptySet();
+    }
+
+    /**
+     * Set the strategy used to locate the {@link OIDCMetadataContext}.
+     *
+     * @param strategy lookup strategy
+     */
+    public void setOidcMetadataContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, OIDCMetadataContext> strategy) {
+        checkSetterPreconditions();
+
+        oidcMetadataContextLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+    }
+
+    /**
+     * Set the strategy used to locate the methods the profile configuration enables.
+     *
+     * @param strategy lookup strategy
+     */
+    public void setTokenEndpointAuthMethodsLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, Set<ClientAuthenticationMethod>> strategy) {
+        checkSetterPreconditions();
+
+        tokenEndpointAuthMethodsLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final AuthenticationContext authenticationContext) {
+
+        if (!super.doPreExecute(profileRequestContext, authenticationContext)) {
+            return false;
+        }
+        final OAuth2ClientAuthenticationContext oauth2Ctx =
+                authenticationContext.getSubcontext(OAuth2ClientAuthenticationContext.class);
+        if (oauth2Ctx != null) {
+            clientAuthentication = oauth2Ctx.getClientAuthentication();
+        }
+        oidcMetadataContext = oidcMetadataContextLookupStrategy.apply(profileRequestContext);
+        final Set<ClientAuthenticationMethod> methods =
+                tokenEndpointAuthMethodsLookupStrategy.apply(profileRequestContext);
+        enabledMethods = methods != null ? methods : Collections.emptySet();
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final AuthenticationContext authenticationContext) {
+
+        ClientAuthenticationMethod registeredMethod = null;
+        if (oidcMetadataContext != null) {
+            final OIDCClientInformation clientInformation = oidcMetadataContext.getClientInformation();
+            if (clientInformation != null) {
+                final OIDCClientMetadata clientMetadata = clientInformation.getOIDCMetadata();
+                registeredMethod = clientMetadata.getTokenEndpointAuthMethod() != null
+                        ? clientMetadata.getTokenEndpointAuthMethod()
+                        : ClientAuthenticationMethod.CLIENT_SECRET_BASIC;
+            }
+        }
+        final ClientAuthenticationMethod used = resolveUsedMethod();
+        if (registeredMethod != null && !registeredMethod.equals(used)) {
+            log.warn("{} Client registered {} but attempted {}", getLogPrefix(), registeredMethod, used);
+            ActionSupport.buildEvent(profileRequestContext, EventIds.ACCESS_DENIED);
+            return;
+        }
+        if (!enabledMethods.contains(used)) {
+            log.warn("{} Requested method {} not enabled in profile configuration", getLogPrefix(), used);
+            ActionSupport.buildEvent(profileRequestContext, EventIds.ACCESS_DENIED);
+        }
+    }
+
+    /**
+     * Function reading the methods the profile configuration enables.
+     */
+    private static class EnabledMethodsLookupFunction
+            implements Function<ProfileRequestContext, Set<ClientAuthenticationMethod>> {
+
+        /** Strategy used to locate the {@link RelyingPartyContext}. */
+        @Nonnull
+        private final Function<ProfileRequestContext, RelyingPartyContext> relyingPartyContextLookupStrategy =
+                new ChildContextLookup<>(RelyingPartyContext.class);
+
+        /** {@inheritDoc} */
+        @Override
+        @Nonnull
+        public Set<ClientAuthenticationMethod> apply(@Nullable final ProfileRequestContext profileRequestContext) {
+
+            final RelyingPartyContext rpCtx = relyingPartyContextLookupStrategy.apply(profileRequestContext);
+            if (rpCtx == null || !(rpCtx
+                    .getProfileConfig() instanceof OAuth2ClientAuthenticableProfileConfiguration configuration)) {
+                return Collections.emptySet();
+            }
+            return configuration.getTokenEndpointAuthMethods(profileRequestContext).stream()
+                    .map(ClientAuthenticationMethod::new).collect(Collectors.toUnmodifiableSet());
+        }
+
+    }
+
+    /**
+     * Read the method the request is authenticated with.
+     *
+     * @return the method
+     */
+    @Nonnull
+    private ClientAuthenticationMethod resolveUsedMethod() {
+
+        if (clientAuthentication != null) {
+            final ClientAuthenticationMethod method = clientAuthentication.getMethod();
+            assert method != null;
+            return method;
+        }
+        final HttpServletRequest request = getHttpServletRequest();
+        if (request != null
+                && request.getHeader(ClientAttestationCredentialValidator.ATTESTATION_HEADER) != null
+                && request.getHeader(ClientAttestationCredentialValidator.POP_HEADER) != null) {
+            return ATTEST_JWT_CLIENT_AUTH;
+        }
+        return ClientAuthenticationMethod.NONE;
+    }
+
+}
diff --git a/openid-vci-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/openid-vci-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index d69fecd..782627b 100644
--- a/openid-vci-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/openid-vci-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -16,10 +16,31 @@
         class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"
         p:placeholderPrefix="%{" p:placeholderSuffix="}" />
         
+    <!-- Login flow of the endpoints of this plugin, the OAuth2Client flow of the OP with the
+         Wallet Attestation of OpenID4VCI as a client authentication method. -->
+    <bean p:id="authn/OpenIDVCIClient" parent="shibboleth.AuthenticationFlow"
+            p:order="%{idp.authn.OpenIDVCIClient.order:1000}"
+            p:nonBrowserSupported="true"
+            p:passiveAuthenticationSupported="true"
+            p:forcedAuthenticationSupported="true"
+            p:proxyRestrictionsEnforced="true"
+            p:proxyScopingEnforced="false"
+            p:discoveryRequired="false"
+            p:lifetime="PT60S"
+            p:inactivityTimeout="PT60S"
+            p:reuseCondition-ref="shibboleth.Conditions.FALSE"
+            p:activationCondition-ref="#{'%{idp.authn.OpenIDVCIClient.activationCondition:shibboleth.Conditions.TRUE}'.trim()}"
+            p:subjectDecorator="#{getObject('%{idp.authn.OpenIDVCIClient.subjectDecorator:}'.trim())}">
+        <property name="supportedPrincipalsByString">
+            <bean parent="shibboleth.CommaDelimStringArray"
+                c:_0="#{'%{idp.authn.OpenIDVCIClient.supportedPrincipals:}'.trim()}" />
+        </property>
+    </bean>
+
     <!-- Property-based definition of login flows for OAuth endpoints. -->
     <bean id="openidvci.PotentialFlows"
         class="org.springframework.beans.factory.config.ListFactoryBean"
-        p:sourceList="#{getObject('shibboleth.AuthenticationFlowDescriptorManager').getComponents().?[id matches 'authn/(' + '%{openidvci.authn.flows:OAuth2Client}'.trim() + ')']}" />
+        p:sourceList="#{getObject('shibboleth.AuthenticationFlowDescriptorManager').getComponents().?[id matches 'authn/(' + '%{openidvci.authn.flows:OpenIDVCIClient}'.trim() + ')']}" />
 
     <bean id="openidvci.PublicClientValidator"
         class="org.geant.shibboleth.plugin.openidvci.authn.impl.WalletCredentialValidator" />
diff --git a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OpenIDVCIClient/OpenIDVCIClient-beans.xml b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OpenIDVCIClient/OpenIDVCIClient-beans.xml
new file mode 100644
index 0000000..6a0469b
--- /dev/null
+++ b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OpenIDVCIClient/OpenIDVCIClient-beans.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Actions of this plugin for the authn/OpenIDVCIClient flow. Every other bean of the flow is
+    the one of authn/OAuth2Client of the OpenID Connect Provider plugin, imported as it is.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:c="http://www.springframework.org/schema/c"
+       xmlns:p="http://www.springframework.org/schema/p"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
+
+       default-init-method="initialize"
+       default-destroy-method="destroy">
+
+    <bean id="OpenIDVCIClientAttestationPresent"
+        class="org.geant.shibboleth.plugin.openidvci.authn.impl.ClientAttestationPresentPredicate"
+        c:supplier-ref="shibboleth.HttpServletRequestSupplier" />
+
+    <bean id="OpenIDVCIValidateClientAuthenticationType"
+        class="org.geant.shibboleth.plugin.openidvci.authn.impl.ValidateClientAuthenticationType" scope="prototype"
+        p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier" />
+
+</beans>
diff --git a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OpenIDVCIClient/OpenIDVCIClient-flow.xml b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OpenIDVCIClient/OpenIDVCIClient-flow.xml
new file mode 100644
index 0000000..c5c51b4
--- /dev/null
+++ b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OpenIDVCIClient/OpenIDVCIClient-flow.xml
@@ -0,0 +1,74 @@
+<!--
+    The authn/OAuth2Client flow of the OpenID Connect Provider plugin with the Wallet Attestation
+    of OpenID4VCI as a client authentication method. Idea is to merge this flow and the one of
+    the OP to one at some point in future.
+
+    The beans of the OP are imported as they are and referred to by their id, the actions of this
+    plugin are the two below.
+
+    ADDED
+      CheckClientAttestation state   branches a request carrying the attestation headers aside
+      AttestedClient state           that branch, no client authentication of the OP to extract
+      OpenIDVCIValidateClientAuthenticationType   reads 'attest_jwt_client_auth' as the method
+
+    REPLACED
+      ValidateClientAuthenticationType of the OP, by the action above
+-->
+<flow xmlns="http://www.springframework.org/schema/webflow"
+      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+      xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow.xsd"
+      parent="authn.abstract">
+
+    <action-state id="OpenIDVCIClient">
+        <evaluate expression="PopulateTokenEndpointJwtSignatureValidationParameters"/>
+        <evaluate expression="'proceed'" />
+
+        <transition on="proceed" to="CheckClientAttestation" />
+    </action-state>
+
+    <decision-state id="CheckClientAttestation">
+        <if test="OpenIDVCIClientAttestationPresent.test(opensamlProfileRequestContext)"
+            then="AttestedClient" else="StandardClient" />
+    </decision-state>
+
+    <action-state id="StandardClient">
+        <evaluate expression="ExtractClientAuthenticationFromRequest" />
+        <evaluate expression="'proceed'" />
+
+        <transition on="proceed" to="ValidateClient" />
+    </action-state>
+
+    <action-state id="AttestedClient">
+        <evaluate expression="'proceed'" />
+
+        <transition on="proceed" to="ValidateClient" />
+    </action-state>
+
+    <action-state id="ValidateClient">
+        <evaluate expression="OpenIDVCIValidateClientAuthenticationType" />
+        <evaluate expression="ValidateJWTSignature"/>
+        <evaluate expression="ValidateCredentials" />
+        <evaluate expression="PopulateSubjectCanonicalizationContext" />
+        <evaluate expression="'proceed'" />
+
+        <transition on="proceed" to="CallSubjectCanonicalization" />
+    </action-state>
+
+    <subflow-state id="CallSubjectCanonicalization" subflow="c14n">
+        <input name="calledAsSubflow" value="true" />
+        <transition on="proceed" to="proceed" />
+
+        <transition on="SubjectCanonicalizationError" to="ReselectFlow" />
+    </subflow-state>
+
+    <global-transitions>
+        <transition on="NoCredentials" to="ReselectFlow" />
+        <transition on="InvalidCredentials" to="ReselectFlow" />
+        <transition on="RequestUnsupported" to="ReselectFlow" />
+        <transition on="UnknownUsername" to="ReselectFlow" />
+    </global-transitions>
+
+    <bean-import resource="classpath:/META-INF/net/shibboleth/idp/flows/authn/OAuth2Client/OAuth2Client-beans.xml" />
+    <bean-import resource="classpath:/META-INF/net/shibboleth/idp/flows/authn/OpenIDVCIClient/OpenIDVCIClient-beans.xml" />
+
+</flow>

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list