[java-idp-plugin-vci] branch dev/REFRESH updated: Adopt OP token flow features, Refresh tokens etc.Credential Issuer Identifier as access token audience

Codeberg noreply at shibboleth.net
Wed Nov 4 09:00:36 UTC 2026


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

codeberg pushed a commit to branch dev/REFRESH
in repository java-idp-plugin-vci.

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

The following commit(s) were added to refs/heads/dev/REFRESH by this push:
     new 7e5f791  Adopt OP token flow features, Refresh tokens etc.Credential Issuer Identifier as access token audience
7e5f791 is described below

commit 7e5f791cb283d2cbd0cb2a75bf81ae8845d2a01e
Author: Janne Lauros <janne.lauros at csc.fi>
AuthorDate: Wed Nov 4 10:59:50 2026 +0200

    Adopt OP token flow features, Refresh tokens etc.Credential Issuer Identifier as access token audience
---
 .../impl/OpenIDVCITokenRequestDecoder.java         |  81 +-
 .../PotentialCredentialsLookupFunction.java        |  45 +
 .../messaging/impl/OpenIDVCITokenRequest.java      | 191 ++++-
 .../impl/DefaultOpenIDVCITokenConfiguration.java   |  90 +-
 .../impl/FormOutboundTokenResponseMessage.java     |  83 +-
 .../impl/InitializePotentialCredentials.java       |  65 ++
 .../SetCredentialsAudienceToResponseContext.java   |  70 +-
 ...wrapGrant.java => UnwrapPreAuthorizedCode.java} |  88 +-
 .../idp/flows/did/document/document-beans.xml      |   4 +-
 .../authorization-server-beans.xml                 |   4 +-
 .../oauth/jwt-vc-issuer/jwt-vc-issuer-beans.xml    |   4 +-
 .../status-list/assignments/assignments-beans.xml  |   6 +-
 .../flows/oauth/status-list/list/list-beans.xml    |   4 +-
 .../oauth/status-list/revoke/revoke-beans.xml      |   6 +-
 .../openid/vci/abstract-api/abstract-api-flow.xml  |   6 +-
 .../credential-offer/credential-offer-beans.xml    |   6 +-
 .../openid/vci/credentials/credentials-beans.xml   |   6 +-
 .../vci/issuer-metadata/issuer-metadata-beans.xml  |   4 +-
 .../idp/flows/openid/vci/nonce/nonce-beans.xml     |   4 +-
 .../idp/flows/openid/vci/token/token-beans.xml     | 917 +++------------------
 .../idp/flows/openid/vci/token/token-flow.xml      |  95 ++-
 .../idp/service/relying-party/postconfig.xml       |  35 +-
 .../plugin/openidvci/conf/openid-vci.properties    |   4 +
 .../static/oauth-authorization-server.json         |   1 +
 .../impl/OpenIDVCITokenRequestDecoderTest.java     |  28 +-
 .../messaging/impl/OpenIDVCITokenRequestTest.java  |  63 +-
 .../DefaultOpenIDVCITokenConfigurationTest.java    |  18 +-
 ...etCredentialsAudienceToResponseContextTest.java |  52 +-
 28 files changed, 873 insertions(+), 1107 deletions(-)

diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/decoding/impl/OpenIDVCITokenRequestDecoder.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/decoding/impl/OpenIDVCITokenRequestDecoder.java
index e74bb39..e64135c 100644
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/decoding/impl/OpenIDVCITokenRequestDecoder.java
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/decoding/impl/OpenIDVCITokenRequestDecoder.java
@@ -14,42 +14,65 @@
  * limitations under the License.
  */
 
+/*
+ * Derived from net.shibboleth.idp.plugin.oidc.op.decoding.impl.OIDCTokenRequestDecoder
+ * of the Shibboleth OpenID Connect Provider plugin, Apache License 2.0, modified by GÉANT.
+ */
+
 package org.geant.shibboleth.plugin.openidvci.decoding.impl;
 
 import java.io.IOException;
 
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
 import org.geant.shibboleth.plugin.openidvci.messaging.impl.OpenIDVCITokenRequest;
 import org.opensaml.messaging.decoder.MessageDecodingException;
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import net.shibboleth.idp.plugin.oidc.op.decoding.impl.RequestUtil;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.base.MoreObjects;
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.TokenRequest;
 import com.nimbusds.oauth2.sdk.http.HTTPRequest;
 import com.nimbusds.oauth2.sdk.http.JakartaServletUtils;
 
+import jakarta.servlet.http.HttpServletRequest;
+import net.shibboleth.idp.plugin.oidc.op.decoding.impl.RequestUtil;
+import net.shibboleth.idp.plugin.oidc.op.decoding.impl.OIDCTokenRequestDecoder;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
 /**
  * Message decoder decoding {@link OpenIDVCITokenRequest}s.
  */
-public class OpenIDVCITokenRequestDecoder extends BaseOpenIDVCIRequestDecoder<OpenIDVCITokenRequest> {
+public class OpenIDVCITokenRequestDecoder extends OIDCTokenRequestDecoder {
 
     /** Class logger. */
     @Nonnull
     private final Logger log = LoggerFactory.getLogger(OpenIDVCITokenRequestDecoder.class);
 
+    /** Constructor. */
+    public OpenIDVCITokenRequestDecoder() {
+        setProtocolMessageLoggerSubCategory("OAUTH2.OpenIDVCI");
+    }
+
     /** {@inheritDoc} */
     @Override
-    protected OpenIDVCITokenRequest parseMessage() throws MessageDecodingException {
+    protected TokenRequest parseMessage() throws MessageDecodingException {
         try {
-            final HTTPRequest httpReq = JakartaServletUtils.createHTTPRequest(getHttpServletRequest());
-            getProtocolMessageLogger().trace("Inbound request {}", RequestUtil.toString(httpReq));
-            return OpenIDVCITokenRequest.parse(httpReq);
-        } catch (final com.nimbusds.oauth2.sdk.ParseException | IOException e) {
+            final HttpServletRequest httpServletRequest = getHttpServletRequest();
+            if (httpServletRequest != null) {
+                getProtocolMessageLogger().trace("Inbound request {}",
+                        RequestUtil.toString(JakartaServletUtils.createHTTPRequest(httpServletRequest)));
+                final HTTPRequest httpReq = JakartaServletUtils.createHTTPRequest(
+                        new RemoveScopeWhenCodeGrantHttpServletRequestWrapper(httpServletRequest));
+                if (httpReq != null) {
+                    return OpenIDVCITokenRequest.parse(httpReq);
+                }
+            }
+            throw new MessageDecodingException("Could not create HTTPRequest object from the incoming request");
+        } catch (final ParseException | IOException e) {
             log.error("Unable to decode inbound request: {}", e.getMessage());
             throw new MessageDecodingException(e);
         }
@@ -57,28 +80,34 @@ public class OpenIDVCITokenRequestDecoder extends BaseOpenIDVCIRequestDecoder<Op
 
     /** {@inheritDoc} */
     @Override
-    protected String getMessageToLog(final OpenIDVCITokenRequest message) {
+    protected String getMessageToLog(@Nullable final TokenRequest message) {
+        if (!(message instanceof OpenIDVCITokenRequest request)) {
+            return super.getMessageToLog(message);
+        }
         String details = null;
         try {
-            details = message.getCredentialAuthorizationDetails() != null
-                    ? new ObjectMapper().writeValueAsString(message.getCredentialAuthorizationDetails())
+            details = request.getCredentialAuthorizationDetails() != null
+                    ? new ObjectMapper().writeValueAsString(request.getCredentialAuthorizationDetails())
                     : null;
         } catch (final JsonProcessingException e) {
             log.warn("Failed serializing authorization_details", e);
-            // ignore
         }
-        return message == null ? null
-                : MoreObjects.toStringHelper(this).omitNullValues()
-                        .add("grant_type", message.getGrantType())
-                        .add("code", message.getCode())
-                        .add("code_verifier", message.getCodeVerifier())
-                        .add("pre-authorized_code", message.getPreAuthorizedCode())
-                        .add("tx_code", message.getTxCode())
-                        .add("client_authentication",
-                                RequestUtil.getClientAuthenticationLog(message.getClientAuthentication()))
-                        .add("client_id", message.getClientID() != null ? message.getClientID().getValue() : null)
-                        .add("authorization_details", details)
-                        .toString();
+        return MoreObjects.toStringHelper(this).omitNullValues()
+                .add("grant_type", request.getGrantType())
+                .add("code", request.getCode())
+                .add("code_verifier", request.getCodeVerifier())
+                .add("pre-authorized_code", request.getPreAuthorizedCode())
+                .add("tx_code", request.getTxCode())
+                .add("authorizationGrant", RequestUtil.getAuthorizationGrantLog(request.getAuthorizationGrant()))
+                .add("client_authentication", RequestUtil.getClientAuthenticationLog(request.getClientAuthentication()))
+                .add("client_id", request.getClientID() != null ? request.getClientID().getValue() : null)
+                .add("customParameters", request.getCustomParameters())
+                .add("endpointURI", getEndpointURI(request))
+                .add("existingGrant", RequestUtil.getRefreshTokenLog(request.getExistingGrant()))
+                .add("resources", request.getResources())
+                .add("scope", request.getScope())
+                .add("authorization_details", details)
+                .toString();
     }
 
-}
\ No newline at end of file
+}
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/messaging/context/navigate/PotentialCredentialsLookupFunction.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/messaging/context/navigate/PotentialCredentialsLookupFunction.java
new file mode 100644
index 0000000..1dda7e2
--- /dev/null
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/messaging/context/navigate/PotentialCredentialsLookupFunction.java
@@ -0,0 +1,45 @@
+/*
+ * 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.messaging.context.navigate;
+
+import javax.annotation.Nullable;
+
+import org.geant.shibboleth.plugin.openidvci.messaging.context.TokenContext;
+import org.opensaml.messaging.context.navigate.ContextDataLookupFunction;
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
+
+/** A function that returns potential credentials claims set from token context. Null if not able to locate it. */
+public class PotentialCredentialsLookupFunction
+        implements ContextDataLookupFunction<ProfileRequestContext, ClaimsSet> {
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable
+    public ClaimsSet apply(@Nullable final ProfileRequestContext input) {
+        if (input == null || input.getInboundMessageContext() == null) {
+            return null;
+        }
+        final TokenContext ctx = input.ensureInboundMessageContext().getSubcontext(TokenContext.class);
+        if (ctx == null) {
+            return null;
+        }
+        return ctx.getPotentialCredentials();
+    }
+
+}
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/messaging/impl/OpenIDVCITokenRequest.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/messaging/impl/OpenIDVCITokenRequest.java
index 374370d..a5f32dc 100644
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/messaging/impl/OpenIDVCITokenRequest.java
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/messaging/impl/OpenIDVCITokenRequest.java
@@ -18,6 +18,7 @@ package org.geant.shibboleth.plugin.openidvci.messaging.impl;
 
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -31,6 +32,7 @@ import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
 import com.nimbusds.oauth2.sdk.AuthorizationGrant;
 import com.nimbusds.oauth2.sdk.OAuth2Error;
 import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.Scope;
 import com.nimbusds.oauth2.sdk.TokenRequest;
 import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
 import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
@@ -38,6 +40,7 @@ import com.nimbusds.oauth2.sdk.http.HTTPRequest;
 import com.nimbusds.jwt.SignedJWT;
 import com.nimbusds.oauth2.sdk.id.ClientID;
 import com.nimbusds.oauth2.sdk.pkce.CodeVerifier;
+import com.nimbusds.oauth2.sdk.token.RefreshToken;
 import com.nimbusds.oauth2.sdk.util.MultivaluedMapUtils;
 import com.nimbusds.oauth2.sdk.util.StringUtils;
 
@@ -111,7 +114,33 @@ public class OpenIDVCITokenRequest extends TokenRequest {
             @Nonnull final String grantType, @Nullable final String preAuthorizedCode, @Nullable final String code,
             @Nullable final String txCode, @Nullable final String codeVerifier,
             @Nullable final List<OpenIDVCIAuthorizationDetail> authorizationDetails) {
-        super(uri, clientAuth, toAuthorizationGrant(grantType, preAuthorizedCode, code, txCode, codeVerifier));
+        this(uri, clientAuth, grantType, preAuthorizedCode, code, txCode, codeVerifier, null, null,
+                authorizationDetails);
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param uri               The URI of the endpoint (HTTP or HTTPS) for which
+     *                          the request is intended, {@code null} if not
+     *                          specified.
+     * @param clientAuth        The client authentication, {@code null} if none.
+     * @param grantType         Grant type.
+     * @param preAuthorizedCode The pre-authorized code
+     * @param code              Authorization code
+     * @param txCode            The tx-code in pre-authorized flow
+     * @param codeVerifier      The PKCE code verifier
+     * @param scope             The requested scope, {@code null} if not specified.
+     * @param resources         The resource URIs, {@code null} if not specified.
+     * @param authorizationDetails Authorization details of the request
+     */
+    public OpenIDVCITokenRequest(@Nullable final URI uri, @Nullable final ClientAuthentication clientAuth,
+            @Nonnull final String grantType, @Nullable final String preAuthorizedCode, @Nullable final String code,
+            @Nullable final String txCode, @Nullable final String codeVerifier, @Nullable final Scope scope,
+            @Nullable final List<URI> resources,
+            @Nullable final List<OpenIDVCIAuthorizationDetail> authorizationDetails) {
+        super(uri, clientAuth, toAuthorizationGrant(grantType, preAuthorizedCode, code, txCode, codeVerifier),
+                scope, resources, (Map<String, List<String>>) null);
         this.grantType = grantType;
         this.preAuthorizedCode = preAuthorizedCode;
         this.code = code;
@@ -141,8 +170,34 @@ public class OpenIDVCITokenRequest extends TokenRequest {
             @Nullable final String preAuthorizedCode, @Nullable final String code, @Nullable final String txCode,
             @Nullable final String codeVerifier,
             @Nullable final List<OpenIDVCIAuthorizationDetail> authorizationDetails) {
-        super(uri, (ClientID) clientID, toAuthorizationGrant(grantType, preAuthorizedCode, code, txCode,
-                codeVerifier));
+        this(uri, clientID, grantType, preAuthorizedCode, code, txCode, codeVerifier, null, null,
+                authorizationDetails);
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param uri               The URI of the endpoint (HTTP or HTTPS) for which
+     *                          the request is intended, {@code null} if not
+     *                          specified.
+     * @param clientID          The client identifier, {@code null} if not
+     *                          specified.
+     * @param grantType         Grant type.
+     * @param preAuthorizedCode The pre-authorized code
+     * @param code              Authorization code
+     * @param txCode            The tx-code in pre-authorized flow
+     * @param codeVerifier      The PKCE code verifier
+     * @param scope             The requested scope, {@code null} if not specified.
+     * @param resources         The resource URIs, {@code null} if not specified.
+     * @param authorizationDetails Authorization details of the request
+     */
+    public OpenIDVCITokenRequest(@Nullable final URI uri, @Nullable final ClientID clientID,
+            @Nonnull final String grantType,
+            @Nullable final String preAuthorizedCode, @Nullable final String code, @Nullable final String txCode,
+            @Nullable final String codeVerifier, @Nullable final Scope scope, @Nullable final List<URI> resources,
+            @Nullable final List<OpenIDVCIAuthorizationDetail> authorizationDetails) {
+        super(uri, clientID, toAuthorizationGrant(grantType, preAuthorizedCode, code, txCode, codeVerifier),
+                scope, resources, (RefreshToken) null, (Map<String, List<String>>) null);
         this.grantType = grantType;
         this.preAuthorizedCode = preAuthorizedCode;
         this.code = code;
@@ -150,6 +205,55 @@ public class OpenIDVCITokenRequest extends TokenRequest {
         this.codeVerifier = codeVerifier;
         this.authorizationDetails = authorizationDetails;
     }
+
+    /**
+     * Constructor of a request of a grant other than the authorization code and the pre-authorized code.
+     * 
+     * @param uri          The URI of the endpoint, {@code null} if not specified.
+     * @param clientAuth   The client authentication.
+     * @param grant        The authorization grant.
+     * @param scope        The requested scope, {@code null} if not specified.
+     * @param resources    The resource URIs, {@code null} if not specified.
+     * @param customParams Custom parameters, {@code null} if none.
+     * @param authorizationDetails Authorization details of the request
+     */
+    private OpenIDVCITokenRequest(@Nullable final URI uri, @Nonnull final ClientAuthentication clientAuth,
+            @Nonnull final AuthorizationGrant grant, @Nullable final Scope scope, @Nullable final List<URI> resources,
+            @Nullable final Map<String, List<String>> customParams,
+            @Nullable final List<OpenIDVCIAuthorizationDetail> authorizationDetails) {
+        super(uri, clientAuth, grant, scope, resources, customParams);
+        this.grantType = grant.getType().getValue();
+        this.preAuthorizedCode = null;
+        this.code = null;
+        this.txCode = null;
+        this.codeVerifier = null;
+        this.authorizationDetails = authorizationDetails;
+    }
+
+    /**
+     * Constructor of a request of a grant other than the authorization code and the pre-authorized code.
+     * 
+     * @param uri           The URI of the endpoint, {@code null} if not specified.
+     * @param clientID      The client identifier, {@code null} if not specified.
+     * @param grant         The authorization grant.
+     * @param scope         The requested scope, {@code null} if not specified.
+     * @param resources     The resource URIs, {@code null} if not specified.
+     * @param existingGrant Existing refresh token, {@code null} if none.
+     * @param customParams  Custom parameters, {@code null} if none.
+     * @param authorizationDetails Authorization details of the request
+     */
+    private OpenIDVCITokenRequest(@Nullable final URI uri, @Nullable final ClientID clientID,
+            @Nonnull final AuthorizationGrant grant, @Nullable final Scope scope, @Nullable final List<URI> resources,
+            @Nullable final RefreshToken existingGrant, @Nullable final Map<String, List<String>> customParams,
+            @Nullable final List<OpenIDVCIAuthorizationDetail> authorizationDetails) {
+        super(uri, clientID, grant, scope, resources, existingGrant, customParams);
+        this.grantType = grant.getType().getValue();
+        this.preAuthorizedCode = null;
+        this.code = null;
+        this.txCode = null;
+        this.codeVerifier = null;
+        this.authorizationDetails = authorizationDetails;
+    }
 // Checkstyle: ParameterNumber ON
 
     /**
@@ -278,6 +382,7 @@ public class OpenIDVCITokenRequest extends TokenRequest {
         }
     }
 
+// Checkstyle: CyclomaticComplexity|MethodLength|ReturnCount OFF
     /**
      * Parses request from http request.
      * 
@@ -303,11 +408,6 @@ public class OpenIDVCITokenRequest extends TokenRequest {
                 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
             }
         }
-        final String grantType = MultivaluedMapUtils.getFirstValue(params, "grant_type");
-        final String preAuthorizedCode = MultivaluedMapUtils.getFirstValue(params, "pre-authorized_code");
-        final String code = MultivaluedMapUtils.getFirstValue(params, "code");
-        final String txCode = MultivaluedMapUtils.getFirstValue(params, "tx_code");
-        final String codeVerifier = MultivaluedMapUtils.getFirstValue(params, "code_verifier");
         final List<OpenIDVCIAuthorizationDetail> authorizationDetails;
         try {
             authorizationDetails = OpenIDVCIAuthorizationDetail.parse(httpRequest);
@@ -315,6 +415,16 @@ public class OpenIDVCITokenRequest extends TokenRequest {
             throw new ParseException(e.getMessage(),
                     OAuth2Error.INVALID_REQUEST.appendDescription(": " + e.getMessage()));
         }
+        final String grantType = MultivaluedMapUtils.getFirstValue(params, "grant_type");
+        if (!GRANT_TYPE_VALUE_PRE_AUTH.equals(grantType) && !GRANT_TYPE_VALUE_CODE.equals(grantType)) {
+            return wrap(TokenRequest.parse(httpRequest), authorizationDetails, httpRequest);
+        }
+        final Scope scope = Scope.parse(MultivaluedMapUtils.getFirstValue(params, "scope"));
+        final List<URI> resources = parseResources(params);
+        final String preAuthorizedCode = MultivaluedMapUtils.getFirstValue(params, "pre-authorized_code");
+        final String code = MultivaluedMapUtils.getFirstValue(params, "code");
+        final String txCode = MultivaluedMapUtils.getFirstValue(params, "tx_code");
+        final String codeVerifier = MultivaluedMapUtils.getFirstValue(params, "code_verifier");
         final URI uri;
         try {
             uri = httpRequest.getURL().toURI();
@@ -323,7 +433,7 @@ public class OpenIDVCITokenRequest extends TokenRequest {
         }
         if (clientAuth != null) {
             return new OpenIDVCITokenRequest(uri, clientAuth, grantType, preAuthorizedCode, code, txCode, codeVerifier,
-                    authorizationDetails);
+                    scope, resources, authorizationDetails);
         }
         String clientIDString = MultivaluedMapUtils.getFirstValue(params, "client_id");
         if (StringUtils.isBlank(clientIDString)) {
@@ -331,10 +441,69 @@ public class OpenIDVCITokenRequest extends TokenRequest {
         }
         if (StringUtils.isBlank(clientIDString)) {
             return new OpenIDVCITokenRequest(uri, (ClientID) null, grantType, preAuthorizedCode, code, txCode,
-                    codeVerifier, authorizationDetails);
+                    codeVerifier, scope, resources, authorizationDetails);
         }
         return new OpenIDVCITokenRequest(uri, new ClientID(clientIDString), grantType, preAuthorizedCode, code, txCode,
-                codeVerifier, authorizationDetails);
+                codeVerifier, scope, resources, authorizationDetails);
+    }
+// Checkstyle: CyclomaticComplexity|MethodLength|ReturnCount ON
+
+    /**
+     * Wrap a token request of a grant other than the authorization code and the pre-authorized code.
+     * 
+     * @param request request to wrap
+     * @param authorizationDetails authorization details of the request
+     * @param httpRequest the parsed http request
+     * @return wrapped request
+     */
+    @Nonnull
+    private static OpenIDVCITokenRequest wrap(@Nonnull final TokenRequest request,
+            @Nullable final List<OpenIDVCIAuthorizationDetail> authorizationDetails,
+            @Nonnull final HTTPRequest httpRequest) {
+        final ClientAuthentication clientAuth = request.getClientAuthentication();
+        if (clientAuth != null) {
+            return new OpenIDVCITokenRequest(request.getEndpointURI(), clientAuth, request.getAuthorizationGrant(),
+                    request.getScope(), request.getResources(), request.getCustomParameters(), authorizationDetails);
+        }
+        ClientID clientID = request.getClientID();
+        if (clientID == null) {
+            final String attested = attestedClientID(httpRequest);
+            clientID = StringUtils.isBlank(attested) ? null : new ClientID(attested);
+        }
+        return new OpenIDVCITokenRequest(request.getEndpointURI(), clientID,
+                request.getAuthorizationGrant(), request.getScope(), request.getResources(), request.getExistingGrant(),
+                request.getCustomParameters(), authorizationDetails);
+    }
+
+    /**
+     * Parse the 'resource' parameters of a request.
+     * 
+     * @param params parameters of the request
+     * @return resource URIs, or null if there are none
+     * @throws ParseException if a resource is not an absolute URI without a fragment
+     */
+    @Nullable
+    private static List<URI> parseResources(@Nonnull final Map<String, List<String>> params) throws ParseException {
+        final List<String> values = params.get("resource");
+        if (values == null || values.isEmpty()) {
+            return null;
+        }
+        final List<URI> resources = new ArrayList<>();
+        for (final String value : values) {
+            final URI resource;
+            try {
+                resource = new URI(value);
+            } catch (final URISyntaxException e) {
+                throw new ParseException("Illegal resource parameter: " + value,
+                        OAuth2Error.INVALID_TARGET.appendDescription(": " + value));
+            }
+            if (!resource.isAbsolute() || resource.getFragment() != null) {
+                throw new ParseException("Illegal resource parameter: " + value,
+                        OAuth2Error.INVALID_TARGET.appendDescription(": " + value));
+            }
+            resources.add(resource);
+        }
+        return resources;
     }
 
 }
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/config/impl/DefaultOpenIDVCITokenConfiguration.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/config/impl/DefaultOpenIDVCITokenConfiguration.java
index 4c5f834..a543ae3 100644
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/config/impl/DefaultOpenIDVCITokenConfiguration.java
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/config/impl/DefaultOpenIDVCITokenConfiguration.java
@@ -16,103 +16,23 @@
 
 package org.geant.shibboleth.plugin.openidvci.profile.config.impl;
 
-import java.util.Collection;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.BiFunction;
-import java.util.function.Function;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
+import java.util.List;
 
 import org.geant.shibboleth.plugin.openidvci.messaging.impl.OpenIDVCITokenRequest;
 import org.geant.shibboleth.plugin.openidvci.profile.config.OpenIDVCIConfiguration;
-import org.opensaml.profile.context.ProfileRequestContext;
 
 import com.nimbusds.oauth2.sdk.GrantType;
 
-import net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenConfiguration;
-import net.shibboleth.shared.annotation.constraint.NonnullElements;
-import net.shibboleth.shared.annotation.constraint.NotLive;
-import net.shibboleth.shared.annotation.constraint.Unmodifiable;
-import net.shibboleth.shared.collection.CollectionSupport;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.logic.FunctionSupport;
-import net.shibboleth.shared.primitive.StringSupport;
+import net.shibboleth.oidc.profile.oauth2.config.impl.DefaultOAuth2TokenConfiguration;
 
 /** Profile configuration for the Token endpoint. */
-public class DefaultOpenIDVCITokenConfiguration extends AbstractOpenIDVCIConfiguration
-        implements OAuth2TokenConfiguration {
-
-    /** Lookup function to supply the enabled grant types. */
-    @Nonnull
-    private Function<ProfileRequestContext, Set<String>> grantTypesLookupStrategy;
+public class DefaultOpenIDVCITokenConfiguration extends DefaultOAuth2TokenConfiguration {
 
     /** Constructor. */
     public DefaultOpenIDVCITokenConfiguration() {
         super(OpenIDVCIConfiguration.PROFILE_ID_TOKEN);
-        grantTypesLookupStrategy = FunctionSupport.constant(CollectionSupport.setOf(
-                GrantType.AUTHORIZATION_CODE.getValue(), OpenIDVCITokenRequest.GRANT_TYPE_VALUE_PRE_AUTH));
-        setRefreshTokensEnabled(false);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    @Nonnull
-    @NonnullElements
-    @NotLive
-    @Unmodifiable
-    public Set<String> getGrantTypes(@Nullable final ProfileRequestContext profileRequestContext) {
-        final Collection<String> types = grantTypesLookupStrategy.apply(profileRequestContext);
-
-        return types != null ? CollectionSupport.copyToSet(types) : CollectionSupport.emptySet();
-    }
-
-    /**
-     * Set the enabled grant types.
-     *
-     * @param types enabled grant types
-     */
-    public void setGrantTypes(@Nonnull @NonnullElements final Collection<String> types) {
-        Constraint.isNotNull(types, "Grant types cannot be null");
-
-        grantTypesLookupStrategy =
-                FunctionSupport.constant(Set.copyOf(StringSupport.normalizeStringCollection(types)));
-    }
-
-    /**
-     * Set the lookup strategy for the enabled grant types.
-     *
-     * @param strategy lookup strategy
-     */
-    public void setGrantTypesLookupStrategy(@Nonnull final Function<ProfileRequestContext, Set<String>> strategy) {
-        grantTypesLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public boolean isEnforceRefreshTokenRotation(@Nullable final ProfileRequestContext profileRequestContext) {
-        return false;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    @Nullable
-    public BiFunction<ProfileRequestContext, Map<String, Object>, Map<String, Object>>
-            getRefreshTokenClaimsSetManipulationStrategy(@Nullable final ProfileRequestContext profileRequestContext) {
-        return null;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public boolean isIssueIdTokenViaRefreshToken(@Nullable final ProfileRequestContext profileRequestContext) {
-        return false;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public boolean isLimitInitialAccessTokenToSelf(@Nullable final ProfileRequestContext profileRequestContext) {
-        return false;
+        setGrantTypes(List.of(GrantType.AUTHORIZATION_CODE.getValue(), GrantType.REFRESH_TOKEN.getValue(),
+                OpenIDVCITokenRequest.GRANT_TYPE_VALUE_PRE_AUTH));
     }
 
 }
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundTokenResponseMessage.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundTokenResponseMessage.java
index b7a1d18..ddd600b 100644
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundTokenResponseMessage.java
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundTokenResponseMessage.java
@@ -14,6 +14,11 @@
  * limitations under the License.
  */
 
+/*
+ * Derived from net.shibboleth.idp.plugin.oidc.op.profile.impl.FormOutboundTokenResponseMessage
+ * of the Shibboleth OpenID Connect Provider plugin, Apache License 2.0, modified by GÉANT.
+ */
+
 package org.geant.shibboleth.plugin.openidvci.profile.impl;
 
 import java.util.HashMap;
@@ -21,33 +26,52 @@ import java.util.List;
 import java.util.Map;
 
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
 import org.geant.shibboleth.plugin.openidvci.messaging.context.TokenContext;
 import org.geant.shibboleth.plugin.openidvci.messaging.impl.AbstractOpenIDVCITokenResponseAction;
-import org.geant.shibboleth.plugin.openidvci.messaging.impl.OpenIDVCITokenRequest;
 import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.jwt.JWT;
 import com.nimbusds.oauth2.sdk.AccessTokenResponse;
+import com.nimbusds.oauth2.sdk.TokenResponse;
+import com.nimbusds.oauth2.sdk.token.AccessToken;
 import com.nimbusds.oauth2.sdk.token.Tokens;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+import com.nimbusds.openid.connect.sdk.token.OIDCTokens;
 
-import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
+import net.shibboleth.shared.primitive.LoggerFactory;
 
-/** Action forming the outbound token response message. */
+/**
+ * Action that forms outbound message based on token request and response context. Formed message is set to
+ * {@link ProfileRequestContext#getOutboundMessageContext()}.
+ *
+ * <p>This is both OIDC and OAuth aware, based on the presence of an ID token in the context. The authorization
+ * details of {@link TokenContext#getResponseAuthorizationDetails()} are added as 'authorization_details'.</p>
+ */
 public class FormOutboundTokenResponseMessage extends AbstractOpenIDVCITokenResponseAction {
 
+    /** Name of the authorization details parameter. */
+    @Nonnull
+    private static final String AUTHORIZATION_DETAILS = "authorization_details";
+
     /** Class logger. */
     @Nonnull
-    private Logger log = LoggerFactory.getLogger(FormOutboundTokenResponseMessage.class);
+    private final Logger log = LoggerFactory.getLogger(FormOutboundTokenResponseMessage.class);
+
+    /** access token for response. */
+    @Nullable
+    private AccessToken accessToken;
 
-    /** Token context. */
-    @NonnullBeforeExec
-    private TokenContext tokenContext;
+    /** ID token for response. */
+    @Nullable
+    private JWT idToken;
 
     /** {@inheritDoc} */
     @Override
@@ -55,34 +79,39 @@ public class FormOutboundTokenResponseMessage extends AbstractOpenIDVCITokenResp
         if (!super.doPreExecute(profileRequestContext)) {
             return false;
         }
-
-        tokenContext = profileRequestContext.getInboundMessageContext().getSubcontext(TokenContext.class);
-        if (tokenContext == null) {
-            log.error("{} No token context", getLogPrefix());
+        final OIDCAuthenticationResponseContext oidcResponseContext = getOidcResponseContext();
+        assert oidcResponseContext != null;
+        accessToken = oidcResponseContext.getAccessToken();
+        if (accessToken == null) {
+            log.error("{} unable to provide required access token", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
             return false;
         }
+        idToken = oidcResponseContext.getProcessedToken();
         return true;
     }
 
     /** {@inheritDoc} */
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-        if (profileRequestContext.getInboundMessageContext() == null
-                || !(profileRequestContext.getInboundMessageContext().getMessage() instanceof OpenIDVCITokenRequest)) {
-            log.error("{} no inbound OpenIDVCITokenRequest available ", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
-            return;
-        }
-        Map<String, Object> params = null;
-        if (tokenContext.getResponseAuthorizationDetails() != null) {
-            params = new HashMap<>();
-            params.put("authorization_details", new ObjectMapper()
-                    .convertValue(tokenContext.getResponseAuthorizationDetails(), new TypeReference<List<Object>>() {
+        final OIDCAuthenticationResponseContext oidcResponseContext = getOidcResponseContext();
+        assert oidcResponseContext != null;
+        final Map<String, Object> params = new HashMap<>();
+        final TokenContext tokenContext = profileRequestContext.ensureInboundMessageContext()
+                .getSubcontext(TokenContext.class);
+        if (tokenContext != null && tokenContext.getResponseAuthorizationDetails() != null) {
+            params.put(AUTHORIZATION_DETAILS, new ObjectMapper().convertValue(
+                    tokenContext.getResponseAuthorizationDetails(), new TypeReference<List<Object>>() {
                     }));
         }
-        profileRequestContext.ensureOutboundMessageContext().setMessage(
-                new AccessTokenResponse(new Tokens(getOidcResponseContext().getAccessToken(), null), params));
-        return;
+        final TokenResponse resp;
+        if (idToken != null) {
+            resp = new OIDCTokenResponse(
+                    new OIDCTokens(idToken, accessToken, oidcResponseContext.getRefreshToken()), params);
+        } else {
+            resp = new AccessTokenResponse(new Tokens(accessToken, oidcResponseContext.getRefreshToken()), params);
+        }
+        profileRequestContext.ensureOutboundMessageContext().setMessage(resp);
     }
-}
\ No newline at end of file
+
+}
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/InitializePotentialCredentials.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/InitializePotentialCredentials.java
new file mode 100644
index 0000000..2389b72
--- /dev/null
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/InitializePotentialCredentials.java
@@ -0,0 +1,65 @@
+/*
+ * 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.profile.impl;
+
+import javax.annotation.Nonnull;
+
+import org.geant.shibboleth.plugin.openidvci.messaging.context.TokenContext;
+import org.opensaml.profile.action.AbstractProfileAction;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Action that sets an empty claims set to {@link TokenContext#setPotentialCredentials(ClaimsSet)}.
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_MSG_CTX}
+ */
+public class InitializePotentialCredentials extends AbstractProfileAction {
+
+    /** Class logger. */
+    @Nonnull
+    private final Logger log = LoggerFactory.getLogger(InitializePotentialCredentials.class);
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }
+        if (profileRequestContext.getInboundMessageContext() == null) {
+            log.error("{} No inbound message context", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            return false;
+        }
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        profileRequestContext.ensureInboundMessageContext().ensureSubcontext(TokenContext.class)
+                .setPotentialCredentials(new ClaimsSet());
+    }
+
+}
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/SetCredentialsAudienceToResponseContext.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/SetCredentialsAudienceToResponseContext.java
index 5e24716..53d5e1e 100644
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/SetCredentialsAudienceToResponseContext.java
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/SetCredentialsAudienceToResponseContext.java
@@ -16,39 +16,95 @@
 
 package org.geant.shibboleth.plugin.openidvci.profile.impl;
 
+import java.util.function.Function;
+
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
+import org.geant.shibboleth.plugin.openidvci.messaging.context.TokenContext;
 import org.geant.shibboleth.plugin.openidvci.messaging.impl.AbstractOpenIDVCITokenResponseAction;
+import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
 import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
+import net.shibboleth.idp.profile.IdPEventIds;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
 /**
- * Action that adds the audience of the Credential endpoint to {@link OIDCAuthenticationResponseContext}.
+ * Action that adds the Credential Issuer Identifier to the audience in {@link OIDCAuthenticationResponseContext}
+ * when {@link TokenContext#getPotentialCredentials()} is not empty.
  *
  * @event {@link EventIds#PROCEED_EVENT_ID}
  * @event {@link EventIds#INVALID_MSG_CTX}
+ * @event {@link IdPEventIds#INVALID_PROFILE_CONFIG}
  */
 public class SetCredentialsAudienceToResponseContext extends AbstractOpenIDVCITokenResponseAction {
 
-    /** Audience of the Credential endpoint. */
-    @Nonnull
-    public static final String AUDIENCE = "credentials";
-
     /** Class logger. */
     @Nonnull
     private Logger log = LoggerFactory.getLogger(SetCredentialsAudienceToResponseContext.class);
 
+    /** Strategy used to locate the Credential Issuer Identifier. */
+    @Nullable
+    private Function<ProfileRequestContext, String> audienceLookupStrategy;
+
+    /** Credential Issuer Identifier of the request. */
+    @Nullable
+    private String audience;
+
+    /**
+     * Set the strategy used to locate the Credential Issuer Identifier.
+     *
+     * @param strategy lookup strategy
+     */
+    public void setAudienceLookupStrategy(@Nonnull final Function<ProfileRequestContext, String> strategy) {
+        checkSetterPreconditions();
+        audienceLookupStrategy = Constraint.isNotNull(strategy, "Audience lookup strategy cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        if (audienceLookupStrategy == null) {
+            throw new ComponentInitializationException("Audience lookup strategy cannot be null");
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }
+        final TokenContext tokenContext = profileRequestContext.ensureInboundMessageContext()
+                .getSubcontext(TokenContext.class);
+        if (tokenContext == null || tokenContext.getPotentialCredentials() == null
+                || tokenContext.getPotentialCredentials().toJSONObject().isEmpty()) {
+            log.debug("{} No potential credentials, nothing to do", getLogPrefix());
+            return false;
+        }
+        assert audienceLookupStrategy != null;
+        audience = audienceLookupStrategy.apply(profileRequestContext);
+        if (audience == null) {
+            log.error("{} No Credential Issuer Identifier", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
+            return false;
+        }
+        return true;
+    }
+
     /** {@inheritDoc} */
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
         final OIDCAuthenticationResponseContext oidcResponseContext = getOidcResponseContext();
         assert oidcResponseContext != null;
-        if (!oidcResponseContext.getAudience().contains(AUDIENCE)) {
-            oidcResponseContext.getAudience().add(AUDIENCE);
+        if (!oidcResponseContext.getAudience().contains(audience)) {
+            oidcResponseContext.getAudience().add(audience);
         }
         log.debug("{} Access token audience {}", getLogPrefix(), oidcResponseContext.getAudience());
     }
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/UnwrapGrant.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/UnwrapPreAuthorizedCode.java
similarity index 70%
rename from openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/UnwrapGrant.java
rename to openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/UnwrapPreAuthorizedCode.java
index 00476d4..4eff8c4 100644
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/UnwrapGrant.java
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/UnwrapPreAuthorizedCode.java
@@ -42,14 +42,10 @@ import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
 import org.slf4j.Logger;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
-import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
 import com.nimbusds.oauth2.sdk.AuthorizationGrant;
-import com.nimbusds.oauth2.sdk.GrantType;
 import com.nimbusds.oauth2.sdk.id.ClientID;
 import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
 
-import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
-import net.shibboleth.idp.plugin.oidc.op.token.support.AuthorizeCodeClaimsSet;
 import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
 import net.shibboleth.oidc.profile.core.OidcEventIds;
 import net.shibboleth.shared.annotation.ParameterName;
@@ -62,37 +58,23 @@ import net.shibboleth.shared.security.DataSealer;
 import net.shibboleth.shared.security.DataSealerException;
 
 /**
- * Action that unwraps an authorization grant or pre-authorized grant.
+ * Action that unwraps a pre-authorized grant.
  * 
  * <p>
- * Operation is valid if it is successfully unwrapped, parsed as a code or
- * pre-authorized code, is unexpired and was issued to the expected client.
- * 
- * Pre-authorized grant may also be located from storage.
- * </p>
- * 
- * <p>
- * The claims set from the authorization grant grant is stored to response
- * context via
- * {@link OIDCAuthenticationResponseContext#setAuthorizationGrantClaimsSet(TokenClaimsSet)}.
- * </p>
- * 
- * <p>
- * The credential offer object from pre-authorised grant is stored to response
- * context via {@link TokenContext#setCredentialOfferObject}.
+ * Operation is valid if it is successfully unwrapped, parsed as a pre-authorized
+ * code and is unexpired. Pre-authorized grant may also be located from storage.
  * </p>
  * 
  * <p>
- * The potential credentials from pre-authorised grant is stored to response
- * context via {@link TokenContext#setPotentialCredentials}.
+ * The credential offer object and the potential credentials of the grant are
+ * stored to {@link TokenContext}.
  * </p>
- * 
  */
-public class UnwrapGrant extends AbstractOpenIDVCITokenResponseAction {
+public class UnwrapPreAuthorizedCode extends AbstractOpenIDVCITokenResponseAction {
 
     /** Class logger. */
     @Nonnull
-    private Logger log = LoggerFactory.getLogger(UnwrapGrant.class);
+    private Logger log = LoggerFactory.getLogger(UnwrapPreAuthorizedCode.class);
 
     /** Data sealer for unwrapping authorization code. */
     @Nonnull
@@ -111,7 +93,7 @@ public class UnwrapGrant extends AbstractOpenIDVCITokenResponseAction {
      * 
      * @param sealer sealer to decrypt/hmac authorize code.
      */
-    public UnwrapGrant(@Nonnull @ParameterName(name = "sealer") final DataSealer sealer) {
+    public UnwrapPreAuthorizedCode(@Nonnull @ParameterName(name = "sealer") final DataSealer sealer) {
         dataSealer = Constraint.isNotNull(sealer, "DataSealer cannot be null");
         final APIRequestClientIDLookupFunction lookup = new APIRequestClientIDLookupFunction();
         lookup.setDataSealer(sealer);
@@ -154,40 +136,7 @@ public class UnwrapGrant extends AbstractOpenIDVCITokenResponseAction {
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
         final AuthorizationGrant grant = getTokenRequest().getAuthorizationGrant();
         log.debug("{} Unwrapping grant type: {}", getLogPrefix(), grant.getType());
-        if (GrantType.AUTHORIZATION_CODE.equals(grant.getType())) {
-            TokenClaimsSet tokenClaimsSet = null;
-            final AuthorizationCodeGrant codeGrant = (AuthorizationCodeGrant) grant;
-            if (codeGrant.getAuthorizationCode() != null && codeGrant.getAuthorizationCode().getValue() != null) {
-                try {
-                    final String codeValue = codeGrant.getAuthorizationCode().getValue();
-                    assert codeValue != null;
-                    final AuthorizeCodeClaimsSet authzCodeClaimsSet = AuthorizeCodeClaimsSet.parse(codeValue,
-                            dataSealer);
-                    assert authzCodeClaimsSet != null;
-                    final String jti = authzCodeClaimsSet.getID();
-                    if (jti == null) {
-                        log.warn("{} Invalid contents in the authz code grant: no JTI", getLogPrefix());
-                        ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
-                        return;
-                    }
-                    log.debug("{} Authz code unwrapped {}", getLogPrefix(), authzCodeClaimsSet.serialize());
-                    tokenClaimsSet = authzCodeClaimsSet;
-
-                } catch (final DataSealerException | ParseException e) {
-                    log.warn("{} Unwrapping authz code failed: {}", getLogPrefix(), e.getMessage());
-                    ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
-                    return;
-                }
-            }
-            validateTokenClaimsSet(profileRequestContext, tokenClaimsSet, true);
-            final OIDCAuthenticationResponseContext oidcResponseContext = getOidcResponseContext();
-            assert oidcResponseContext != null;
-            oidcResponseContext.setAuthorizationGrantClaimsSet(tokenClaimsSet);
-            final TokenContext tokenContext = profileRequestContext.getInboundMessageContext()
-                    .ensureSubcontext(TokenContext.class);
-            tokenContext.setPotentialCredentials(tokenClaimsSet.getUserinfoDeliveryClaims());
-            return;
-        } else if (PreAuthorizedCodeGrant.GRANT_TYPE.equals(grant.getType())) {
+        if (PreAuthorizedCodeGrant.GRANT_TYPE.equals(grant.getType())) {
             final String code = getOpenIDVCITokenRequest().getPreAuthorizedCode();
             log.info("{} Unwrapping pre-authorization code", getLogPrefix());
             final Exception e;
@@ -201,7 +150,7 @@ public class UnwrapGrant extends AbstractOpenIDVCITokenResponseAction {
                     return;
                 }
                 log.debug("{} pre-authorized grant unwrapped {}", getLogPrefix(), credentialOffer.serialize());
-                validateTokenClaimsSet(profileRequestContext, credentialOffer, false);
+                validateTokenClaimsSet(profileRequestContext, credentialOffer);
                 final TokenContext tokenContext = profileRequestContext.getInboundMessageContext()
                         .ensureSubcontext(TokenContext.class);
                 tokenContext.setCredentialOfferObject(CredentialOfferObject.parse(credentialOffer.getSubject()));
@@ -252,10 +201,9 @@ public class UnwrapGrant extends AbstractOpenIDVCITokenResponseAction {
      *
      * @param profileRequestContext profile request context
      * @param tokenClaimsSet        claims of the unwrapped grant
-     * @param clientMatch           whether the grant was issued to this client
      */
     private void validateTokenClaimsSet(@Nonnull final ProfileRequestContext profileRequestContext,
-            final TokenClaimsSet tokenClaimsSet, final boolean clientMatch) {
+            final TokenClaimsSet tokenClaimsSet) {
         if (tokenClaimsSet == null) {
             log.warn("{} Grant type not supported", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
@@ -264,20 +212,6 @@ public class UnwrapGrant extends AbstractOpenIDVCITokenResponseAction {
         if (!tokenClaimsSet.isTimeValid()) {
             log.warn("{} Token is expired or not yet valid", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
-            return;
-        }
-        if (!clientMatch) {
-            // pre-authorized grants are for any.
-            return;
-        }
-        final ClientID clientId = tokenClaimsSet.getClientID();
-        assert clientId != null;
-        final ClientID requestClientId = clientIDLookupStrategy.apply(profileRequestContext);
-        if (!clientId.equals(requestClientId)) {
-            log.warn("{} Token issued to client {}, invalid for {}", getLogPrefix(), clientId.getValue(),
-                    requestClientId);
-            ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
-            return;
         }
     }
 
diff --git a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/did/document/document-beans.xml b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/did/document/document-beans.xml
index 2fb61f1..df29dce 100644
--- a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/did/document/document-beans.xml
+++ b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/did/document/document-beans.xml
@@ -19,12 +19,12 @@
   <bean id="InitializeUnverifiedRelyingPartyContext"
         class="net.shibboleth.idp.plugin.oidc.op.profile.impl.InitializeUnverifiedRelyingPartyContext" scope="prototype" />
 
-  <bean id="FormOutboundMessage"
+  <bean id="OpenIDVCIFormOutboundMessage"
         class="org.geant.shibboleth.plugin.openidvci.profile.impl.FormOutboundDidDocumentResponseMessage"
         scope="prototype"
         p:deploymentIssuer="#{getObject('shibboleth.oidc.issuer')}" />
 
-  <bean id="BuildErrorResponseFromEvent" class="net.shibboleth.oidc.profile.impl.BuildJSONErrorResponseFromEvent"
+  <bean id="OpenIDVCIBuildErrorResponseFromEvent" class="net.shibboleth.oidc.profile.impl.BuildJSONErrorResponseFromEvent"
         scope="prototype" p:defaultStatusCode="500" p:defaultCode="server_error"
         p:httpServletResponseSupplier-ref="shibboleth.HttpServletResponseSupplier">
     <property name="eventContextLookupStrategy">
diff --git a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth/authorization-server/authorization-server-beans.xml b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth/authorization-server/authorization-server-beans.xml
index 9462320..88c9752 100644
--- a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth/authorization-server/authorization-server-beans.xml
+++ b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth/authorization-server/authorization-server-beans.xml
@@ -22,14 +22,14 @@
   <bean id="InitializeUnverifiedRelyingPartyContext"
         class="net.shibboleth.idp.plugin.oidc.op.profile.impl.InitializeUnverifiedRelyingPartyContext" scope="prototype" />
 
-  <bean id="FormOutboundMessage"
+  <bean id="OpenIDVCIFormOutboundMessage"
         class="org.geant.shibboleth.plugin.openidvci.profile.impl.FormOutboundMetadataDocumentResponseMessage"
         scope="prototype"
         p:metadataResolver-ref="#{'%{openidvci.authorizationServer.resolver:openidvci.DefaultAuthorizationServerMetadataResolver}'.trim()}"
         p:httpServletResponseSupplier-ref="shibboleth.HttpServletResponseSupplier"
         p:cacheControl="%{openidvci.authorizationServer.cacheControl:max-age=3600}" />
 
-  <bean id="BuildErrorResponseFromEvent" class="net.shibboleth.oidc.profile.impl.BuildJSONErrorResponseFromEvent"
+  <bean id="OpenIDVCIBuildErrorResponseFromEvent" class="net.shibboleth.oidc.profile.impl.BuildJSONErrorResponseFromEvent"
         scope="prototype" p:defaultStatusCode="500" p:defaultCode="server_error"
         p:httpServletResponseSupplier-ref="shibboleth.HttpServletResponseSupplier">
     <property name="eventContextLookupStrategy">
diff --git a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth/jwt-vc-issuer/jwt-vc-issuer-beans.xml b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth/jwt-vc-issuer/jwt-vc-issuer-beans.xml
index 1499d35..34ed1ae 100644
--- a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth/jwt-vc-issuer/jwt-vc-issuer-beans.xml
+++ b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth/jwt-vc-issuer/jwt-vc-issuer-beans.xml
@@ -22,14 +22,14 @@
   <bean id="InitializeUnverifiedRelyingPartyContext"
         class="net.shibboleth.idp.plugin.oidc.op.profile.impl.InitializeUnverifiedRelyingPartyContext" scope="prototype" />
 
-  <bean id="FormOutboundMessage"
+  <bean id="OpenIDVCIFormOutboundMessage"
         class="org.geant.shibboleth.plugin.openidvci.profile.impl.FormOutboundMetadataDocumentResponseMessage"
         scope="prototype"
         p:metadataResolver-ref="#{'%{openidvci.jwtVcIssuer.resolver:openidvci.DefaultJWTVCIssuerMetadataResolver}'.trim()}"
         p:httpServletResponseSupplier-ref="shibboleth.HttpServletResponseSupplier"
         p:cacheControl="%{openidvci.jwtVcIssuer.cacheControl:max-age=3600}" />
 
-  <bean id="BuildErrorResponseFromEvent" class="net.shibboleth.oidc.profile.impl.BuildJSONErrorResponseFromEvent"
+  <bean id="OpenIDVCIBuildErrorResponseFromEvent" class="net.shibboleth.oidc.profile.impl.BuildJSONErrorResponseFromEvent"
         scope="prototype" p:defaultStatusCode="500" p:defaultCode="server_error"
         p:httpServletResponseSupplier-ref="shibboleth.HttpServletResponseSupplier">
     <property name="eventContextLookupStrategy">
diff --git a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth/status-list/assignments/assignments-beans.xml b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth/status-list/assignments/assignments-beans.xml
index 6fa0fc2..29afba5 100644
--- a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth/status-list/assignments/assignments-beans.xml
+++ b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth/status-list/assignments/assignments-beans.xml
@@ -16,7 +16,7 @@
   <bean id="openidvci.loggingId" class="java.lang.String"
         c:_0="%{openidvci.logging.status-list.assignments:OAuth.StatusList.Assignments}"/>
 
-  <bean id="DecodeMessage" class="org.opensaml.profile.action.impl.DecodeMessage" scope="prototype">
+  <bean id="OpenIDVCIDecodeMessage" class="org.opensaml.profile.action.impl.DecodeMessage" scope="prototype">
     <constructor-arg>
       <bean class="org.geant.shibboleth.plugin.openidvci.statuslist.decoding.impl.StatusListAssignmentsRequestDecoder"
             scope="prototype" p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier" />
@@ -37,12 +37,12 @@
         p:retention="%{openidvci.status-list.assignment.retention:P7D}"
         p:storage-ref="#{'%{openidvci.status-list.assignment.StorageService:shibboleth.StorageService}'.trim()}" />
 
-  <bean id="FormOutboundMessage"
+  <bean id="OpenIDVCIFormOutboundMessage"
         class="org.geant.shibboleth.plugin.openidvci.statuslist.profile.impl.FormOutboundStatusListAssignmentsResponseMessage"
         scope="prototype"
         p:statusListAssignmentStore-ref="openidvci.status-list.AssignmentStore" />
 
-  <bean id="BuildErrorResponseFromEvent" class="net.shibboleth.oidc.profile.impl.BuildJSONErrorResponseFromEvent"
+  <bean id="OpenIDVCIBuildErrorResponseFromEvent" class="net.shibboleth.oidc.profile.impl.BuildJSONErrorResponseFromEvent"
         scope="prototype" p:defaultStatusCode="400" p:defaultCode="invalid_request"
         p:httpServletResponseSupplier-ref="shibboleth.HttpServletResponseSupplier">
     <property name="eventContextLookupStrategy">
diff --git a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth/status-list/list/list-beans.xml b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth/status-list/list/list-beans.xml
index 4813571..5927f0b 100644
--- a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth/status-list/list/list-beans.xml
+++ b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth/status-list/list/list-beans.xml
@@ -66,7 +66,7 @@
     </property>
   </bean>
 
-  <bean id="FormOutboundMessage"
+  <bean id="OpenIDVCIFormOutboundMessage"
         class="org.geant.shibboleth.plugin.openidvci.statuslist.profile.impl.FormOutboundStatusListResponseMessage"
         scope="prototype" />
 
@@ -92,7 +92,7 @@
         scope="prototype" init-method=""
         p:httpServletResponseSupplier-ref="shibboleth.HttpServletResponseSupplier" />
 
-  <bean id="BuildErrorResponseFromEvent" class="net.shibboleth.oidc.profile.impl.BuildJSONErrorResponseFromEvent" 
+  <bean id="OpenIDVCIBuildErrorResponseFromEvent" class="net.shibboleth.oidc.profile.impl.BuildJSONErrorResponseFromEvent" 
         scope="prototype" p:defaultStatusCode="400" p:defaultCode="invalid_request"
         p:httpServletResponseSupplier-ref="shibboleth.HttpServletResponseSupplier">
     <property name="eventContextLookupStrategy">
diff --git a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth/status-list/revoke/revoke-beans.xml b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth/status-list/revoke/revoke-beans.xml
index d9cfcc0..a845a06 100644
--- a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth/status-list/revoke/revoke-beans.xml
+++ b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth/status-list/revoke/revoke-beans.xml
@@ -16,7 +16,7 @@
   <bean id="openidvci.loggingId" class="java.lang.String"
         c:_0="%{openidvci.logging.status-list.revoke:OAuth.StatusList.Revoke}"/>
 
-  <bean id="DecodeMessage" class="org.opensaml.profile.action.impl.DecodeMessage" scope="prototype">
+  <bean id="OpenIDVCIDecodeMessage" class="org.opensaml.profile.action.impl.DecodeMessage" scope="prototype">
     <constructor-arg>
       <bean class="org.geant.shibboleth.plugin.openidvci.statuslist.decoding.impl.StatusListRevokeRequestDecoder"
             scope="prototype" p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier" />
@@ -41,11 +41,11 @@
         scope="prototype"
         p:statusListStore-ref="openidvci.status-list.Store" />
 
-  <bean id="FormOutboundMessage"
+  <bean id="OpenIDVCIFormOutboundMessage"
         class="org.geant.shibboleth.plugin.openidvci.statuslist.profile.impl.FormOutboundStatusListRevokeResponseMessage"
         scope="prototype" />
 
-  <bean id="BuildErrorResponseFromEvent" class="net.shibboleth.oidc.profile.impl.BuildJSONErrorResponseFromEvent"
+  <bean id="OpenIDVCIBuildErrorResponseFromEvent" class="net.shibboleth.oidc.profile.impl.BuildJSONErrorResponseFromEvent"
         scope="prototype" p:defaultStatusCode="400" p:defaultCode="invalid_request"
         p:httpServletResponseSupplier-ref="shibboleth.HttpServletResponseSupplier">
     <property name="eventContextLookupStrategy">
diff --git a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/abstract-api/abstract-api-flow.xml b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/abstract-api/abstract-api-flow.xml
index 30fa6eb..8d0410e 100644
--- a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/abstract-api/abstract-api-flow.xml
+++ b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/abstract-api/abstract-api-flow.xml
@@ -9,7 +9,7 @@
     <on-entry>
         <evaluate expression="flowRequestContext.getFlowScope().get('transitionAfterDecode') != null ? flowRequestContext.getFlowScope().get('transitionAfterDecode') : 'DoMetadataLookup'" result="flowScope.transitionTarget"/>
     </on-entry>  
-    <evaluate expression="DecodeMessage"/>
+    <evaluate expression="OpenIDVCIDecodeMessage"/>
     <evaluate expression="PostDecodePopulateAuditContext"/>
     <evaluate expression="'proceed'" />
     <transition on="proceed" to="#{transitionTarget}" />
@@ -53,7 +53,7 @@
   </subflow-state>
 
   <action-state id="BuildResponseMessage">
-    <evaluate expression="FormOutboundMessage"/>
+    <evaluate expression="OpenIDVCIFormOutboundMessage"/>
     <evaluate expression="'proceed'"/>
     <transition on="proceed" to="PopulateOutboundInterceptContext"/>
   </action-state>
@@ -63,7 +63,7 @@
       <evaluate expression="opensamlProfileRequestContext.ensureSubcontext(T(net.shibboleth.idp.profile.context.SpringRequestContext)).setRequestContext(flowRequestContext)"/>
       <evaluate expression="LogEvent"/>
     </on-entry>
-    <evaluate expression="BuildErrorResponseFromEvent"/>
+    <evaluate expression="OpenIDVCIBuildErrorResponseFromEvent"/>
     <evaluate expression="'proceed'"/>
     <transition on="proceed" to="PopulateOutboundInterceptContext"/>
   </action-state>
diff --git a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/credential-offer/credential-offer-beans.xml b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/credential-offer/credential-offer-beans.xml
index bb6f225..cf060cf 100644
--- a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/credential-offer/credential-offer-beans.xml
+++ b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/credential-offer/credential-offer-beans.xml
@@ -15,7 +15,7 @@
  <bean id="openidvci.loggingId" class="java.lang.String"
        c:_0="%{openidvci.logging.credential-offer:OpenID.VCI.CredentialOffer}"/>
  
- <bean id="DecodeMessage" class="org.opensaml.profile.action.impl.DecodeMessage" scope="prototype">
+ <bean id="OpenIDVCIDecodeMessage" class="org.opensaml.profile.action.impl.DecodeMessage" scope="prototype">
   <constructor-arg>
    <bean class="org.geant.shibboleth.plugin.openidvci.decoding.impl.CredentialOfferRequestDecoder"
         scope="prototype" p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"/>
@@ -33,11 +33,11 @@
     scope="prototype" p:credentialOfferCache-ref="openidvci.CredentialOfferCache"
     c:sealer-ref="#{'%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}"/>
        
- <bean id="FormOutboundMessage" class="org.geant.shibboleth.plugin.openidvci.profile.impl.FormOutboundCredentialOfferResponseMessage"
+ <bean id="OpenIDVCIFormOutboundMessage" class="org.geant.shibboleth.plugin.openidvci.profile.impl.FormOutboundCredentialOfferResponseMessage"
     p:issuerLookupStrategy-ref="shibboleth.ResponderIdLookup.Simple"
     p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier" scope="prototype"/>
     
- <bean id="BuildErrorResponseFromEvent"
+ <bean id="OpenIDVCIBuildErrorResponseFromEvent"
         class="net.shibboleth.oidc.profile.impl.BuildJSONErrorResponseFromEvent" scope="prototype"
         p:defaultStatusCode="400" p:defaultCode="invalid_request"
         p:httpServletResponseSupplier-ref="shibboleth.HttpServletResponseSupplier">
diff --git a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/credentials/credentials-beans.xml b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/credentials/credentials-beans.xml
index 96d494a..aeed6a9 100644
--- a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/credentials/credentials-beans.xml
+++ b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/credentials/credentials-beans.xml
@@ -15,7 +15,7 @@
   <bean id="openidvci.loggingId" class="java.lang.String"
         c:_0="%{openidvci.logging.credentials:OpenID.VCI.Credentials}"/>
   
-  <bean id="DecodeMessage" class="org.opensaml.profile.action.impl.DecodeMessage" scope="prototype">
+  <bean id="OpenIDVCIDecodeMessage" class="org.opensaml.profile.action.impl.DecodeMessage" scope="prototype">
     <constructor-arg>
       <bean class="org.geant.shibboleth.plugin.openidvci.decoding.impl.OpenIDVCICredentialsRequestDecoder" 
             scope="prototype" p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier" />
@@ -191,7 +191,7 @@
         </property>
   </bean>
           
-  <bean id="FormOutboundMessage" class="org.geant.shibboleth.plugin.openidvci.profile.impl.FormOutboundCredentialsResponseMessage"
+  <bean id="OpenIDVCIFormOutboundMessage" class="org.geant.shibboleth.plugin.openidvci.profile.impl.FormOutboundCredentialsResponseMessage"
         scope="prototype"  />
 
   <!-- Error vocabulary of the Credential Endpoint, OpenID4VCI 1.0 section 8.4. -->
@@ -229,7 +229,7 @@
     </property>
   </bean>
 
-  <bean id="BuildErrorResponseFromEvent"
+  <bean id="OpenIDVCIBuildErrorResponseFromEvent"
         class="org.geant.shibboleth.plugin.openidvci.profile.impl.BuildCredentialErrorResponseFromEvent" scope="prototype"
         p:httpServletResponseSupplier-ref="shibboleth.HttpServletResponseSupplier"
         p:mappedErrors="#{getObject('openidvci.credentials.MappedErrors')}"
diff --git a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/issuer-metadata/issuer-metadata-beans.xml b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/issuer-metadata/issuer-metadata-beans.xml
index d95603b..bc3d407 100644
--- a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/issuer-metadata/issuer-metadata-beans.xml
+++ b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/issuer-metadata/issuer-metadata-beans.xml
@@ -38,14 +38,14 @@
     </property>
   </bean>
 
-  <bean id="FormOutboundMessage"
+  <bean id="OpenIDVCIFormOutboundMessage"
         class="org.geant.shibboleth.plugin.openidvci.profile.impl.FormOutboundIssuerMetadataResponseMessage"
         scope="prototype"
         p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"
         p:metadataResolver-ref="#{'%{openidvci.issuerMetadata.resolver:openidvci.DefaultCredentialIssuerMetadataResolver}'.trim()}"
         p:signedMetadataLifetime="#{'%{openidvci.issuerMetadata.signedLifetime:}'.trim().isEmpty() ? null : T(java.time.Duration).parse('%{openidvci.issuerMetadata.signedLifetime:}'.trim())}" />
 
-  <bean id="BuildErrorResponseFromEvent" class="net.shibboleth.oidc.profile.impl.BuildJSONErrorResponseFromEvent"
+  <bean id="OpenIDVCIBuildErrorResponseFromEvent" class="net.shibboleth.oidc.profile.impl.BuildJSONErrorResponseFromEvent"
         scope="prototype" p:defaultStatusCode="500" p:defaultCode="server_error"
         p:httpServletResponseSupplier-ref="shibboleth.HttpServletResponseSupplier">
     <property name="eventContextLookupStrategy">
diff --git a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/nonce/nonce-beans.xml b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/nonce/nonce-beans.xml
index f9a9562..5dbf64e 100644
--- a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/nonce/nonce-beans.xml
+++ b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/nonce/nonce-beans.xml
@@ -21,10 +21,10 @@
   <bean id="InitializeUnverifiedRelyingPartyContext"
         class="net.shibboleth.idp.plugin.oidc.op.profile.impl.InitializeUnverifiedRelyingPartyContext" scope="prototype" />
     
-  <bean id="FormOutboundMessage" class="org.geant.shibboleth.plugin.openidvci.profile.impl.FormOutboundNonceResponseMessage"
+  <bean id="OpenIDVCIFormOutboundMessage" class="org.geant.shibboleth.plugin.openidvci.profile.impl.FormOutboundNonceResponseMessage"
         scope="prototype" p:httpServletResponseSupplier-ref="shibboleth.HttpServletResponseSupplier" />
  
-  <bean id="BuildErrorResponseFromEvent" class="net.shibboleth.oidc.profile.impl.BuildJSONErrorResponseFromEvent" 
+  <bean id="OpenIDVCIBuildErrorResponseFromEvent" class="net.shibboleth.oidc.profile.impl.BuildJSONErrorResponseFromEvent" 
         scope="prototype" p:defaultStatusCode="500" p:defaultCode="server_error"
         p:httpServletResponseSupplier-ref="shibboleth.HttpServletResponseSupplier">
     <property name="eventContextLookupStrategy">
diff --git a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/token/token-beans.xml b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/token/token-beans.xml
index f597646..36bce61 100644
--- a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/token/token-beans.xml
+++ b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/token/token-beans.xml
@@ -1,7 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!--
-    Derived from META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
-    of the Shibboleth OpenID Connect Provider plugin, Apache License 2.0, modified by GÉANT.
+    Beans of the token flow. The beans of META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
+    of the Shibboleth OpenID Connect Provider plugin are imported; this file has the beans of this
+    plugin. Bean definitions derived from that file, Apache License 2.0, modified by GÉANT.
 -->
 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:c="http://www.springframework.org/schema/c"
     xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
@@ -11,393 +12,98 @@
                         http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
     default-init-method="initialize" default-destroy-method="destroy">
 
-    <!-- REPLACED shibboleth.oidc.profileId, legacyProfileId and loggingId. -->
+    <import resource="classpath:/META-INF/net/shibboleth/idp/flows/oidc/abstract-api/oidc-abstract-api-beans.xml" />
+    <import resource="classpath:/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml" />
+
+    <!-- Beans named by openid/vci/abstract-api. -->
+
     <bean id="openidvci.profileId" class="java.lang.String" c:_0="http://geant.org/ns/profiles/openid/vci/token"/>
 
     <bean id="openidvci.loggingId" class="java.lang.String"
         c:_0="%{openidvci.logging.token:OpenID.VCI.Token}"/>
 
-    <bean id="shibboleth.oidc.MetadataEnforcedDPoP" parent="shibboleth.Conditions.Expression"
-        c:expression="#input.ensureInboundMessageContext().getSubcontext(T(net.shibboleth.oidc.metadata.context.OIDCMetadataContext)) != null and #input.ensureInboundMessageContext().getSubcontext(T(net.shibboleth.oidc.metadata.context.OIDCMetadataContext)).getClientInformation() != null and #input.ensureInboundMessageContext().getSubcontext(T(net.shibboleth.oidc.metadata.context.OIDCMetadataContext)).getClientInformation().getMetadata().toJSONObject().get('dpop_bound_access_tokens') !=  [...]
-
-    <util:constant id="shibboleth.metrics.ProfileCounter"
-        static-field="net.shibboleth.oidc.profile.oauth2.config.impl.DefaultOAuth2TokenConfiguration.PROFILE_COUNTER" />
-
-    <bean id="DecodeMessage" class="org.opensaml.profile.action.impl.DecodeMessage" scope="prototype">
+    <!-- In place of DecodeMessage, with OpenIDVCITokenRequestDecoder in place of OIDCTokenRequestDecoder. -->
+    <bean id="OpenIDVCIDecodeMessage" class="org.opensaml.profile.action.impl.DecodeMessage" scope="prototype">
         <constructor-arg>
-            <!-- REPLACED OIDCTokenRequestDecoder. -->
             <bean class="org.geant.shibboleth.plugin.openidvci.decoding.impl.OpenIDVCITokenRequestDecoder"
-                scope="prototype" p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier" />
+                scope="prototype" p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"
+                p:removeIpAddressFromEndpointUri="%{idp.oidc.logging.removeIpAddressFromProtocolMessage:false}"
+                p:customRequestParser="#{getObject('%{openidvci.requestParser.TokenRequest:}'.trim())}" />
         </constructor-arg>
     </bean>
 
-    <!-- REPLACED TokenRequestClientIDLookupFunction. -->
-    <bean id="shibboleth.ClientIDLookupStrategy" class="org.geant.shibboleth.plugin.openidvci.profile.context.navigate.APIRequestClientIDLookupFunction"
-        p:credentialOfferCache-ref="openidvci.CredentialOfferCache"
-        p:dataSealer-ref="#{'%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}" />
-
-    <bean id="openidvci.ProfileRequestClientIDLookupStrategy" parent="shibboleth.Functions.Compose"
-        c:g-ref="shibboleth.ClientIDLookupStrategy"
-        c:f-ref="shibboleth.MessageContextLookup.Inbound" />
-
-    <!-- REPLACED UnwrapGrant. -->
-    <bean id="OpenIDVCIUnwrapGrant" class="org.geant.shibboleth.plugin.openidvci.profile.impl.UnwrapGrant" scope="prototype"
-        c:sealer-ref="#{'%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}"
-        p:clientIDLookupStrategy-ref="openidvci.ProfileRequestClientIDLookupStrategy"
-        p:credentialOfferCache-ref="openidvci.CredentialOfferCache"/>
-
-    <bean id="ResolveAttributesForClientPredicate"
-        class="net.shibboleth.profile.config.logic.ResolveAttributesPredicate" />
+    <!-- In place of FormOutboundMessage. -->
+    <bean id="OpenIDVCIFormOutboundMessage"
+        class="org.geant.shibboleth.plugin.openidvci.profile.impl.FormOutboundTokenResponseMessage" scope="prototype" />
 
-    <bean id="ResolveAttributesForAudiencePredicate"
-        class="net.shibboleth.profile.config.logic.ResolveAttributesPredicate"
-        p:relyingPartyContextLookupStrategy-ref="AudienceRelyingPartyCreationStrategy" />
-
-    <bean id="InitializeOutboundMessageContext"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.InitializeOutboundResponseMessageContext"
-        scope="prototype" />
-
-    <bean id="ValidateClientIDAgainstPolicy"
-        class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ValidateClientIDAgainstPolicy"
-        p:clientIDLookupStrategy-ref="shibboleth.ClientIDLookupStrategy"
-        scope="prototype"
-        p:unregisteredClientPolicyEnforcer="#{getObject('shibboleth.oidc.UnregisteredClientPolicyEnforcer') ?: getObject('shibboleth.oidc.DefaultUnregisteredClientPolicyEnforcer')}"/>
-
-    <bean id="ValidateGrantType" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateGrantType"
-        scope="prototype" />
-
-    <!-- Condition signaling that request was NOT for client_credentials grant. -->        
-    <bean id="NotClientCredentialsGrantCondition" parent="shibboleth.Conditions.NOT">
-        <constructor-arg>
-            <bean class="net.shibboleth.idp.plugin.oidc.op.messaging.context.logic.RequestedGrantTypesCondition"
-                p:grantTypes="#{T(com.nimbusds.oauth2.sdk.GrantType).CLIENT_CREDENTIALS}" />
-        </constructor-arg>
-    </bean>
-
-    <!-- Condition signaling that request was for authorizaton_code grant. -->        
-    <bean id="AuthorizationCodeGrantCondition"
-        class="net.shibboleth.idp.plugin.oidc.op.messaging.context.logic.RequestedGrantTypesCondition"
-        p:grantTypes="#{T(com.nimbusds.oauth2.sdk.GrantType).AUTHORIZATION_CODE}" />
-
-    <!-- Condition signaling that request was for refresh_token grant. -->        
-    <bean id="RefreshTokenGrantCondition"
-        class="net.shibboleth.idp.plugin.oidc.op.messaging.context.logic.RequestedGrantTypesCondition"
-        p:grantTypes="#{T(com.nimbusds.oauth2.sdk.GrantType).REFRESH_TOKEN}" />
-
-    <!-- Traditional third-party grant handling. -->
-
-    <bean id="ValidateGrant" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateGrant" scope="prototype"
-        p:replayCache-ref="shibboleth.ReplayCache"
-        p:revocationCache-ref="shibboleth.oidc.RevocationCache"
-        p:tokenRevocationCondition="#{getObject('%{idp.oauth2.revocationCondition:shibboleth.BiConditions.FALSE}')}">
-        <property name="chainRevocationLifetimeLookupStrategy">
-            <bean class="net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultChainRevocationLifetimeLookupStrategy"
-                p:clockSkew="%{idp.policy.clockSkew:PT5M}" p:useActiveProfileOnly="false" />
+    <!-- In place of BuildErrorResponseFromEvent, with the error map of this plugin. -->
+    <bean id="OpenIDVCIBuildErrorResponseFromEvent"
+        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.BuildTokenErrorResponseFromEvent" scope="prototype"
+        p:httpServletResponseSupplier-ref="shibboleth.HttpServletResponseSupplier"
+        p:mappedErrors="#{getObject('shibboleth.oidc.token.MappedErrors') ?: getObject('openidvci.token.MappedErrors')}">
+        <property name="eventContextLookupStrategy">
+            <bean class="net.shibboleth.idp.profile.context.navigate.WebFlowCurrentEventLookupFunction" />
         </property>
     </bean>
 
-    <!-- REPLACED WIRING. Code verifier lookup of this plugin. -->
-    <bean id="ValidatePKCE" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidatePKCE" scope="prototype"
-        p:codeVerifierLookupStrategy-ref="RequestCodeVerifierLookupFunction"/>
-
-    <bean id="RequestCodeVerifierLookupFunction" class="org.geant.shibboleth.plugin.openidvci.messaging.context.navigate.RequestCodeVerifierLookupFunction" />
-
-    <bean id="ValidateRedirectURI" class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ValidateRedirectURI"
-        scope="prototype"
-        p:activationCondition-ref="AuthorizationCodeGrantCondition"
-        p:redirectURILookupStrategy-ref="shibboleth.TokenRequestRedirectURILookupStrategy"
-        p:validRedirectURIsLookupStrategy-ref="shibboleth.TokenRequestValidRequestUrisLookupStrategy"
-        p:requireRequestedValue="false"
-        p:unregisteredClientPolicyEnforcer="#{getObject('shibboleth.oidc.UnregisteredClientPolicyEnforcer') ?: getObject('shibboleth.oidc.DefaultUnregisteredClientPolicyEnforcer')}"/>
-
-    <bean id="shibboleth.TokenRequestRedirectURILookupStrategy"
-        class="net.shibboleth.idp.plugin.oidc.op.messaging.context.navigate.TokenRequestRedirectURILookupFunction" />
-
-    <bean id="shibboleth.TokenRequestValidRequestUrisLookupStrategy"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.TokenRequestValidRequestURIsLookupFunction" />
-
-    <bean id="SetRequestedClaimsToResponseContext"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SetRequestedClaimsToResponseContext" scope="prototype"
-        p:requestedClaimsLookupStrategy-ref="shibboleth.TokenRequestRequestedClaimsLookupFunction"
-        p:transcoderRegistry-ref="shibboleth.AttributeRegistryService" />
-
-    <bean id="shibboleth.TokenRequestRequestedClaimsLookupFunction"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.TokenRequestRequestedClaimsLookupFunction" />
-
-    <bean id="SetAuthenticationContextClassReferenceFromAuthzCodeToResponseContext"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SetAuthenticationContextClassReferenceFromAuthzCodeToResponseContext"
-        scope="prototype" />
-
-    <bean id="SetAuthenticationTimeFromAuthzCodeToResponseContext"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SetAuthenticationTimeToResponseContext" scope="prototype"
-        p:authTimeLookupStrategy-ref="shibboleth.TokenRequestAuthTimeLookupFunction" />
-
-    <bean id="shibboleth.TokenRequestAuthTimeLookupFunction"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.TokenRequestAuthTimeLookupFunction" />
-
-    <bean id="SetTokenDeliveryAttributesFromTokenToResponseContext"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SetTokenDeliveryAttributesFromTokenToResponseContext"
-        scope="prototype" />
-
-    <bean id="SetConsentToResponseContext"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SetConsentFromTokenToResponseContext" scope="prototype" />
+    <bean id="InitializeAuthenticationContext"
+        class="org.geant.shibboleth.plugin.openidvci.profile.impl.InitializeAuthenticationContext" scope="prototype" />
 
-    <bean id="InitializeSubjectContext"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.InitializeSubjectContext" scope="prototype" />
-
-    <bean id="SetSubjectFromAuthzCodeToResponseContext"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SetSubjectToResponseContext" scope="prototype" />
-
-    <bean id="SetSectorIdentifierForAttributeResolution"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SetSectorIdentifierForAttributeResolution" scope="prototype" />
-
-    <!-- client_credentials grant handling. -->
-
-    <bean id="SetAuthenticationContextClassReferenceToResponseContext"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SetAuthenticationContextClassReferenceToResponseContext"
-        scope="prototype" />
-
-    <bean id="SetAuthenticationTimeToResponseContext"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SetAuthenticationTimeToResponseContext" scope="prototype" />
+    <!--
+    REPLACED, KEEPING THE BEAN ID.
+    APIRequestClientIDLookupFunction in place of TokenRequestClientIDLookupFunction.
+    -->
+    <bean id="shibboleth.ClientIDLookupStrategy"
+        class="org.geant.shibboleth.plugin.openidvci.profile.context.navigate.APIRequestClientIDLookupFunction"
+        p:credentialOfferCache-ref="openidvci.CredentialOfferCache"
+        p:dataSealer-ref="#{'%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}" />
 
-    <bean id="SetSubjectFromSubjectContextToResponseContext"
-            class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SetSubjectToResponseContext" scope="prototype">
-        <property name="subjectLookupStrategy">
-            <bean parent="shibboleth.Functions.Compose">
-                <constructor-arg>
-                    <bean class="net.shibboleth.idp.authn.context.navigate.SubjectContextPrincipalLookupFunction" />
-                </constructor-arg>
-                <constructor-arg>
-                    <ref bean="shibboleth.ChildLookup.SubjectContext"/>
-                </constructor-arg>
-            </bean>
-        </property>
-    </bean>
+    <!-- Beans of this flow in place of beans of oidc/token. -->
 
-    <bean id="SetSessionIdToResponseContext"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SetSessionIdToResponseContext" scope="prototype"
-        p:sessionIdLookupStrategy-ref="SessionIdGenerationStrategy" />
-
-    <bean id="SetSessionIdFromAuthzCodeToResponseContext"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SetSessionIdToResponseContext" scope="prototype" />
-
-    <!-- Common grant handling.  -->
-
-    <bean id="ValidateScope" class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ValidateScope" scope="prototype"
-        p:requestedScopeLookupStrategy-ref="TokenRequestScopeLookupStrategy"
-        p:allowedScopeLookupStrategy="#{getObject('shibboleth.oidc.AllowedScopeStrategy') ?: getObject('shibboleth.oidc.DefaultAllowedScopeStrategy')}" />
-
-    <bean id="ValidateAudience"
-        class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ValidateAudience" scope="prototype"
-        p:allowedAudienceLookupStrategy="#{getObject('shibboleth.oidc.AllowedAudienceStrategy') ?: getObject('shibboleth.oidc.DefaultAllowedAudienceStrategy')}">
-        <property name="selfAudienceCondition">
-            <bean parent="shibboleth.Conditions.AND">
-                <constructor-arg>
-                    <list>
-                        <ref bean="BuildOIDCTokensCondition" />
-                        <ref bean="NotClientCredentialsGrantCondition" />
-                    </list>
-                </constructor-arg>
-            </bean>
+    <!-- In place of UnwrapGrant, with the client id lookup of this plugin and not for the pre-authorized code. -->
+    <bean id="OpenIDVCIUnwrapGrant" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.UnwrapGrant"
+        scope="prototype"
+        c:sealer-ref="#{'%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}"
+        p:refreshTokenDeserializers-ref="#{'%{idp.oauth2.refreshToken.deserializers:shibboleth.oidc.DefaultRefreshTokenDeserializers}'.trim()}"
+        p:clientIDLookupStrategy-ref="shibboleth.ClientIDLookupStrategy">
+        <property name="activationCondition">
+            <bean parent="shibboleth.Conditions.NOT" c:_0-ref="OpenIDVCIPreAuthorizedCodeGrantCondition" />
         </property>
     </bean>
 
-    <bean id="TokenRequestScopeLookupStrategy"
-        class="net.shibboleth.idp.plugin.oidc.op.messaging.context.navigate.TokenRequestScopeLookupFunction" />
-
-    <bean id="BuildOIDCTokensCondition"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.logic.IssueIDTokenCondition" />
+    <!-- In place of ValidatePKCE, with the code verifier lookup of this plugin. -->
+    <bean id="OpenIDVCIValidatePKCE" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidatePKCE"
+        scope="prototype" p:codeVerifierLookupStrategy-ref="RequestCodeVerifierLookupFunction"/>
 
-    <bean id="IssueIDTokenCondition" parent="shibboleth.Conditions.AND">
-        <constructor-arg>
-            <list>
-                <ref bean="BuildOIDCTokensCondition" />
-                <bean parent="shibboleth.Conditions.OR">
-                    <constructor-arg>
-                        <list>
-                            <ref bean="AuthorizationCodeGrantCondition" />
-                            <bean parent="shibboleth.Conditions.AND">
-                                <constructor-arg>
-                                    <list>
-                                        <ref bean="RefreshTokenGrantCondition" />
-                                        <bean class="net.shibboleth.oidc.profile.config.logic.IssueIdTokenViaRefreshTokenPredicate" />
-                                    </list>
-                                </constructor-arg>
-                            </bean>
-                        </list>
-                    </constructor-arg>
-                </bean>
-            </list>
-        </constructor-arg>
-    </bean>
+    <bean id="RequestCodeVerifierLookupFunction"
+        class="org.geant.shibboleth.plugin.openidvci.messaging.context.navigate.RequestCodeVerifierLookupFunction" />
 
     <!--
-    Do a metadata lookup for the primary audience of the token for encryption purposes.
-    Contexts are stored under the outbound MessageContext, including the new RelyingPartyContext.
+    In place of ValidateGrant in TraditionalGrantProcessing, with the client instance key condition of this plugin
+    added to the token revocation condition.
     -->
-    
-    <bean id="AudienceOIDCMetadataLookup" class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor"
-            scope="prototype" c:executionDirection="OUTBOUND">
-        <constructor-arg name="messageHandler">
-            <bean class="net.shibboleth.idp.plugin.oidc.op.profile.impl.OIDCMetadataLookupHandler" scope="prototype">
-                <property name="clientInformationResolver">
-                    <ref bean="shibboleth.ClientInformationResolver" />
-                </property>
-                <property name="clientIDLookupStrategy">
-                    <ref bean="AudienceClientIDLookupStrategy" />
-                </property>
-            </bean>
-        </constructor-arg>
-    </bean>
-    
-    <bean id="AudienceClientIDLookupStrategy"
-        class="net.shibboleth.idp.plugin.oidc.op.messaging.context.navigate.AudienceClientIDLookupFunction" />
-
-    <bean id="InitializeAudienceRelyingPartyContext"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.InitializeRelyingPartyContext" scope="prototype"
-        p:relyingPartyContextCreationStrategy-ref="AudienceRelyingPartyCreationStrategy"
-        p:oidcMetadataContextLookupStrategy-ref="LookupOutboundOIDCMetadataContext"
-        p:clientIDLookupStrategy-ref="AudienceClientIDLookupStrategy"
-        p:inbound="false" />
-
-    <bean id="LookupOutboundOIDCMetadataContext"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultOIDCMetadataContextLookupFunction"
-        p:inbound="false" />
-
-    <bean id="AudienceRelyingPartyCreationStrategy" parent="shibboleth.Functions.Compose"
-        c:g-ref="shibboleth.ChildLookupOrCreate.RelyingPartyContext"
-        c:f-ref="shibboleth.MessageContextLookup.Outbound" />
-
-    <bean id="AudienceSAMLProtocolAndRole"
-            class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor" scope="prototype"
-            c:executionDirection="OUTBOUND"
-            p:activationCondition-ref="%{idp.oidc.metadata.saml:shibboleth.Conditions.TRUE}">
-        <constructor-arg name="messageHandler">
-            <bean class="org.opensaml.saml.common.binding.impl.SAMLProtocolAndRoleHandler" scope="prototype"
-                p:protocol="http://openid.net/specs/openid-connect-core-1_0.html"
-                p:role-ref="shibboleth.MetadataLookup.Role"
-                p:entityContextClass="org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext"/>
-        </constructor-arg>
-    </bean>
-
-    <bean id="SetAudienceEntityIdToSAMLPeerEntityContext"
-            class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor" scope="prototype"
-            c:executionDirection="OUTBOUND"
-            p:errorEvent="#{T(org.opensaml.profile.action.EventIds).INVALID_MSG_CTX}"
-            p:activationCondition-ref="%{idp.oidc.metadata.saml:shibboleth.Conditions.TRUE}">
-        <constructor-arg name="messageHandler">
-            <bean class="net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.impl.SetEntityIdToSAMLPeerEntityContext"
-                scope="prototype"
-                p:clientIDLookupStrategy-ref="AudienceClientIDLookupStrategy"
-                p:entityContextClass="org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext" />
-        </constructor-arg>
-    </bean>
- 
-     <bean id="AudienceSAMLMetadataLookup"
-        class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor" scope="prototype"
-        c:executionDirection="OUTBOUND"
-        p:activationCondition-ref="%{idp.oidc.metadata.saml:shibboleth.Conditions.TRUE}">
-        <constructor-arg name="messageHandler">
-            <bean class="org.opensaml.saml.common.binding.impl.SAMLMetadataLookupHandler" scope="prototype"
-                p:entityContextClass="org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext">
-                <property name="roleDescriptorResolver">
-                    <bean class="org.opensaml.saml.metadata.resolver.impl.PredicateRoleDescriptorResolver"
-                        c:mdResolver-ref="shibboleth.MetadataResolver" />
-                </property>
-            </bean>
-        </constructor-arg>
-    </bean>
- 
-     <bean id="PopulateAudienceOIDCMetadataContext"
-            class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor" scope="prototype"
-            c:executionDirection="OUTBOUND"
-            p:activationCondition-ref="%{idp.oidc.metadata.saml:shibboleth.Conditions.TRUE}">
-        <constructor-arg name="messageHandler">
-            <bean class="net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.impl.PopulateOIDCMetadataContext"
-                scope="prototype" />
-        </constructor-arg>
-    </bean>
-        
-    <bean id="InitializeAudienceRelyingPartyContextFromSAMLPeer"
-        class="net.shibboleth.idp.saml.profile.impl.InitializeRelyingPartyContextFromSAMLPeer" scope="prototype"
-        p:relyingPartyContextCreationStrategy-ref="AudienceRelyingPartyCreationStrategy"
-        p:peerEntityContextLookupStrategy-ref="LookupOutboundPeerEntityContext" />
-
-    <bean id="LookupOutboundPeerEntityContext" parent="shibboleth.Functions.Compose"
-        c:g-ref="shibboleth.ChildLookup.SAMLPeerEntityContext"
-        c:f-ref="shibboleth.MessageContextLookup.Outbound"/>
-
-    <bean id="SelectAudienceRelyingPartyConfiguration"
-        class="net.shibboleth.idp.profile.impl.SelectRelyingPartyConfiguration" scope="prototype"
-        p:relyingPartyContextLookupStrategy-ref="AudienceRelyingPartyCreationStrategy"
-        p:relyingPartyConfigurationResolver-ref="shibboleth.RelyingPartyResolverService" />
-
-    <bean id="SelectAudienceProfileConfiguration"
-        class="net.shibboleth.idp.profile.impl.SelectProfileConfiguration" scope="prototype"
-        p:relyingPartyContextLookupStrategy-ref="AudienceRelyingPartyCreationStrategy"
-        p:profileId="#{T(net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenAudienceConfiguration).PROFILE_ID}" />
-
-    <bean id="ResolveAttributesForAudience" class="net.shibboleth.idp.profile.impl.ResolveAttributes" scope="prototype"
-        c:resolverService-ref="shibboleth.AttributeResolverService"
-        p:maskFailures="%{idp.service.attribute.resolver.maskFailures:true}"
-        p:issuerLookupStrategy-ref="AudienceIssuerLookupFunction" />
-
-    <bean id="FilterAttributesForAudience" class="net.shibboleth.idp.profile.impl.FilterAttributes" scope="prototype"
-        c:filterService-ref="shibboleth.AttributeFilterService"
-        p:maskFailures="%{idp.service.attribute.filter.maskFailures:true}"
-        p:issuerLookupStrategy-ref="AudienceIssuerLookupFunction"
-        p:proxiedRequesterContextLookupStrategy-ref="AudienceProxiedRequesterLookupFunction"
-        p:proxiedRequesterMetadataContextLookupStrategy-ref="LookupOutboundSAMLEntityContext" />
-
-    <bean id="AudienceIssuerLookupFunction"
-        class="net.shibboleth.profile.context.navigate.IssuerLookupFunction"
-        p:relyingPartyContextLookupStrategy-ref="AudienceRelyingPartyCreationStrategy" />
-
-    <bean id="AudienceProxiedRequesterLookupFunction" parent="shibboleth.Functions.Compose">
-        <constructor-arg name="g">
-            <bean class="org.opensaml.messaging.context.navigate.ChildContextLookup"
-                c:type="#{ T(org.opensaml.profile.context.ProxiedRequesterContext) }" />
-        </constructor-arg>
-        <constructor-arg name="f">
-            <ref bean="shibboleth.MessageContextLookup.Outbound" />
-        </constructor-arg>
-    </bean>
-
-    <bean id="LookupOutboundSAMLEntityContext" parent="shibboleth.Functions.Compose"
-        c:g-ref="shibboleth.ChildLookup.SAMLMetadataContext"
-        c:f-ref="LookupOutboundPeerEntityContext"/>
-
-    <!-- Back to token prep. -->
-    
-    <!-- OIDC response handling for access/refresh tokens. -->
-
-    <bean id="PopulateUserInfoAccessTokenSignatureSigningParameters"
-            class="net.shibboleth.oidc.profile.impl.PopulateJWTSignatureSigningParameters"
-            scope="prototype"
-            c:strategy-ref="shibboleth.MessageContextLookup.Inbound">
-        <property name="configurationLookupStrategy">
-            <bean lazy-init="true"
-                class="net.shibboleth.oidc.profile.config.navigate.JWTSignatureSigningConfigurationLookupFunction" />
-        </property>
-        <property name="signatureSigningParametersResolver">
-            <bean class="net.shibboleth.oidc.security.jose.impl.ClientInformationSignatureSigningParametersResolver">
-                <constructor-arg name="signatureAlgorithmLookupStrategy">
+    <bean id="OpenIDVCIValidateGrant" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateGrant"
+        scope="prototype"
+        p:replayCache-ref="shibboleth.ReplayCache"
+        p:revocationCache-ref="shibboleth.oidc.RevocationCache">
+        <property name="tokenRevocationCondition">
+            <bean parent="shibboleth.BiConditions.OR"
+                c:_0="#{getObject('%{idp.oauth2.revocationCondition:shibboleth.BiConditions.FALSE}')}">
+                <constructor-arg index="1">
                     <bean
-                        class="net.shibboleth.oidc.profile.config.navigate.ClientInformationStringValueLookupFunction"
-                        c:keyName="id_token_signed_response_alg" />
+                        class="org.geant.shibboleth.plugin.openidvci.profile.logic.ClientInstanceKeyRevocationCondition"
+                        c:strategy-ref="openidvci.ClientInstanceKeyThumbprintLookup" />
                 </constructor-arg>
-                <constructor-arg name="defaultAlgorithmValue" value="RS256" />
             </bean>
         </property>
-        <property name="securityParametersContextLookupStrategy">
-            <bean parent="shibboleth.Functions.Compose"
-                c:g-ref="shibboleth.oidc.ChildLookupOrCreate.JWTSecurityParameters"
-                c:f-ref="shibboleth.ChildLookup.RelyingParty" />
+        <property name="chainRevocationLifetimeLookupStrategy">
+            <bean class="net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultChainRevocationLifetimeLookupStrategy"
+                p:clockSkew="%{idp.policy.clockSkew:PT5M}" p:useActiveProfileOnly="false" />
         </property>
     </bean>
 
-    <!-- REPLACED WIRING. Client id lookup of this plugin. -->
-    <bean id="BuildOIDCAccessToken"
+    <!-- In place of BuildOIDCAccessToken, with the client id lookup of this plugin. -->
+    <bean id="OpenIDVCIBuildOIDCAccessToken"
         class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.BuildAccessToken" scope="prototype"
         p:dataSealer="#{getObject('%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim())}"
         p:xmlSafeIdentifier="%{idp.oidc.xmlSafeIdentifiers:true}"
@@ -405,469 +111,87 @@
         p:clientIDLookupStrategy-ref="openidvci.ProfileRequestClientIDLookupStrategy"
         p:objectMapper-ref="#{'%{idp.oidc.logging.objectMapper:shibboleth.oidc.JSONObjectMapper}'.trim()}" />
 
-    <bean id="SignOIDCAccessToken" class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor"
-            scope="prototype" c:executionDirection="OUTBOUND">
-        <constructor-arg name="messageHandler">
-            <bean id="SignOIDCAccessTokenHandler"
-                class="net.shibboleth.oidc.security.impl.SignJWTHandler" scope="prototype" p:logName="Access Token"
-                p:typeHeader="at+jwt">
-                <property name="securityParametersLookupStrategy">
-                    <bean parent="shibboleth.Functions.Compose">
-                        <constructor-arg name="g">
-                            <bean parent="shibboleth.Functions.Compose"
-                                c:g-ref="shibboleth.oidc.ChildLookupOrCreate.JWTSecurityParameters"
-                                c:f-ref="shibboleth.ChildLookup.RelyingParty" />
-                        </constructor-arg>
-                        <constructor-arg name="f">
-                            <bean parent="shibboleth.Functions.Expression" c:expression="#input.getParent()" />
-                        </constructor-arg>
-                    </bean>
-                </property>
-                <property name="claimsToSignLookupStrategy">
-                     <bean
-                        class="net.shibboleth.idp.plugin.oidc.op.messaging.context.logic.JWTClaimsSetFromJWTAccessTokenLookupFunction" />
-                </property>
-                <property name="jwtUpdateConsumer">
-                    <bean
-                        class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.JWTAccessTokenUpdateStrategy" />
-                </property>
-            </bean>
-        </constructor-arg>
-    </bean>
- 
-    <bean id="SetOAuthAccessTokenToResponseContext"
-        class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.SetAccessTokenToResponseContext"
-        scope="prototype" />
-
-    <bean id="SetRefreshTokenToResponseContext"
-            class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SetRefreshTokenToResponseContext" scope="prototype"
-            c:sealer-ref="#{'%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}"
-            p:revocationCache-ref="shibboleth.oidc.RevocationCache"
-            p:activationCondition-ref="#{'%{idp.oauth2.refreshToken.activation:DefaultRefreshTokenActivationCondition}'.trim()}"
-            p:refreshTokenSerializationStrategies-ref="#{'%{idp.oauth2.refreshToken.serializationStrategies:shibboleth.oidc.DefaultRefreshTokenSerializationStrategies}'.trim()}"
-            p:xmlSafeIdentifier="%{idp.oidc.xmlSafeIdentifiers:true}"
-            p:identifierGeneratorLookupStrategy-ref="shibboleth.oidc.DefaultIdentifierGenerationStrategy">
-        <property name="tokenRevocationLifetimeLookupStrategy">
-            <bean class="net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultTokenRevocationLifetimeLookupStrategy"
-                p:clockSkew="%{idp.policy.clockSkew:PT5M}" />
-        </property>
-    </bean>
-
-    <util:map id="shibboleth.oidc.DefaultRefreshTokenSerializationStrategies">
-        <entry key="JWT">
-            <bean factory-bean="DefaultJwtRefreshTokenSerializerFactory" factory-method="getBean" />
-        </entry>
-    </util:map>
-
-    <bean id="DefaultJwtRefreshTokenSerializerFactory"
-        parent="shibboleth.oidc.RefreshTokenSerializerFactory"
-        c:id="DefaultJwtRefreshTokenSerializationFunction"/>
-
-    <bean id="DefaultJwtRefreshTokenSerializationFunction"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultJwtRefreshTokenSerializationFunction"
-        scope="prototype"
-        p:dataSealer-ref="#{'%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}"
-        p:objectMapper-ref="#{'%{idp.oidc.logging.objectMapper:shibboleth.oidc.JSONObjectMapper}'.trim()}">
-        <property name="signingParametersHandler">
-            <bean class="net.shibboleth.oidc.profile.impl.PopulateJWTSignatureSigningParametersHandler"
-                scope="prototype">
-                <property name="configurationLookupStrategy">
-                    <bean parent="shibboleth.Functions.Compose">
-                        <constructor-arg name="f">
-                            <bean class="org.opensaml.messaging.context.navigate.ParentContextLookup"
-                                c:type="org.opensaml.profile.context.ProfileRequestContext" />
-                        </constructor-arg>
-                        <constructor-arg name="g">
-                            <bean lazy-init="true"
-                                class="net.shibboleth.oidc.profile.config.navigate.JWTSignatureSigningConfigurationLookupFunction" />
-                        </constructor-arg>
-                    </bean>
-                </property>
-                <property name="signatureSigningParametersResolver">
-                    <bean class="net.shibboleth.oidc.security.jose.impl.ClientInformationSignatureSigningParametersResolver">
-                        <constructor-arg name="signatureAlgorithmLookupStrategy">
-                            <bean class="net.shibboleth.oidc.profile.config.navigate.ClientInformationStringValueLookupFunction"
-                                c:keyName="id_token_signed_response_alg" />
-                        </constructor-arg>
-                        <constructor-arg name="defaultAlgorithmValue" value="RS256" />
-                    </bean>
-                </property>
-            </bean>
-        </property>
-        <property name="audienceLookupStrategy">
-            <bean parent="shibboleth.Functions.Expression"
-                c:expression="#custom.get().getRequestURL().toString()"
-                p:customObject-ref="shibboleth.HttpServletRequestSupplier" />
-        </property>
-        <property name="typeHeaderLookupStrategy">
-            <bean parent="shibboleth.BiFunctions.Expression" c:expression="#null" />
-        </property>
-    </bean>
-
-
-    <bean id="DefaultRefreshTokenActivationCondition" parent="shibboleth.Conditions.AND">
-        <constructor-arg>
-            <list>
-                <bean class="net.shibboleth.oidc.profile.config.logic.RefreshTokensEnabledPredicate" />
-                <ref bean="NotClientCredentialsGrantCondition" />
-                <bean parent="shibboleth.Conditions.OR">
-                    <constructor-arg>
-                        <list>
-                            <bean parent="shibboleth.Conditions.NOT">
-                                <constructor-arg>
-                                    <ref bean="BuildOIDCTokensCondition" />
-                                </constructor-arg>
-                            </bean>
-                            <bean parent="shibboleth.Conditions.AND">
-                                <constructor-arg>
-                                    <list>
-                                        <ref bean="BuildOIDCTokensCondition" />
-                                        <bean class="net.shibboleth.idp.plugin.oidc.op.profile.logic.OfflineAccessScopeCondition" />
-                                    </list>
-                                </constructor-arg>
-                            </bean>
-                        </list>
-                    </constructor-arg>
-                </bean>
-            </list>
-        </constructor-arg>
-    </bean>
-
-    <!-- ID token actions. -->
-
-    <bean id="PopulateIDTokenSignatureSigningParameters"
-            class="net.shibboleth.oidc.profile.impl.PopulateJWTSignatureSigningParameters"
-            scope="prototype" p:noResultIsError="true"
-            c:strategy-ref="shibboleth.MessageContextLookup.Inbound">
-        <property name="configurationLookupStrategy">
-            <bean lazy-init="true"
-                class="net.shibboleth.oidc.profile.config.navigate.JWTSignatureSigningConfigurationLookupFunction" />
-        </property>
-        <property name="signatureSigningParametersResolver">
-            <bean class="net.shibboleth.oidc.security.jose.impl.ClientInformationSignatureSigningParametersResolver">
-                <constructor-arg name="signatureAlgorithmLookupStrategy">
-                    <bean
-                        class="net.shibboleth.oidc.profile.config.navigate.ClientInformationStringValueLookupFunction"
-                        c:keyName="id_token_signed_response_alg" />
-                </constructor-arg>
-                <constructor-arg name="defaultAlgorithmValue" value="RS256" />
-            </bean>
-        </property>
-         <property name="securityParametersContextLookupStrategy">
-            <bean parent="shibboleth.Functions.Compose"
-                c:g-ref="shibboleth.oidc.ChildLookupOrCreate.JWTSecurityParameters"
-                c:f-ref="shibboleth.MessageContextLookup.Outbound" />
-        </property>
-    </bean>
-
-    <bean id="PopulateIDTokenEncryptionParameters"
-        class="net.shibboleth.oidc.profile.impl.PopulateJWTEncryptionParameters" scope="prototype"
-        p:forFriendlyName="ID Token">
-        <property name="clientMetadataContextLookupStrategy">
-           <bean class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultOIDCMetadataContextLookupFunction"
-               p:inbound="true" />
-        </property>
-        <property name="configurationLookupStrategy">
-            <bean lazy-init="true"
-                class="net.shibboleth.oidc.profile.config.navigate.JWTEncryptionConfigurationLookupFunction" />
-        </property>
-        <property name="encryptionParametersResolver">
-            <bean class="net.shibboleth.oidc.security.jose.impl.DefaultEncryptionParametersResolver">
-                <property name="keyTransportEncryptionAlgorithmsLookupStrategy">
-                    <bean
-                        class="net.shibboleth.oidc.security.jose.impl.ClientInformationKeyTransportEncryptionAlgorithmsLookupStrategy">
-                        <constructor-arg>
-                            <bean
-                                class="net.shibboleth.oidc.profile.config.navigate.ClientInformationStringValueLookupFunction"
-                                c:keyName="id_token_encrypted_response_alg" />
-                        </constructor-arg>
-                    </bean>
-                </property>
-                <property name="dataEncryptionAlgorithmsLookupStrategy">
-                    <bean class="net.shibboleth.oidc.security.jose.impl.ClientInformationDataEncryptionAlgorithmsLookupStrategy">
-                        <constructor-arg>
-                            <bean
-                                class="net.shibboleth.oidc.profile.config.navigate.ClientInformationStringValueLookupFunction"
-                                c:keyName="id_token_encrypted_response_enc" />
-                        </constructor-arg>
-                    </bean>
-                </property>
-            </bean>
-        </property>
-    </bean>
-
-    <bean id="AddIDTokenShell"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.AddIDTokenShell" scope="prototype" />
-
-    <bean id="AddAttributeClaimsToIDToken"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.AddAttributesToClaimsSet" scope="prototype"
-        p:transcoderRegistry-ref="shibboleth.AttributeRegistryService"
-        p:reservedClaimNames="#{getObject('shibboleth.oidc.IDTokenReservedClaimNames') ?: getObject('shibboleth.oidc.DefaultIDTokenReservedClaimNames')}" />
+    <bean id="openidvci.ProfileRequestClientIDLookupStrategy" parent="shibboleth.Functions.Compose"
+        c:g-ref="shibboleth.ClientIDLookupStrategy"
+        c:f-ref="shibboleth.MessageContextLookup.Inbound" />
 
-    <bean id="AddTokenDeliveryAttributesToIDToken"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.AddTokenDeliveryAttributesToClaimsSet" scope="prototype"
-        p:targetIDToken="true" />
+    <!-- Beans of oidc/abstract, not a parent of this flow. -->
 
-    <bean id="AddAuthTimeToIDToken"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.AddAuthTimeToIDToken" scope="prototype" />
+    <bean id="ResolveAttributes" class="net.shibboleth.idp.profile.impl.ResolveAttributes" scope="prototype"
+        c:resolverService-ref="shibboleth.AttributeResolverService"
+        p:maskFailures="%{idp.service.attribute.resolver.maskFailures:true}" />
 
-    <bean id="AddAcrToIDToken"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.AddAcrToIDToken" scope="prototype" />
+    <bean id="FilterAttributes" class="net.shibboleth.idp.profile.impl.FilterAttributes" scope="prototype"
+        c:filterService-ref="shibboleth.AttributeFilterService"
+        p:maskFailures="%{idp.service.attribute.filter.maskFailures:true}" />
 
-    <bean id="AddNonceToIDToken"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.AddNonceToIDToken" scope="prototype"
-        p:requestNonceLookupStrategy-ref="shibboleth.TokenRequestNonceLookupStrategy" />
+    <!-- The pre-authorized code grant. -->
 
-    <bean id="shibboleth.TokenRequestNonceLookupStrategy"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.TokenRequestNonceLookupFunction" />
+    <bean id="OpenIDVCIPreAuthorizedCodeGrantCondition"
+        class="net.shibboleth.idp.plugin.oidc.op.messaging.context.logic.RequestedGrantTypesCondition"
+        p:grantTypes="#{T(org.geant.shibboleth.plugin.openidvci.messaging.impl.PreAuthorizedCodeGrant).GRANT_TYPE}" />
 
-    <bean id="AddAccessTokenHashToIDToken"
-            class="net.shibboleth.idp.plugin.oidc.op.profile.impl.AddAccessTokenHashToIDToken" scope="prototype">
-        <property name="securityParametersLookupStrategy">
-            <bean parent="shibboleth.Functions.Compose"
-                c:g-ref="shibboleth.oidc.ChildLookupOrCreate.JWTSecurityParameters"
-                c:f-ref="shibboleth.MessageContextLookup.Outbound" />
-        </property>
-    </bean>
+    <bean id="OpenIDVCIUnwrapPreAuthorizedCode"
+        class="org.geant.shibboleth.plugin.openidvci.profile.impl.UnwrapPreAuthorizedCode" scope="prototype"
+        c:sealer-ref="#{'%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}"
+        p:clientIDLookupStrategy-ref="openidvci.ProfileRequestClientIDLookupStrategy"
+        p:credentialOfferCache-ref="openidvci.CredentialOfferCache"
+        p:activationCondition-ref="OpenIDVCIPreAuthorizedCodeGrantCondition" />
 
-    <bean id="ManipulateClaimsForIDToken"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ManipulateClaimsForIDToken" scope="prototype"
-        p:objectMapper-ref="#{'%{idp.oidc.logging.objectMapper:shibboleth.oidc.JSONObjectMapper}'.trim()}" />
+    <bean id="OpenIDVCIValidatePreAuthorizedCode"
+        class="org.geant.shibboleth.plugin.openidvci.profile.impl.ValidatePreAuthorizedCode" scope="prototype"
+        p:replayCache-ref="shibboleth.ReplayCache"/>
 
-    <bean id="SignIDToken" class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor"
-            scope="prototype" c:executionDirection="OUTBOUND">
-        <constructor-arg name="messageHandler">
-            <bean id="SignIDTokenHandler"
-                class="net.shibboleth.oidc.security.impl.SignJWTHandler" scope="prototype"  p:logName="ID Token">
-                <property name="claimsToSignLookupStrategy">
-                     <bean
-                        class="net.shibboleth.idp.plugin.oidc.op.messaging.context.logic.JWTClaimsSetFromIDTokenLookupFunction" />
-                </property>
-                <property name="jwtUpdateConsumer">
-                    <bean
-                        class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.ProcessedTokenUpdateStrategy" />
-                </property>
-            </bean>
-        </constructor-arg>
-    </bean>
+    <bean id="OpenIDVCIConsumeCredentialOffer"
+        class="org.geant.shibboleth.plugin.openidvci.profile.impl.ConsumeCredentialOffer" scope="prototype"
+        p:credentialOfferCache-ref="openidvci.CredentialOfferCache" />
 
-    <bean id="EncryptIDToken" class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor"
-            scope="prototype" c:executionDirection="OUTBOUND">
-        <constructor-arg name="messageHandler">
-            <bean id="EncryptIDTokenHandler"
-                class="net.shibboleth.oidc.security.impl.EncryptJWTHandler" scope="prototype"
-                p:logName="ID Token">
-                <property name="payloadToEncryptLookupStrategy">
-                    <bean
-                        class="net.shibboleth.idp.plugin.oidc.op.messaging.context.logic.PayloadFromProcessedTokenLookupFunction" />
-                </property>
-                <property name="jwtUpdateConsumer">
-                    <bean
-                        class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.ProcessedTokenUpdateStrategy" />
-                </property>
-            </bean>
-        </constructor-arg>
-    </bean>
+    <bean id="OpenIDVCIValidateTxCode" class="org.geant.shibboleth.plugin.openidvci.profile.impl.ValidateTxCode"
+        scope="prototype"/>
 
-    <!-- REPLACED FormOutboundTokenResponseMessage. -->
-    <bean id="FormOutboundMessage" class="org.geant.shibboleth.plugin.openidvci.profile.impl.FormOutboundTokenResponseMessage"
+    <bean id="OpenIDVCISetDPoPProofThumbprintToResponseContext"
+        class="org.geant.shibboleth.plugin.openidvci.profile.impl.SetDPoPProofThumbprintToResponseContext"
         scope="prototype" />
 
-    <bean id="BuildErrorResponseFromEvent"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.BuildTokenErrorResponseFromEvent" scope="prototype"
-        p:httpServletResponseSupplier-ref="shibboleth.HttpServletResponseSupplier"
-        p:mappedErrors="#{getObject('shibboleth.oidc.token.MappedErrors') ?: getObject('openidvci.token.MappedErrors')}">
-        <property name="eventContextLookupStrategy">
-            <bean class="net.shibboleth.idp.profile.context.navigate.WebFlowCurrentEventLookupFunction" />
-        </property>
-    </bean>
-
-    <!-- Third-party token actions. -->
+    <!-- The credentials of a grant. -->
 
-    <bean id="PopulateThirdPartyAccessTokenSignatureSigningParameters"
-        class="net.shibboleth.oidc.profile.impl.PopulateJWTSignatureSigningParameters"
-        scope="prototype" p:noResultIsError="true"
-        p:securityParametersContextLookupStrategy-ref="AudienceSecurityParametersCreationStrategy"
-        c:strategy-ref="shibboleth.MessageContextLookup.Outbound">
-        <property name="configurationLookupStrategy">
-            <bean lazy-init="true"
-                class="net.shibboleth.oidc.profile.config.navigate.JWTSignatureSigningConfigurationLookupFunction" />
-        </property>
-        <property name="signatureSigningParametersResolver">
-            <bean class="net.shibboleth.oidc.security.jose.impl.ClientInformationSignatureSigningParametersResolver">
-                <constructor-arg name="signatureAlgorithmLookupStrategy">
-                    <bean
-                        class="net.shibboleth.oidc.profile.config.navigate.ClientInformationStringValueLookupFunction"
-                        c:keyName="id_token_signed_response_alg" />
-                </constructor-arg>
-                <constructor-arg name="defaultAlgorithmValue" value="RS256" />
-            </bean>
-        </property>
-    </bean>        
-
-    <bean id="AudienceSecurityParametersCreationStrategy" parent="shibboleth.Functions.Compose"
-        c:g-ref="shibboleth.oidc.ChildLookupOrCreate.JWTSecurityParameters"
-        c:f-ref="AudienceRelyingPartyCreationStrategy" />
-
-    <bean id="AudienceSecurityParametersCreationViaMessageContextStrategy" parent="shibboleth.Functions.Compose">
-        <constructor-arg name="g" ref="AudienceSecurityParametersCreationStrategy" />
-        <constructor-arg name="f">
-            <bean parent="shibboleth.Functions.Expression" c:expression="#input.getParent()" />
-        </constructor-arg>
-    </bean>
-        
-    <bean id="PopulateThirdPartyAccessTokenEncryptionParameters"
-            class="net.shibboleth.oidc.profile.impl.PopulateJWTEncryptionParameters" scope="prototype"
-            p:encryptionOptionalPredicate-ref="AudienceEncryptionOptionalPredicate"
-            p:securityParametersContextLookupStrategy-ref="AudienceSecurityParametersCreationStrategy"
-            p:forFriendlyName="JWT Access Token">
-        <property name="clientMetadataContextLookupStrategy">
-           <bean class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultOIDCMetadataContextLookupFunction"
-               p:inbound="false" />
-        </property>
-        <property name="configurationLookupStrategy">
-            <bean lazy-init="true"
-                class="net.shibboleth.oidc.profile.config.navigate.JWTEncryptionConfigurationLookupFunction" />
-        </property>
-        <property name="encryptionParametersResolver">
-            <bean class="net.shibboleth.oidc.security.jose.impl.DefaultEncryptionParametersResolver">
-                <property name="keyTransportEncryptionAlgorithmsLookupStrategy">
-                    <bean
-                        class="net.shibboleth.oidc.security.jose.impl.ClientInformationKeyTransportEncryptionAlgorithmsLookupStrategy">
-                        <constructor-arg>
-                            <bean
-                                class="net.shibboleth.oidc.profile.config.navigate.ClientInformationStringValueLookupFunction"
-                                c:keyName="id_token_encrypted_response_alg"/>
-                        </constructor-arg>
-                    </bean>
-                </property>
-                <property name="dataEncryptionAlgorithmsLookupStrategy">
-                    <bean class="net.shibboleth.oidc.security.jose.impl.ClientInformationDataEncryptionAlgorithmsLookupStrategy">
-                        <constructor-arg>
-                            <bean
-                                class="net.shibboleth.oidc.profile.config.navigate.ClientInformationStringValueLookupFunction"
-                                c:keyName="id_token_encrypted_response_enc"/>
-                        </constructor-arg>
-                    </bean>
-                </property>
-            </bean>
-        </property>
-        <property name="activationCondition">
-            <bean parent="shibboleth.Conditions.NOT" c:_0-ref="BuildOIDCTokensCondition" />
-        </property>
-    </bean>
-
-    <bean id="AudienceEncryptionOptionalPredicate"
-        class="net.shibboleth.oidc.profile.config.logic.EncryptionOptionalPredicate"
-        p:relyingPartyContextLookupStrategy-ref="AudienceRelyingPartyCreationStrategy" />    
-
-    <bean id="AudienceEncryptionContextCreationStrategy" parent="shibboleth.Functions.Compose"
-        c:g-ref="shibboleth.ChildLookupOrCreate.EncryptionParameters"
-        c:f-ref="AudienceRelyingPartyCreationStrategy" />
+    <bean id="OpenIDVCIInitializePotentialCredentials"
+        class="org.geant.shibboleth.plugin.openidvci.profile.impl.InitializePotentialCredentials" scope="prototype" />
 
-    <bean id="AddAttributeClaimsToAccessToken"
+    <bean id="OpenIDVCIAddAttributeClaimsToPotentialCredentials"
         class="net.shibboleth.idp.plugin.oidc.op.profile.impl.AddAttributesToClaimsSet" scope="prototype"
-        p:transcoderRegistry-ref="shibboleth.AttributeRegistryService"
-        p:responseClaimsSetLookupStrategy-ref="AccessTokenClaimsSetLookupFunction"
-        p:reservedClaimNames="#{getObject('shibboleth.oidc.AccessTokenReservedClaimNames') ?: getObject('shibboleth.oidc.DefaultAccessTokenReservedClaimNames')}" />
-
-    <bean id="AccessTokenClaimsSetLookupFunction"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.AccessTokenClaimsSetLookupFunction"
-        p:autoCreate="true" />
-
-    <bean id="BuildAccessToken"
-        class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.BuildAccessToken" scope="prototype"
-        p:dataSealer="#{getObject('%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim())}"
-        p:issuerLookupStrategy-ref="AudienceIssuerLookupFunction"
-        p:accessTokenTypeLookupStrategy-ref="AccessTokenTypeLookupFunction"
-        p:accessTokenLifetimeLookupStrategy-ref="AccessTokenLifetimeLookupFunction"
-        p:xmlSafeIdentifier="%{idp.oidc.xmlSafeIdentifiers:true}"
-        p:identifierGeneratorLookupStrategy-ref="shibboleth.oidc.DefaultIdentifierGenerationStrategy"
-        p:objectMapper-ref="#{'%{idp.oidc.logging.objectMapper:shibboleth.oidc.JSONObjectMapper}'.trim()}" />
-
-    <bean id="AccessTokenTypeLookupFunction"
-        class="net.shibboleth.oidc.profile.config.navigate.AccessTokenTypeLookupFunction"
-        p:relyingPartyContextLookupStrategy-ref="AudienceRelyingPartyCreationStrategy" />
-        
-    <bean id="AccessTokenLifetimeLookupFunction"
-        class="net.shibboleth.oidc.profile.config.navigate.AccessTokenLifetimeLookupFunction"
-        p:relyingPartyContextLookupStrategy-ref="AudienceRelyingPartyCreationStrategy" />
-
-    <bean id="SignAccessToken" class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor"
-            scope="prototype" c:executionDirection="OUTBOUND">
-        <constructor-arg name="messageHandler">
-            <bean id="SignAccessTokenHandler"
-                class="net.shibboleth.oidc.security.impl.SignJWTHandler" scope="prototype" p:logName="Access Token"
-                p:securityParametersLookupStrategy-ref="AudienceSecurityParametersCreationViaMessageContextStrategy"
-                p:typeHeader="at+jwt">
-                <property name="claimsToSignLookupStrategy">
-                     <bean
-                        class="net.shibboleth.idp.plugin.oidc.op.messaging.context.logic.JWTClaimsSetFromJWTAccessTokenLookupFunction" />
-                </property>
-                <property name="jwtUpdateConsumer">
-                    <bean
-                        class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.JWTAccessTokenUpdateStrategy" />
-                </property>
-            </bean>
-        </constructor-arg>
-    </bean>
-
-    <bean id="EncryptAccessToken" class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor"
-            scope="prototype" c:executionDirection="OUTBOUND">
-        <constructor-arg name="messageHandler">
-            <bean id="EncryptAccessTokenHandler"
-                class="net.shibboleth.oidc.security.impl.EncryptJWTHandler" scope="prototype"
-                p:securityParametersLookupStrategy-ref="AudienceSecurityParametersCreationViaMessageContextStrategy"
-                p:logName="Access Token">
-                <property name="payloadToEncryptLookupStrategy">
-                    <bean
-                        class="net.shibboleth.idp.plugin.oidc.op.messaging.context.logic.PayloadFromJWTAccessTokenLookupFunction" />
-                </property>
-                <property name="jwtUpdateConsumer">
-                    <bean
-                        class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.JWTAccessTokenUpdateStrategy" />
-                </property>
-            </bean>
-        </constructor-arg>
-        <property name="activationCondition">
-            <bean parent="shibboleth.Conditions.NOT" c:_0-ref="BuildOIDCTokensCondition" />
-        </property>
-    </bean>
-
-    <bean id="PostResponsePopulateAuditContext" parent="shibboleth.AbstractPopulateAuditContext"
-        p:fieldExtractors="#{getObject('shibboleth.oidc.TokenPostResponseAuditExtractors') ?: getObject('shibboleth.oidc.DefaultTokenPostResponseAuditExtractors')}" />
-
-    <!-- ADDED. Actions and condition of the pre-authorized code grant. -->
-
-    <bean id="OpenIDVCIValidateTxCode" class="org.geant.shibboleth.plugin.openidvci.profile.impl.ValidateTxCode"
-        scope="prototype"/>
+        p:responseClaimsSetLookupStrategy-ref="OpenIDVCIPotentialCredentialsLookupStrategy"
+        p:transcoderRegistry-ref="shibboleth.AttributeRegistryService" />
 
-    <bean id="OpenIDVCIValidatePreAuthorizedCode" class="org.geant.shibboleth.plugin.openidvci.profile.impl.ValidatePreAuthorizedCode"
-        scope="prototype" p:replayCache-ref="shibboleth.ReplayCache"/>
+    <bean id="OpenIDVCIAddTokenDeliveryAttributesToPotentialCredentials"
+        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.AddTokenDeliveryAttributesToClaimsSet" scope="prototype"
+        p:responseClaimsSetLookupStrategy-ref="OpenIDVCIPotentialCredentialsLookupStrategy" />
 
-    <bean id="OpenIDVCISetAuthorizationDetailsToResponseContext" class="org.geant.shibboleth.plugin.openidvci.profile.impl.SetAuthorizationDetailsToResponseContext"
-        scope="prototype"/>
+    <bean id="OpenIDVCIPotentialCredentialsLookupStrategy"
+        class="org.geant.shibboleth.plugin.openidvci.messaging.context.navigate.PotentialCredentialsLookupFunction" />
 
     <bean id="OpenIDVCIFilterPotentialCredentials"
         class="org.geant.shibboleth.plugin.openidvci.profile.impl.FilterPotentialCredentials" scope="prototype"
         p:credentialConfigurationsResolver-ref="openidvci.CredentialConfigurationsResolver" />
 
-    <bean id="OpenIDVCIConsumeCredentialOffer" class="org.geant.shibboleth.plugin.openidvci.profile.impl.ConsumeCredentialOffer"
-        scope="prototype" p:credentialOfferCache-ref="openidvci.CredentialOfferCache" />
-
-    <bean id="OpenIDVCISetDPoPProofThumbprintToResponseContext"
-        class="org.geant.shibboleth.plugin.openidvci.profile.impl.SetDPoPProofThumbprintToResponseContext"
-        scope="prototype" />
+    <bean id="OpenIDVCISetAuthorizationDetailsToResponseContext"
+        class="org.geant.shibboleth.plugin.openidvci.profile.impl.SetAuthorizationDetailsToResponseContext"
+        scope="prototype"/>
 
     <bean id="OpenIDVCISetCredentialsAudienceToResponseContext"
         class="org.geant.shibboleth.plugin.openidvci.profile.impl.SetCredentialsAudienceToResponseContext"
-        scope="prototype" />
+        scope="prototype"
+        p:audienceLookupStrategy-ref="OpenIDVCICredentialIssuerIdentifierLookup" />
 
-    <!-- REPLACED InitializeAuthenticationContext of oidc/abstract-api. -->
-    <bean id="InitializeAuthenticationContext" class="org.geant.shibboleth.plugin.openidvci.profile.impl.InitializeAuthenticationContext"
-        scope="prototype" />
+    <bean id="OpenIDVCICredentialIssuerIdentifierLookup"
+        class="org.geant.shibboleth.plugin.openidvci.metadata.impl.CredentialIssuerIdentifierLookupFunction"
+        c:resolver-ref="#{'%{openidvci.issuerMetadata.resolver:openidvci.DefaultCredentialIssuerMetadataResolver}'.trim()}" />
 
-    <!-- ADDED. Error map of this plugin, extending the map of the OP. -->
+    <bean id="OpenIDVCICredentialIssuerAudienceCondition" parent="shibboleth.Conditions.Expression"
+        c:expression="#input.ensureOutboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)).getAudience().isEmpty() or (#custom.apply(#input) != null and T(java.util.List).of(#custom.apply(#input)).containsAll(#input.ensureOutboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)).getAudience()))"
+        p:customObject-ref="OpenIDVCICredentialIssuerIdentifierLookup" />
+
+    <!-- Error map of this plugin, extending the map of the OP. -->
     <bean id="openidvci.token.MappedErrors" parent="shibboleth.oidc.DefaultApiMappedErrors"
             class="org.springframework.beans.factory.config.MapFactoryBean">
         <property name="sourceMap">
@@ -893,9 +217,4 @@
         </property>
     </bean>
 
-    <!-- Condition signaling that the request was for the pre-authorized code grant. -->
-    <bean id="OpenIDVCIPreAuthorizedCodeGrantCondition"
-        class="net.shibboleth.idp.plugin.oidc.op.messaging.context.logic.RequestedGrantTypesCondition"
-        p:grantTypes="#{T(org.geant.shibboleth.plugin.openidvci.messaging.impl.PreAuthorizedCodeGrant).GRANT_TYPE}" />
-
 </beans>
diff --git a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/token/token-flow.xml b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/token/token-flow.xml
index b178bfb..4abedbe 100644
--- a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/token/token-flow.xml
+++ b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/token/token-flow.xml
@@ -7,29 +7,44 @@
     Idea is to merge OP token and this flow to one at some point in future.
 
     ADDED
-      OpenIDVCIPreAuthorizedCodeGrantCondition     selects the pre-authorized code grant
-      OpenIDVCIValidateTxCode                      transaction code of a pre-authorized offer
+      OpenIDVCIUnwrapPreAuthorizedCode             unwraps or locates the pre-authorized code
       OpenIDVCIValidatePreAuthorizedCode           one time use of a pre-authorized code
-      OpenIDVCISetAuthorizationDetailsToResponseContext   credential authorization details
       OpenIDVCIConsumeCredentialOffer              retires a stored offer before the transaction code is read
+      OpenIDVCIValidateTxCode                      transaction code of a pre-authorized offer
       OpenIDVCISetDPoPProofThumbprintToResponseContext   DPoP key the pre-authorized access token is bound to
-      OpenIDVCISetCredentialsAudienceToResponseContext   audience of the Credential endpoint in the access token of both grants
-      OpenIDVCIFilterPotentialCredentials          credentials of the code grant limited to the granted scope and authorization_details
-      OpenIDVCIBranchOnPreAuthorizedCodeGrant state   takes that grant aside
+      OpenIDVCIInitializePotentialCredentials      empty credentials of a grant other than the pre-authorized code
+      OpenIDVCIAddAttributeClaimsToPotentialCredentials   resolved attributes of the grant as credentials
+      OpenIDVCIAddTokenDeliveryAttributesToPotentialCredentials   token delivery claims of the grant as credentials
+      OpenIDVCIFilterPotentialCredentials          credentials limited to the granted scope and authorization_details
+      OpenIDVCISetAuthorizationDetailsToResponseContext   credential authorization details
+      OpenIDVCISetCredentialsAudienceToResponseContext   Credential Issuer Identifier as audience in the access token
+                                                   of a grant with credentials, in BuildTokensForUserInfoAccess,
+                                                   BuildTokensForThirdPartyAccess and the pre-authorized branch
+      OpenIDVCIBranchOnPreAuthorizedCodeGrant state   takes the pre-authorized code grant aside
       OpenIDVCIPreAuthzGrantProcessing state       the pre-authorized code grant branch
-      OpenIDVCIBuildTokensForCredentialsAccess state  the opaque access token of both grants
+      OpenIDVCIBuildTokensForCredentialsAccess state  the opaque access token of the pre-authorized code grant
+      OpenIDVCIBuildPotentialCredentials state     the credentials of a grant other than client_credentials
+      OpenIDVCIBuildResponse state                 the decision of BuildResponse, the Credential Issuer Identifier
+                                                   as audience counted as empty
 
     REPLACED
       the parent of the flow   openid/vci/abstract-api in place of oidc/abstract-api
-      the bean import          the classpath form the other flows of this plugin use
-      UnwrapGrant              -> OpenIDVCIUnwrapGrant     also reads the credential offer cache
-
-    REPLACED, KEEPING THE ORIGINAL BEAN ID
-      DecodeMessage, FormOutboundMessage, InitializeAuthenticationContext
-          Named by states of openid/vci/abstract-api. Marked in token-beans.xml.
-
-    REMOVED
-      the transition from TraditionalGrantProcessing to CheckTraditionalGrantForAudience
+      the bean import          token-beans.xml of this flow, importing the beans of oidc/abstract-api
+                               and oidc/token
+      UnwrapGrant              -> OpenIDVCIUnwrapGrant     client id lookup of this plugin, not for the
+                                                           pre-authorized code
+      ValidateGrant            -> OpenIDVCIValidateGrant   in TraditionalGrantProcessing, client instance key
+                                                           condition added to the token revocation condition
+      ValidatePKCE             -> OpenIDVCIValidatePKCE    code verifier lookup of this plugin
+      BuildOIDCAccessToken     -> OpenIDVCIBuildOIDCAccessToken   client id lookup of this plugin
+      the targets of BuildResponse   OpenIDVCIBuildPotentialCredentials, and OpenIDVCIBuildResponse for
+                                     client_credentials, in place of BuildTokensForUserInfoAccess and
+                                     BuildTokensForThirdPartyAccess
+
+    BEANS NAMED BY THE PARENT FLOWS
+      OpenIDVCIDecodeMessage, OpenIDVCIFormOutboundMessage, OpenIDVCIBuildErrorResponseFromEvent and
+      InitializeAuthenticationContext of openid/vci/abstract-api; shibboleth.ClientIDLookupStrategy of
+      oidc/metadata-lookup, replaced keeping the bean id. In token-beans.xml.
 
 -->
 <flow xmlns="http://www.springframework.org/schema/webflow" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@@ -51,6 +66,8 @@
     <action-state id="PostDecodeMessage">
         <!-- REPLACED UnwrapGrant. -->
         <evaluate expression="OpenIDVCIUnwrapGrant" />
+        <!-- ADDED. -->
+        <evaluate expression="OpenIDVCIUnwrapPreAuthorizedCode" />
         <evaluate expression="'proceed'" />
         
         <!-- DoMetadataLookup is expected to proceed to SelectConfiguration -->
@@ -92,8 +109,10 @@
 
     <!-- These steps apply to grants that rely on the authorization endpoint to "prime" the token request. -->
     <action-state id="TraditionalGrantProcessing">
-        <evaluate expression="ValidateGrant" />
-        <evaluate expression="ValidatePKCE" />
+        <!-- REPLACED ValidateGrant. -->
+        <evaluate expression="OpenIDVCIValidateGrant" />
+        <!-- REPLACED ValidatePKCE. -->
+        <evaluate expression="OpenIDVCIValidatePKCE" />
         <evaluate expression="ValidateRedirectURI" />
         <evaluate expression="SetRequestedClaimsToResponseContext" />
         <evaluate expression="SetAuthenticationContextClassReferenceFromAuthzCodeToResponseContext" />
@@ -106,12 +125,9 @@
         <evaluate expression="ValidateScope" />
         <evaluate expression="ValidateAudience" />
         <evaluate expression="SetSessionIdFromAuthzCodeToResponseContext" />
-        <!-- ADDED. -->
-        <evaluate expression="OpenIDVCIFilterPotentialCredentials" />
         <evaluate expression="'proceed'" />
-        
-        <!-- REPLACED the transition to CheckTraditionalGrantForAudience. -->
-        <transition on="proceed" to="OpenIDVCIBuildTokensForCredentialsAccess" />
+
+        <transition on="proceed" to="CheckTraditionalGrantForAudience" />
     </action-state>
 
     <!-- For standard grants, audience may or may not be a factor. -->
@@ -136,7 +152,7 @@
     <action-state id="OpenIDVCIBuildTokensForCredentialsAccess">
         <evaluate expression="OpenIDVCISetAuthorizationDetailsToResponseContext" />
         <evaluate expression="OpenIDVCISetCredentialsAudienceToResponseContext" />
-        <evaluate expression="BuildOIDCAccessToken" />
+        <evaluate expression="OpenIDVCIBuildOIDCAccessToken" />
         <evaluate expression="SetOAuthAccessTokenToResponseContext" />
         <evaluate expression="'proceed'" />
 
@@ -214,9 +230,21 @@
         <evaluate expression="ResolveAttributes" />
         <evaluate expression="FilterAttributes" />
         <evaluate expression="'proceed'" />
-        
+
         <transition on="proceed" to="DoConsentLookup" />
     </action-state>
+
+    <!-- ADDED. Credentials of the grant from the resolved attributes and the token delivery claims. -->
+    <action-state id="OpenIDVCIBuildPotentialCredentials">
+        <evaluate expression="OpenIDVCIInitializePotentialCredentials" />
+        <evaluate expression="OpenIDVCIAddAttributeClaimsToPotentialCredentials" />
+        <evaluate expression="OpenIDVCIAddTokenDeliveryAttributesToPotentialCredentials" />
+        <evaluate expression="OpenIDVCIFilterPotentialCredentials" />
+        <evaluate expression="OpenIDVCISetAuthorizationDetailsToResponseContext" />
+        <evaluate expression="'proceed'" />
+
+        <transition on="proceed" to="OpenIDVCIBuildResponse" />
+    </action-state>
     
     <decision-state id="CheckAttributeResolutionForAudience">
         <if test="ResolveAttributesForAudiencePredicate.test(opensamlProfileRequestContext)"
@@ -232,8 +260,16 @@
         <transition on="proceed" to="DoConsentLookup" />
     </action-state>
     
+    <!-- REPLACED the targets BuildTokensForUserInfoAccess and BuildTokensForThirdPartyAccess. -->
     <decision-state id="BuildResponse">
-        <if test="opensamlProfileRequestContext.ensureOutboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)).getAudience().isEmpty()"
+        <if test="NotClientCredentialsGrantCondition.test(opensamlProfileRequestContext)"
+            then="OpenIDVCIBuildPotentialCredentials"
+            else="OpenIDVCIBuildResponse" />
+    </decision-state>
+
+    <!-- ADDED. The decision of BuildResponse, the Credential Issuer Identifier as audience counted as empty. -->
+    <decision-state id="OpenIDVCIBuildResponse">
+        <if test="OpenIDVCICredentialIssuerAudienceCondition.test(opensamlProfileRequestContext)"
             then="BuildTokensForUserInfoAccess"
             else="BuildTokensForThirdPartyAccess" />
     </decision-state>
@@ -249,7 +285,10 @@
     -->
     <action-state id="BuildTokensForUserInfoAccess">
         <evaluate expression="PopulateUserInfoAccessTokenSignatureSigningParameters" />
-        <evaluate expression="BuildOIDCAccessToken" />
+        <!-- ADDED. -->
+        <evaluate expression="OpenIDVCISetCredentialsAudienceToResponseContext" />
+        <!-- REPLACED BuildOIDCAccessToken. -->
+        <evaluate expression="OpenIDVCIBuildOIDCAccessToken" />
         <evaluate expression="SignOIDCAccessToken" />
         <evaluate expression="SetOAuthAccessTokenToResponseContext" />
         <evaluate expression="SetRefreshTokenToResponseContext" />
@@ -270,6 +309,8 @@
         <evaluate expression="PopulateThirdPartyAccessTokenSignatureSigningParameters" />
         <evaluate expression="PopulateThirdPartyAccessTokenEncryptionParameters" />
         <evaluate expression="AddAttributeClaimsToAccessToken" />
+        <!-- ADDED. -->
+        <evaluate expression="OpenIDVCISetCredentialsAudienceToResponseContext" />
         <evaluate expression="BuildAccessToken" />
         <evaluate expression="SignAccessToken" />
         <evaluate expression="EncryptAccessToken" />
diff --git a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
index 6bc3ab2..a4873ac 100644
--- a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
+++ b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
@@ -41,7 +41,36 @@
     <bean id="OpenID.VCI.CredentialOffer" parent="AbstractVCIProfile" lazy-init="true"
         class="org.geant.shibboleth.plugin.openidvci.profile.config.impl.DefaultOpenIDVCICredentialOfferConfiguration" />
 
-    <bean id="OpenID.VCI.Token" parent="AbstractVCIProfile" lazy-init="true"
+    <!-- Settings of AbstractOIDCSSOProfile and OAUTH2.Token of the OP plugin, with the DPoP claims validator of this plugin. -->
+    <bean id="AbstractVCITokenProfile" abstract="true"
+        p:securityConfiguration-ref="%{idp.security.oidc.config:shibboleth.oidc.DefaultSecurityConfiguration}"
+        p:issuer="#{getObject('shibboleth.oidc.issuer')}"
+        p:tokenEndpointAuthMethods="%{idp.oidc.tokenEndpointAuthMethods:client_secret_basic,client_secret_post,client_secret_jwt,private_key_jwt}"
+        p:claimsValidator="#{getObject('DefaultJWTClaimsValidator')}"
+        p:encryptionOptional="%{idp.oidc.encryptionOptional:true}"
+        p:forcePKCE="%{idp.oidc.forcePKCE:false}"
+        p:allowPKCEPlain="%{idp.oidc.allowPKCEPlain:false}"
+        p:iDTokenLifetime="%{idp.oidc.idToken.defaultLifetime:PT1H}"
+        p:accessTokenType="%{idp.oauth2.accessToken.type:}"
+        p:accessTokenLifetime="%{idp.oidc.accessToken.defaultLifetime:PT10M}"
+        p:refreshTokenType="%{idp.oauth2.refreshToken.type:}"
+        p:refreshTokenTimeout="%{idp.oidc.refreshToken.defaultTimeout:PT2H}"
+        p:refreshTokenChainLifetime="%{idp.oidc.refreshToken.defaultChainLifetime:PT2H}"
+        p:alwaysIncludedAttributes="%{idp.oidc.alwaysIncludedAttributes:}"
+        p:tokenEndpointAuthMethod="%{idp.authn.oidc.rp.client.authenticationMethod:client_secret_basic}"
+        p:useTargetedEndpointAsJWTAudience="%{idp.oauth2.jwtAuth.targetedEndpointAsJWTAudience:true}"
+        p:requireSingleJWTAudience="%{idp.oauth2.jwtAuth.requireSingleJWTAudience:false}"
+        p:clientId="#{%{idp.authn.oidc.rp.provider.discoveryRequired:false} == true ? null : '%{idp.authn.oidc.rp.client.clientId:}'}"
+        p:clientCredential="#{%{idp.authn.oidc.rp.provider.discoveryRequired:false} == true ? {null} : getObject('shibboleth.authn.oidc.rp.DefaultCredential')}"
+        p:unregisteredClientPolicy="#{getObject('shibboleth.oidc.DefaultUnregisteredClientPolicy')}"
+        p:dpopProofClaimsValidator="#{getObject('openidvci.DefaultDPoPProofClaimsValidator')}"
+        p:dpopProofSignatureValidationConfiguration="#{getObject('DPoPSignatureValidationConfiguration')}"
+        p:dpopProofNonceGenerator="#{getObject('DefaultOAuth2DPoPNonceGenerator')}"
+        p:strictScopeValidation="%{idp.oidc.strictScopeValidation:false}"
+        p:enforceRefreshTokenRotation="%{idp.oauth2.enforceRefreshTokenRotation:false}"
+        p:issueIdTokenViaRefreshToken="%{idp.oidc.issueIdTokenViaRefreshToken:true}" />
+
+    <bean id="OpenID.VCI.Token" parent="AbstractVCITokenProfile" lazy-init="true"
         class="org.geant.shibboleth.plugin.openidvci.profile.config.impl.DefaultOpenIDVCITokenConfiguration" />
 
     <bean id="OpenID.VCI.Nonce" parent="AbstractVCIProfile" lazy-init="true"
@@ -187,7 +216,9 @@
     <bean id="openidvci.CredentialsAudienceClaimsValidator"
         class="net.shibboleth.oidc.security.jwt.claims.impl.AudienceClaimsValidator">
         <property name="audienceLookupStrategy">
-            <bean parent="shibboleth.BiFunctions.Expression" c:expression="T(org.geant.shibboleth.plugin.openidvci.profile.impl.SetCredentialsAudienceToResponseContext).AUDIENCE" />
+            <bean parent="shibboleth.BiFunctions.Expression"
+                c:expression="#custom.apply(#input1)"
+                p:customObject-ref="openidvci.CredentialIssuerIdentifierLookup" />
         </property>
     </bean>
 
diff --git a/openid-vci-impl/src/main/resources/org/geant/shibboleth/plugin/openidvci/conf/openid-vci.properties b/openid-vci-impl/src/main/resources/org/geant/shibboleth/plugin/openidvci/conf/openid-vci.properties
index 1e3d64f..c85bcb7 100644
--- a/openid-vci-impl/src/main/resources/org/geant/shibboleth/plugin/openidvci/conf/openid-vci.properties
+++ b/openid-vci-impl/src/main/resources/org/geant/shibboleth/plugin/openidvci/conf/openid-vci.properties
@@ -25,6 +25,10 @@ openidvci.issuer =
 # Lifetime of the c_nonce of the Nonce endpoint
 #openidvci.nonce.lifetime = PT5M
 
+# Bean name of a custom parser of the Token endpoint request, a CustomNimbusRequestParser of the OP
+# plugin returning an OpenIDVCITokenRequest
+#openidvci.requestParser.TokenRequest =
+
 # The location for the JSON file of Credential Configurations, published in Credential Issuer
 # metadata and used as the instruction on how a credential is formed. Every flow reads them
 # from here, through one resolver, so the file is read when it changes and not once per request.
diff --git a/openid-vci-impl/src/main/resources/org/geant/shibboleth/plugin/openidvci/static/oauth-authorization-server.json b/openid-vci-impl/src/main/resources/org/geant/shibboleth/plugin/openidvci/static/oauth-authorization-server.json
index d396c3f..5743a48 100644
--- a/openid-vci-impl/src/main/resources/org/geant/shibboleth/plugin/openidvci/static/oauth-authorization-server.json
+++ b/openid-vci-impl/src/main/resources/org/geant/shibboleth/plugin/openidvci/static/oauth-authorization-server.json
@@ -13,6 +13,7 @@
    ],
    "grant_types_supported":[
       "authorization_code",
+      "refresh_token",
       "urn:ietf:params:oauth:grant-type:pre-authorized_code"
    ],
    "code_challenge_methods_supported":[
diff --git a/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/decoding/impl/OpenIDVCITokenRequestDecoderTest.java b/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/decoding/impl/OpenIDVCITokenRequestDecoderTest.java
index 8825a1a..a0dfbb7 100644
--- a/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/decoding/impl/OpenIDVCITokenRequestDecoderTest.java
+++ b/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/decoding/impl/OpenIDVCITokenRequestDecoderTest.java
@@ -24,6 +24,8 @@ import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import com.nimbusds.oauth2.sdk.RefreshTokenGrant;
+
 import jakarta.servlet.http.HttpServletRequest;
 import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.primitive.NonnullSupplier;
@@ -86,4 +88,28 @@ public class OpenIDVCITokenRequestDecoderTest {
                 .getClientID().getValue(), "test");
     }
     
-}
\ No newline at end of file
+    @Test
+    public void testRefreshTokenGrant() throws MessageDecodingException {
+        httpRequest.removeParameter("pre-authorized_code");
+        httpRequest.setParameter("grant_type", "refresh_token");
+        httpRequest.addParameter("refresh_token", "abc");
+        decoder.decode();
+        final OpenIDVCITokenRequest request = (OpenIDVCITokenRequest) decoder.getMessageContext().getMessage();
+        Assert.assertTrue(request.getAuthorizationGrant() instanceof RefreshTokenGrant);
+        Assert.assertEquals(request.getClientID().getValue(), "123456");
+    }
+
+    @Test
+    public void testCodeGrantScopeRemovedAndResourcePrefixed() throws MessageDecodingException {
+        httpRequest.removeParameter("pre-authorized_code");
+        httpRequest.setParameter("grant_type", "authorization_code");
+        httpRequest.addParameter("code", "abc");
+        httpRequest.addParameter("scope", "openid");
+        httpRequest.addParameter("resource", "api");
+        decoder.decode();
+        final OpenIDVCITokenRequest request = (OpenIDVCITokenRequest) decoder.getMessageContext().getMessage();
+        Assert.assertNull(request.getScope());
+        Assert.assertEquals(request.getResources().get(0).toString(), "urn:shibboleth.oidc.op.resource:api");
+    }
+
+}
diff --git a/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/messaging/impl/OpenIDVCITokenRequestTest.java b/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/messaging/impl/OpenIDVCITokenRequestTest.java
index 18f3d8e..5e6bc57 100644
--- a/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/messaging/impl/OpenIDVCITokenRequestTest.java
+++ b/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/messaging/impl/OpenIDVCITokenRequestTest.java
@@ -24,10 +24,13 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
+import com.nimbusds.jose.util.Base64URL;
 import com.nimbusds.common.contenttype.ContentType;
 import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
+import com.nimbusds.oauth2.sdk.ClientCredentialsGrant;
 import com.nimbusds.oauth2.sdk.GrantType;
 import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.RefreshTokenGrant;
 import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
 import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
 import com.nimbusds.oauth2.sdk.auth.Secret;
@@ -123,4 +126,62 @@ public class OpenIDVCITokenRequestTest {
         Assert.assertEquals(message.getAuthorizationGrant().getType(), GrantType.AUTHORIZATION_CODE);
     }
 
-}
\ No newline at end of file
+    @Test
+    public void parseCodeGrantScopeAndResource() throws ParseException, URISyntaxException {
+        httpRequest = new HTTPRequest(HTTPRequest.Method.POST, new URI("http://example.com"));
+        httpRequest.setContentType(ContentType.APPLICATION_URLENCODED.toString());
+        httpRequest.setBody("client_id=clientID&grant_type=authorization_code&code=123456"
+                + "&scope=openid%20profile&resource=https%3A%2F%2Fexample.org%2Fapi");
+        message = OpenIDVCITokenRequest.parse(httpRequest);
+        Assert.assertEquals(message.getScope().toString(), "openid profile");
+        Assert.assertEquals(message.getResources().get(0).toString(), "https://example.org/api");
+    }
+
+    @Test(expectedExceptions = ParseException.class)
+    public void parseRelativeResource() throws ParseException, URISyntaxException {
+        httpRequest = new HTTPRequest(HTTPRequest.Method.POST, new URI("http://example.com"));
+        httpRequest.setContentType(ContentType.APPLICATION_URLENCODED.toString());
+        httpRequest.setBody("client_id=clientID&grant_type=authorization_code&code=123456&resource=api");
+        OpenIDVCITokenRequest.parse(httpRequest);
+    }
+
+    @Test
+    public void parseRefreshTokenGrant() throws ParseException, URISyntaxException {
+        httpRequest = new HTTPRequest(HTTPRequest.Method.POST, new URI("http://example.com"));
+        httpRequest.setContentType(ContentType.APPLICATION_URLENCODED.toString());
+        httpRequest.setBody("client_id=clientID&grant_type=refresh_token&refresh_token=abc&scope=openid");
+        message = OpenIDVCITokenRequest.parse(httpRequest);
+        Assert.assertTrue(message.getAuthorizationGrant() instanceof RefreshTokenGrant);
+        Assert.assertEquals(message.getGrantType(), GrantType.REFRESH_TOKEN.getValue());
+        Assert.assertEquals(message.getClientID().getValue(), "clientID");
+        Assert.assertEquals(message.getScope().toString(), "openid");
+    }
+
+    @Test
+    public void parseRefreshTokenGrantWithClientAttestation() throws ParseException, URISyntaxException {
+        httpRequest = new HTTPRequest(HTTPRequest.Method.POST, new URI("http://example.com"));
+        httpRequest.setContentType(ContentType.APPLICATION_URLENCODED.toString());
+        httpRequest.setHeader("OAuth-Client-Attestation",
+                Base64URL.encode("{\"alg\":\"ES256\",\"typ\":\"oauth-client-attestation+jwt\"}") + "."
+                        + Base64URL.encode("{\"sub\":\"attestedClient\"}") + "." + Base64URL.encode("signature"));
+        httpRequest.setBody("grant_type=refresh_token&refresh_token=abc");
+        message = OpenIDVCITokenRequest.parse(httpRequest);
+        Assert.assertTrue(message.getAuthorizationGrant() instanceof RefreshTokenGrant);
+        Assert.assertNull(message.getClientAuthentication());
+        Assert.assertEquals(message.getClientID().getValue(), "attestedClient");
+    }
+
+    @Test
+    public void parseClientCredentialsGrant() throws ParseException, URISyntaxException {
+        httpRequest = new HTTPRequest(HTTPRequest.Method.POST, new URI("http://example.com"));
+        httpRequest.setContentType(ContentType.APPLICATION_URLENCODED.toString());
+        httpRequest.setAuthorization(new ClientSecretBasic(new ClientID("clientID"), new Secret("secret"))
+                .toHTTPAuthorizationHeader());
+        httpRequest.setBody("grant_type=client_credentials&scope=read");
+        message = OpenIDVCITokenRequest.parse(httpRequest);
+        Assert.assertTrue(message.getAuthorizationGrant() instanceof ClientCredentialsGrant);
+        Assert.assertEquals(message.getClientAuthentication().getClientID().getValue(), "clientID");
+        Assert.assertEquals(message.getScope().toString(), "read");
+    }
+
+}
diff --git a/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/config/impl/DefaultOpenIDVCITokenConfigurationTest.java b/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/config/impl/DefaultOpenIDVCITokenConfigurationTest.java
index 1acdfea..90b6ed4 100644
--- a/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/config/impl/DefaultOpenIDVCITokenConfigurationTest.java
+++ b/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/config/impl/DefaultOpenIDVCITokenConfigurationTest.java
@@ -25,6 +25,7 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenConfiguration;
+import net.shibboleth.oidc.profile.oauth2.config.impl.DefaultOAuth2TokenConfiguration;
 
 /** Tests for {@link DefaultOpenIDVCITokenConfiguration}. */
 public class DefaultOpenIDVCITokenConfigurationTest {
@@ -41,10 +42,15 @@ public class DefaultOpenIDVCITokenConfigurationTest {
         Assert.assertTrue(configuration instanceof OAuth2TokenConfiguration);
     }
 
+    @Test
+    public void testIsOAuth2TokenConfigurationOfOP() {
+        Assert.assertTrue(configuration instanceof DefaultOAuth2TokenConfiguration);
+    }
+
     @Test
     public void testDefaultGrantTypes() {
         Assert.assertEquals(configuration.getGrantTypes(null),
-                Set.of("authorization_code", OpenIDVCITokenRequest.GRANT_TYPE_VALUE_PRE_AUTH));
+                Set.of("authorization_code", "refresh_token", OpenIDVCITokenRequest.GRANT_TYPE_VALUE_PRE_AUTH));
     }
 
     @Test
@@ -60,15 +66,11 @@ public class DefaultOpenIDVCITokenConfigurationTest {
     }
 
     @Test
-    public void testRefreshTokensDisabled() {
-        Assert.assertFalse(configuration.isRefreshTokensEnabled(null));
-    }
-
-    @Test
-    public void testFixedRefreshTokenAndAudienceSettings() {
+    public void testRefreshTokenDefaultsOfOP() {
+        Assert.assertTrue(configuration.isRefreshTokensEnabled(null));
         Assert.assertFalse(configuration.isEnforceRefreshTokenRotation(null));
         Assert.assertNull(configuration.getRefreshTokenClaimsSetManipulationStrategy(null));
-        Assert.assertFalse(configuration.isIssueIdTokenViaRefreshToken(null));
+        Assert.assertTrue(configuration.isIssueIdTokenViaRefreshToken(null));
         Assert.assertFalse(configuration.isLimitInitialAccessTokenToSelf(null));
     }
 
diff --git a/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/impl/SetCredentialsAudienceToResponseContextTest.java b/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/impl/SetCredentialsAudienceToResponseContextTest.java
index 9d72d12..67ebef0 100644
--- a/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/impl/SetCredentialsAudienceToResponseContextTest.java
+++ b/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/impl/SetCredentialsAudienceToResponseContextTest.java
@@ -19,6 +19,7 @@ package org.geant.shibboleth.plugin.openidvci.profile.impl;
 import java.util.List;
 
 import org.geant.shibboleth.plugin.openidvci.decoding.impl.OpenIDVCITokenRequestDecoder;
+import org.geant.shibboleth.plugin.openidvci.messaging.context.TokenContext;
 import org.opensaml.messaging.context.MessageContext;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.springframework.mock.web.MockHttpServletRequest;
@@ -27,11 +28,15 @@ import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
+
 import jakarta.servlet.http.HttpServletRequest;
 import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
+import net.shibboleth.idp.profile.IdPEventIds;
 import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
 import net.shibboleth.idp.profile.testing.ActionTestingSupport;
 import net.shibboleth.idp.profile.testing.RequestContextBuilder;
+import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.primitive.NonnullSupplier;
 
 /**
@@ -39,10 +44,14 @@ import net.shibboleth.shared.primitive.NonnullSupplier;
  */
 public class SetCredentialsAudienceToResponseContextTest {
 
+    private static final String ISSUER = "https://issuer.example.org";
+
     private RequestContext requestCtx;
 
     private OIDCAuthenticationResponseContext responseCtx;
 
+    private TokenContext tokenCtx;
+
     @BeforeMethod
     protected void setUp() throws Exception {
         final MockHttpServletRequest httpRequest = new MockHttpServletRequest();
@@ -67,25 +76,50 @@ public class SetCredentialsAudienceToResponseContextTest {
         profileRequestCtx.setOutboundMessageContext(new MessageContext());
         responseCtx = profileRequestCtx.getOutboundMessageContext()
                 .ensureSubcontext(OIDCAuthenticationResponseContext.class);
+        tokenCtx = profileRequestCtx.ensureInboundMessageContext().ensureSubcontext(TokenContext.class);
+        final ClaimsSet credentials = new ClaimsSet();
+        credentials.setClaim("ConfigurationId", "[]");
+        tokenCtx.setPotentialCredentials(credentials);
     }
 
-    @Test
-    public void testAdded() throws Exception {
+    private SetCredentialsAudienceToResponseContext action(final String issuer) throws Exception {
         final SetCredentialsAudienceToResponseContext action = new SetCredentialsAudienceToResponseContext();
+        action.setAudienceLookupStrategy(prc -> issuer);
         action.initialize();
-        ActionTestingSupport.assertProceedEvent(action.execute(requestCtx));
-        Assert.assertEquals(responseCtx.getAudience(), List.of(SetCredentialsAudienceToResponseContext.AUDIENCE));
+        return action;
+    }
+
+    @Test
+    public void testAdded() throws Exception {
+        ActionTestingSupport.assertProceedEvent(action(ISSUER).execute(requestCtx));
+        Assert.assertEquals(responseCtx.getAudience(), List.of(ISSUER));
     }
 
     @Test
     public void testNotDuplicated() throws Exception {
-        responseCtx.getAudience().add(SetCredentialsAudienceToResponseContext.AUDIENCE);
+        responseCtx.getAudience().add(ISSUER);
         responseCtx.getAudience().add("other");
-        final SetCredentialsAudienceToResponseContext action = new SetCredentialsAudienceToResponseContext();
-        action.initialize();
+        ActionTestingSupport.assertProceedEvent(action(ISSUER).execute(requestCtx));
+        Assert.assertEquals(responseCtx.getAudience(), List.of(ISSUER, "other"));
+    }
+
+    @Test
+    public void testNoIssuer() throws Exception {
+        ActionTestingSupport.assertEvent(action(null).execute(requestCtx), IdPEventIds.INVALID_PROFILE_CONFIG);
+        Assert.assertTrue(responseCtx.getAudience().isEmpty());
+    }
+
+    @Test(expectedExceptions = ComponentInitializationException.class)
+    public void testNoStrategy() throws Exception {
+        new SetCredentialsAudienceToResponseContext().initialize();
+    }
+
+    @Test
+    public void testNotAddedWithoutCredentials() throws Exception {
+        tokenCtx.setPotentialCredentials(new ClaimsSet());
+        final SetCredentialsAudienceToResponseContext action = action(ISSUER);
         ActionTestingSupport.assertProceedEvent(action.execute(requestCtx));
-        Assert.assertEquals(responseCtx.getAudience(),
-                List.of(SetCredentialsAudienceToResponseContext.AUDIENCE, "other"));
+        Assert.assertTrue(responseCtx.getAudience().isEmpty());
     }
 
 }

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


More information about the commits mailing list