[java-idp-oidc] branch main updated: JOIDC-255 - Make request claims sets available before metadata resolution

Henri Mikkonen henri.mikkonen at iki.fi
Fri Sep 5 11:37:29 UTC 2025


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

hjmikkon pushed a commit to branch main
in repository java-idp-oidc.

View the commit online:
http://git.shibboleth.net/view/?p=java-idp-oidc.git;a=commit;h=85caf336bb42f4c319eba16c6b94934c07f02d36

The following commit(s) were added to refs/heads/main by this push:
     new 85caf336 JOIDC-255 - Make request claims sets available before metadata resolution
85caf336 is described below

commit 85caf336bb42f4c319eba16c6b94934c07f02d36
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Sep 5 14:36:54 2025 +0300

    JOIDC-255 - Make request claims sets available before metadata resolution
    
    https://shibboleth.atlassian.net/browse/JOIDC-255
    
    - Token flow: split ValidateGrant into UnwrapGrant and ValidateGrant
      - UnwrapGrant is evaluated right after message decoding
      - Refactored DefaultJwtRefreshTokenDeserializationFunction to exploit shibboleth.oidc.issuer and shibboleth.ClientIDLookupStrategy
        - The relying party configuration is not yet initialized before metadata resolution
    - Authorize flow: deserialize pushed authorization request right after message decoding
---
 .../OAuth2PushedAuthorizationRequestContext.java   |  60 ++++++
 .../profile/impl/DeserializePushedRequest.java     | 131 +++++++++++++
 .../impl/SetRequestObjectToResponseContext.java    |  50 +++--
 .../plugin/oidc/op/profile/impl/UnwrapGrant.java   | 217 +++++++++++++++++++++
 .../plugin/oidc/op/profile/impl/ValidateGrant.java | 156 ++++-----------
 .../oidc/abstract-api/oidc-abstract-api-beans.xml  |  17 +-
 .../idp/flows/oidc/authorize/authorize-beans.xml   |   7 +-
 .../idp/flows/oidc/authorize/authorize-flow.xml    |   1 +
 .../idp/flows/oidc/token/token-beans.xml           |   6 +-
 .../shibboleth/idp/flows/oidc/token/token-flow.xml |  12 +-
 .../plugin/oidc/op/profile/flow/TokenFlowTest.java |   7 +-
 .../oidc/op/profile/impl/ValidateGrantTest.java    | 158 ++++-----------
 12 files changed, 555 insertions(+), 267 deletions(-)

diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/messaging/context/OAuth2PushedAuthorizationRequestContext.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/messaging/context/OAuth2PushedAuthorizationRequestContext.java
new file mode 100644
index 00000000..7ee0e38f
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/messaging/context/OAuth2PushedAuthorizationRequestContext.java
@@ -0,0 +1,60 @@
+/*
+ * 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 net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.context;
+
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+import org.opensaml.messaging.context.MessageContext;
+
+/**
+ * Subcontext carrying information for a pushed authorization request.
+ * 
+ * <p>This context appears as a subcontext of an inbound {@link MessageContext}.</p>
+ * 
+ * @since 4.4.0
+ */
+public class OAuth2PushedAuthorizationRequestContext extends BaseContext {
+
+    /** The claim set carried within pushed authorization request. */
+    @Nullable private Map<String,Object> claimsSet;
+
+    /**
+     * Get the claims set carried within pushed authorization request.
+     * 
+     * @return claims set
+     */
+    @Nullable public Map<String,Object> getClaimsSet() {
+        return claimsSet;
+    }
+
+    /**
+     * Set the claims set carried within pushed authorization request.
+     * 
+     * @param claims claims set
+     * 
+     * @return this context
+     */
+    @Nonnull public OAuth2PushedAuthorizationRequestContext setClaimsSet(
+            @Nullable final Map<String,Object> claims) {
+        claimsSet = claims;
+        
+        return this;
+    }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/DeserializePushedRequest.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/DeserializePushedRequest.java
new file mode 100644
index 00000000..07efd8aa
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/DeserializePushedRequest.java
@@ -0,0 +1,131 @@
+/*
+ * 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 net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.context.OAuth2PushedAuthorizationRequestContext;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Attempts to deserialize the possibly existing request_uri value from the authorization request via configurable
+ * list of pushed authorization deserializers. If any of the deserializer is successful, the extracted claims set is
+ * stored to {@link OAuth2PushedAuthorizationRequestContext}.
+ * 
+ * @since 4.4.0
+ */
+public class DeserializePushedRequest extends AbstractOAuthAuthorizationRequestAction {
+
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(DeserializePushedRequest.class);
+
+    /** Lookup strategy for pushed authorization request context- */
+    @Nonnull private Function<ProfileRequestContext, OAuth2PushedAuthorizationRequestContext>
+        pushedAuthorizationRequestContextLookupStrategy;
+
+    /** List of deserializers for OP-issued request_uri values. */
+    @Nonnull private List<BiFunction<ProfileRequestContext,URI,Map<String,Object>>>
+        pushedAuthorizationRequestUriDeserializers;
+
+    /** Request URI to be deserialized. */
+    @NonnullBeforeExec private URI requestUri;
+
+    /**
+     * Constructor.
+     */
+    public DeserializePushedRequest() {
+        final Function<ProfileRequestContext, OAuth2PushedAuthorizationRequestContext> parcls =
+                new ChildContextLookup<>(OAuth2PushedAuthorizationRequestContext.class, true).compose(
+                        new InboundMessageContextLookup());
+        assert parcls != null;
+        pushedAuthorizationRequestContextLookupStrategy = parcls;
+
+        pushedAuthorizationRequestUriDeserializers = CollectionSupport.emptyList();
+    }
+
+    /**
+     * Set the lookup strategy for pushed authorization request context.
+     * 
+     * @param strategy the lookup strategy for pushed authorization request context
+     */
+    public void setPushedAuthorizationRequestContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, OAuth2PushedAuthorizationRequestContext> strategy) {
+        checkSetterPreconditions();
+        pushedAuthorizationRequestContextLookupStrategy = Constraint.isNotNull(strategy,
+                "Pushed authorization request context lookup strategy cannot be null");
+    }
+
+    /**
+     * Set the list of deserializers for OP-issued request_uri values.
+     * 
+     * @param deserializers What to set.
+     */
+    public void setPushedAuthorizationRequestUriDeserializers(
+            @Nonnull final List<BiFunction<ProfileRequestContext,URI,Map<String,Object>>> deserializers) {
+        checkSetterPreconditions();
+        pushedAuthorizationRequestUriDeserializers = Constraint.isNotNull(deserializers,
+                "List of request_uri deserializers cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }
+
+        requestUri = Optional.ofNullable(getAuthorizationRequest())
+                .map(request -> request.getRequestURI())
+                .orElse(null);
+
+        if (requestUri == null) {
+            log.debug("{} No request_uri value found, nothing to do", getLogPrefix());
+            return false;
+        }
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        for (final BiFunction<ProfileRequestContext,URI,Map<String,Object>> deserializer :
+            pushedAuthorizationRequestUriDeserializers) {
+            final Map<String,Object> claimsSet = deserializer.apply(profileRequestContext, requestUri);
+            if (claimsSet != null && !claimsSet.isEmpty()) {
+                log.trace("{} Storing claims set {}", getLogPrefix(), claimsSet);
+                pushedAuthorizationRequestContextLookupStrategy.apply(profileRequestContext)
+                    .setClaimsSet(CollectionSupport.copyToMap(claimsSet));
+                log.debug("{} Pushed authorization request successfully deserialized and stored", getLogPrefix());
+                return;
+            }
+        }
+    }
+
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/SetRequestObjectToResponseContext.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/SetRequestObjectToResponseContext.java
index c6153249..9371c3ff 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/SetRequestObjectToResponseContext.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/SetRequestObjectToResponseContext.java
@@ -18,10 +18,10 @@ import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.text.ParseException;
-import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
-import java.util.function.BiFunction;
+import java.util.function.Function;
 import java.util.function.Predicate;
 
 import javax.annotation.Nonnull;
@@ -34,10 +34,12 @@ import org.apache.hc.core5.http.ClassicHttpRequest;
 import org.apache.hc.core5.http.ClassicHttpResponse;
 import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.messaging.encoder.AbstractMessageEncoder;
 import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
 import org.opensaml.security.httpclient.HttpClientSecurityParameters;
 import org.opensaml.security.httpclient.HttpClientSecuritySupport;
 import org.slf4j.Logger;
@@ -53,11 +55,11 @@ import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
 
 import net.shibboleth.idp.plugin.oidc.op.encoding.impl.ResponseUtil;
 import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
+import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.context.OAuth2PushedAuthorizationRequestContext;
 import net.shibboleth.idp.profile.IdPEventIds;
 import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
 import net.shibboleth.oidc.profile.core.OidcEventIds;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
@@ -90,9 +92,9 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
     /** Object mapper used for pretty-printing JWT contents. */
     @NonnullAfterInit private ObjectMapper objectMapper;
 
-    /** List of deserializers for OP-issued request_uri values. */
-    @Nonnull private List<BiFunction<ProfileRequestContext,URI,Map<String,Object>>>
-        pushedAuthorizationRequestUriDeserializers;
+    /** Lookup strategy for pushed authorization request context. */
+    @Nonnull private Function<ProfileRequestContext, OAuth2PushedAuthorizationRequestContext>
+        pushedAuthorizationRequestContextLookupStrategy;
 
     /** Whether to require pushed authorization request to be used. */
     private boolean requirePushedAuthorization = false;
@@ -101,7 +103,11 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
      * Constructor.
      */
     public SetRequestObjectToResponseContext() {
-        pushedAuthorizationRequestUriDeserializers = CollectionSupport.emptyList();
+        final Function<ProfileRequestContext, OAuth2PushedAuthorizationRequestContext> parcls =
+                new ChildContextLookup<>(OAuth2PushedAuthorizationRequestContext.class, true).compose(
+                        new InboundMessageContextLookup());
+        assert parcls != null;
+        pushedAuthorizationRequestContextLookupStrategy = parcls;
     }
 
     /**
@@ -111,6 +117,7 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
      *            client to use
      */
     public void setHttpClient(@Nonnull final HttpClient client) {
+        checkSetterPreconditions();
         httpClient = Constraint.isNotNull(client, "HttpClient cannot be null");
     }
 
@@ -121,6 +128,7 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
      *            the new client security parameters
      */
     public void setHttpClientSecurityParameters(@Nullable final HttpClientSecurityParameters params) {
+        checkSetterPreconditions();
         httpClientSecurityParameters = params;
     }
 
@@ -130,6 +138,7 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
      * @param predicate the predicate for enforcing the use of request objects
      */
     public void setRequestObjectEnforcedPredicate(@Nonnull final Predicate<ProfileRequestContext> predicate) {
+        checkSetterPreconditions();
         requestObjectEnforcedPredicate = Constraint.isNotNull(predicate,
                 "Request object enforced predicate annot be null");
     }
@@ -143,6 +152,7 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
      */
     public void setPushedAuthorizationRequestEnforcedPredicate(
             @Nonnull final Predicate<ProfileRequestContext> predicate) {
+        checkSetterPreconditions();
         pushedAuthorizationRequestEnforcedPredicate = Constraint.isNotNull(predicate,
                 "Pushed authorization request enforced predicate annot be null");
     }
@@ -160,17 +170,17 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
     }
 
     /**
-     * Set the list of deserializers for OP-issued request_uri values.
+     * Set the lookup strategy for pushed authorization request context.
      * 
-     * @param deserializers What to set.
+     * @param strategy the lookup strategy for pushed authorization request context
      * 
-     * @since 4.2.0
+     * @since 4.4.0
      */
-    public void setPushedAuthorizationRequestUriDeserializers(
-            @Nonnull final List<BiFunction<ProfileRequestContext,URI,Map<String,Object>>> deserializers) {
+    public void setPushedAuthorizationRequestContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, OAuth2PushedAuthorizationRequestContext> strategy) {
         checkSetterPreconditions();
-        pushedAuthorizationRequestUriDeserializers = Constraint.isNotNull(deserializers,
-                "List of request_uri deserializers cannot be null");
+        pushedAuthorizationRequestContextLookupStrategy = Constraint.isNotNull(strategy,
+                "Pushed authorization request context lookup strategy cannot be null");
     }
 
     /**
@@ -286,13 +296,11 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
         }
         
         if (!authorized) {
-            for (final BiFunction<ProfileRequestContext,URI,Map<String,Object>> deserializer :
-                pushedAuthorizationRequestUriDeserializers) {
-                final Map<String,Object> claimsSet = deserializer.apply(profileRequestContext,
-                        authorizationRequest.getRequestURI());
-                if (claimsSet == null) {
-                    continue;
-                }
+            final Map<String,Object> claimsSet =
+                    Optional.ofNullable(pushedAuthorizationRequestContextLookupStrategy.apply(profileRequestContext))
+                        .map(ctx -> ctx.getClaimsSet())
+                        .orElse(null);
+            if (claimsSet != null && !claimsSet.isEmpty()) {
                 try {
                     final JWTClaimsSet jwtClaimsSet = JWTClaimsSet.parse(claimsSet);
                     oidcResponseContext.setRequestObject(new PlainJWT(jwtClaimsSet));
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/UnwrapGrant.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/UnwrapGrant.java
new file mode 100644
index 00000000..7a886161
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/UnwrapGrant.java
@@ -0,0 +1,217 @@
+/*
+ * 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 net.shibboleth.idp.plugin.oidc.op.profile.impl;
+
+import java.text.ParseException;
+import java.time.Instant;
+import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
+import com.nimbusds.oauth2.sdk.AuthorizationGrant;
+import com.nimbusds.oauth2.sdk.GrantType;
+import com.nimbusds.oauth2.sdk.RefreshTokenGrant;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.navigate.TokenRequestClientIDLookupFunction;
+import net.shibboleth.idp.plugin.oidc.op.token.support.AuthorizeCodeClaimsSet;
+import net.shibboleth.idp.plugin.oidc.op.token.support.RefreshTokenClaimsSet;
+import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
+import net.shibboleth.oidc.profile.core.OidcEventIds;
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.security.DataSealer;
+import net.shibboleth.shared.security.DataSealerException;
+
+/**
+ * Action that unwraps an authorization grant or refresh token grant.
+ * 
+ * <p>Operation is valid if it is successfully unwrapped, parsed as a code or refresh token, is unexpired and was issued
+ * to the expected client.</p>
+ * 
+ * <p> The claims set from the grant is stored to response context via
+ * {@link OIDCAuthenticationResponseContext#setAuthorizationGrantClaimsSet(TokenClaimsSet)}.</p>
+ * 
+ * <p>Note that the addition of support for the "client_credentials" grant type means that there may not in fact be a
+ * grant, or resulting claims set.</p>
+ * 
+ * @since 4.4.0
+ */
+public class UnwrapGrant extends AbstractOIDCTokenResponseAction {
+
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(UnwrapGrant.class);
+
+    /** Data sealer for unwrapping authorization code. */
+    @Nonnull private final DataSealer dataSealer;
+
+    /** Strategy used to obtain the client id value from token request. */
+    @Nonnull private Function<MessageContext, ClientID> clientIDLookupStrategy;
+
+    /** List of deserializer bi-functions for refresh tokens to be used in addition to unsealing opaque value. */
+    @Nonnull private List<BiFunction<ProfileRequestContext,String,RefreshTokenClaimsSet>> refreshTokenDeserializers;
+
+    /**
+     * Constructor.
+     * 
+     * @param sealer sealer to decrypt/hmac authorize code.
+     */
+    public UnwrapGrant(@Nonnull @ParameterName(name = "sealer") final DataSealer sealer) {
+        dataSealer = Constraint.isNotNull(sealer, "DataSealer cannot be null");
+        refreshTokenDeserializers = CollectionSupport.emptyList();
+        clientIDLookupStrategy = new TokenRequestClientIDLookupFunction();
+    }
+
+    /**
+     * Set the list of deserializer bi-functions for refresh tokens to be used in addition to unsealing opaque value.
+     * 
+     * @param deserializers list of deserializers
+     */
+    public void setRefreshTokenDeserializers(
+            @Nonnull final List<BiFunction<ProfileRequestContext,String,RefreshTokenClaimsSet>> deserializers) {
+        checkSetterPreconditions();
+        refreshTokenDeserializers =
+                Constraint.isNotNull(deserializers, "List of refresh token deserializers cannot be null");
+    }
+
+    /**
+     * Set the strategy used to locate the client id of the request.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setClientIDLookupStrategy(@Nonnull final Function<MessageContext, ClientID> strategy) {
+        ifInitializedThrowUnmodifiabledComponentException();
+        clientIDLookupStrategy =
+                Constraint.isNotNull(strategy, "ClientIDLookupStrategy lookup strategy cannot be null");
+    }
+
+// Checkstyle: CyclomaticComplexity|MethodLength|ReturnCount OFF
+    /** {@inheritDoc} */
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        final AuthorizationGrant grant = getTokenRequest().getAuthorizationGrant();
+        
+        log.debug("{} Unwrapping grant type: {}", getLogPrefix(),grant.getType());
+
+        TokenClaimsSet tokenClaimsSet = null;
+        if (GrantType.AUTHORIZATION_CODE.equals(grant.getType())) {
+            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;
+                }
+            }
+        } else if (GrantType.REFRESH_TOKEN.equals(grant.getType())) {
+            final RefreshTokenGrant refreshTokentokenGrant = (RefreshTokenGrant) grant;
+            if (refreshTokentokenGrant.getRefreshToken() != null
+                    && refreshTokentokenGrant.getRefreshToken().getValue() != null) {
+                final String tokenValue = refreshTokentokenGrant.getRefreshToken().getValue();
+                assert tokenValue != null;
+                final RefreshTokenClaimsSet refreshTokenClaimsSet = deserializeRefreshToken(profileRequestContext,
+                        tokenValue);
+                if (refreshTokenClaimsSet == null) {
+                    log.warn("{} Unwrapping refresh token failed", getLogPrefix());
+                    ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
+                    return;
+                }
+                final Instant chainExp = refreshTokenClaimsSet.getChainExp();
+                if (chainExp != null && chainExp.isBefore(Instant.now())) {
+                    log.warn("{} Refresh token chain has expired on {}", getLogPrefix(),
+                            refreshTokenClaimsSet.getChainExp());
+                    ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
+                    return;
+                }
+                tokenClaimsSet = refreshTokenClaimsSet;
+            }
+        } else if (GrantType.CLIENT_CREDENTIALS.equals(grant.getType())) {
+            return;
+        }
+        
+        if (tokenClaimsSet == null) {
+            log.warn("{} Grant type not supported", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
+            return;
+        }
+        if (!tokenClaimsSet.isTimeValid()) {
+            log.warn("{} Token is expired or not net valid", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
+            return;
+        }
+        final ClientID clientId = tokenClaimsSet.getClientID();
+        assert clientId != null;
+        final ClientID requestClientId = clientIDLookupStrategy.apply(profileRequestContext.ensureInboundMessageContext());
+        if (!clientId.equals(requestClientId)) {
+            log.warn("{} Token issued to client {}, invalid for {}", getLogPrefix(),
+                    clientId.getValue(), requestClientId);
+            ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
+            return;
+        }
+        final OIDCAuthenticationResponseContext oidcResponseContext = getOidcResponseContext();
+        assert oidcResponseContext != null;
+        oidcResponseContext.setAuthorizationGrantClaimsSet(tokenClaimsSet);
+    }
+// Checkstyle: CyclomaticComplexity|MethodLength|ReturnCount ON
+
+    /**
+     * Attempt to deseriaalize a (serialized) refresh token value via configured deserializers.
+     * 
+     * @param profileRequestContext The profile request context given to the deserializers
+     * @param refreshToken The serialized refresh token value
+     * @return refresh token claims set, or null if it couldn't be parsed
+     */
+    protected RefreshTokenClaimsSet deserializeRefreshToken(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final String refreshToken) {
+        try {
+            return RefreshTokenClaimsSet.parse(refreshToken, dataSealer);
+        } catch (ParseException | DataSealerException e) {
+        }
+        for (final BiFunction<ProfileRequestContext, String, RefreshTokenClaimsSet> deserializer : 
+            refreshTokenDeserializers) {
+            final RefreshTokenClaimsSet deserializedSet = deserializer.apply(profileRequestContext, refreshToken);
+            if (deserializedSet != null) {
+                return deserializedSet;
+            }
+        }
+        return null;
+    }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrant.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrant.java
index c884911d..fa15f3bc 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrant.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrant.java
@@ -14,11 +14,8 @@
 
 package net.shibboleth.idp.plugin.oidc.op.profile.impl;
 
-import java.text.ParseException;
 import java.time.Duration;
 import java.time.Instant;
-import java.util.List;
-import java.util.function.BiFunction;
 import java.util.function.BiPredicate;
 import java.util.function.Function;
 import java.util.function.Predicate;
@@ -34,10 +31,8 @@ import org.opensaml.storage.RevocationCache;
 import org.slf4j.Logger;
 
 import com.nimbusds.jwt.JWTClaimsSet;
-import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
 import com.nimbusds.oauth2.sdk.AuthorizationGrant;
 import com.nimbusds.oauth2.sdk.GrantType;
-import com.nimbusds.oauth2.sdk.RefreshTokenGrant;
 import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
 import com.nimbusds.oauth2.sdk.id.ClientID;
 import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
@@ -56,15 +51,11 @@ import net.shibboleth.oidc.profile.config.navigate.RefreshTokenChainLifetimeLook
 import net.shibboleth.oidc.profile.config.navigate.RevocationLifetimeLookupFunction;
 import net.shibboleth.oidc.profile.core.OidcEventIds;
 import net.shibboleth.profile.context.RelyingPartyContext;
-import net.shibboleth.shared.annotation.ParameterName;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
 import net.shibboleth.shared.primitive.StringSupport;
-import net.shibboleth.shared.security.DataSealer;
-import net.shibboleth.shared.security.DataSealerException;
 
 /**
  * Action that validates an authorization grant.
@@ -84,9 +75,6 @@ public class ValidateGrant extends AbstractOIDCTokenResponseAction {
     /** Class logger. */
     @Nonnull private Logger log = LoggerFactory.getLogger(ValidateGrant.class);
 
-    /** Data sealer for unwrapping authorization code. */
-    @Nonnull private final DataSealer dataSealer;
-
     /** Message replay cache instance to use. */
     @NonnullAfterInit private ReplayCache replayCache;
 
@@ -107,9 +95,6 @@ public class ValidateGrant extends AbstractOIDCTokenResponseAction {
     /** Strategy used to obtain the refresh token lifetime. */
     @Nonnull private Function<ProfileRequestContext,Duration> refreshTokenChainLifetimeLookupStrategy;
 
-    /** List of deserializer bi-functions for refresh tokens to be used in addition to unsealing opaque value. */
-    @Nonnull private List<BiFunction<ProfileRequestContext,String,RefreshTokenClaimsSet>> refreshTokenDeserializers;
-
     /** Strategy used to locate thumbprint of validated DPoP Proof JWT. */
     @Nonnull private Function<ProfileRequestContext, String> dpopProofThumbprintLookupStrategy;
 
@@ -124,17 +109,13 @@ public class ValidateGrant extends AbstractOIDCTokenResponseAction {
 
     /**
      * Constructor.
-     * 
-     * @param sealer sealer to decrypt/hmac authorize code.
      */
-    public ValidateGrant(@Nonnull @ParameterName(name = "sealer") final DataSealer sealer) {
-        dataSealer = Constraint.isNotNull(sealer, "DataSealer cannot be null");
+    public ValidateGrant() {
         relyingPartyContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class);
         refreshTokensEnabledPredicate = new RefreshTokensEnabledPredicate();
         chainRevocationLifetimeLookupStrategy = new DefaultChainRevocationLifetimeLookupStrategy();
         ((RevocationLifetimeLookupFunction) chainRevocationLifetimeLookupStrategy).setUseActiveProfileOnly(false);
         refreshTokenChainLifetimeLookupStrategy = new RefreshTokenChainLifetimeLookupFunction();
-        refreshTokenDeserializers = CollectionSupport.emptyList();
         dpopProofThumbprintLookupStrategy = new DefaultDPoPProofThumbprintLookupFunction();
     }
 
@@ -207,18 +188,6 @@ public class ValidateGrant extends AbstractOIDCTokenResponseAction {
                 Constraint.isNotNull(strategy, "Refresh token chain lifetime lookup strategy cannot be null");
     }
 
-    /**
-     * Set the list of deserializer bi-functions for refresh tokens to be used in addition to unsealing opaque value.
-     * 
-     * @param deserializers list of deserializers
-     */
-    public void setRefreshTokenDeserializers(
-            @Nonnull final List<BiFunction<ProfileRequestContext,String,RefreshTokenClaimsSet>> deserializers) {
-        checkSetterPreconditions();
-        refreshTokenDeserializers =
-                Constraint.isNotNull(deserializers, "List of refresh token deserializers cannot be null");
-    }
-
     /**
      * Set the strategy used to locate the thumbprint of validated DPoP Proof JWT.
      * 
@@ -286,51 +255,45 @@ public class ValidateGrant extends AbstractOIDCTokenResponseAction {
         
         log.debug("{} Validating grant type: {}", getLogPrefix(),grant.getType());
 
-        TokenClaimsSet tokenClaimsSet = null;
+        final OIDCAuthenticationResponseContext oidcResponseContext = getOidcResponseContext();
+        assert oidcResponseContext != null;
+        final TokenClaimsSet tokenClaimsSet = oidcResponseContext.getAuthorizationGrantClaimsSet();
         if (GrantType.AUTHORIZATION_CODE.equals(grant.getType())) {
-            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());
-                    final String cacheContext = getClass().getName();
-                    assert cacheContext != null;
-                    if (!replayCache.check(cacheContext, jti, authzCodeClaimsSet.getExp())) {
-                        log.error("{} Replay detected of authz code {}", getLogPrefix(), jti);
-                        if (!revokeChain(jti,
-                                chainRevocationLifetimeLookupStrategy.apply(profileRequestContext))) {
-                            log.warn("{} Fatal error, unable to save replayed code to revocation cache",
-                                    getLogPrefix());
-                        }
-                        ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
-                        return;
-                    }
-                    final String dpopJkt = authzCodeClaimsSet.getDpopProofJwkThumbprint();
-                    if (dpopJkt != null) {
-                        final String proofThumbprint = dpopProofThumbprintLookupStrategy.apply(profileRequestContext);
-                        if (!dpopJkt.equals(proofThumbprint)) {
-                            log.warn("{} The DPoP jkt in claims set '{}' did not match with the DPoP proof JWT '{}'",
-                                    getLogPrefix(), dpopJkt, proofThumbprint);
-                            ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_DPOP_PROOF);
-                            return;
-                        }
+            if (tokenClaimsSet instanceof AuthorizeCodeClaimsSet authzCodeClaimsSet) {
+                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());
+                final String cacheContext = getClass().getName();
+                assert cacheContext != null;
+                if (!replayCache.check(cacheContext, jti, authzCodeClaimsSet.getExp())) {
+                    log.error("{} Replay detected of authz code {}", getLogPrefix(), jti);
+                    if (!revokeChain(jti,
+                            chainRevocationLifetimeLookupStrategy.apply(profileRequestContext))) {
+                        log.warn("{} Fatal error, unable to save replayed code to revocation cache",
+                                getLogPrefix());
                     }
-                    tokenClaimsSet = authzCodeClaimsSet;
-                } catch (final DataSealerException | ParseException e) {
-                    log.warn("{} Unwrapping authz code failed: {}", getLogPrefix(), e.getMessage());
                     ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
                     return;
                 }
+                final String dpopJkt = authzCodeClaimsSet.getDpopProofJwkThumbprint();
+                if (dpopJkt != null) {
+                    final String proofThumbprint = dpopProofThumbprintLookupStrategy.apply(profileRequestContext);
+                    if (!dpopJkt.equals(proofThumbprint)) {
+                        log.warn("{} The DPoP jkt in claims set '{}' did not match with the DPoP proof JWT '{}'",
+                                getLogPrefix(), dpopJkt, proofThumbprint);
+                        ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_DPOP_PROOF);
+                        return;
+                    }
+                }
+            } else {
+                log.error("{} Unexpected instance of token claims set for authorization code: {}",
+                        getLogPrefix(), tokenClaimsSet);
+                ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
+                return;
             }
         } else if (GrantType.REFRESH_TOKEN.equals(grant.getType())) {
             if (!refreshTokensEnabledPredicate.test(profileRequestContext)) {
@@ -338,18 +301,7 @@ public class ValidateGrant extends AbstractOIDCTokenResponseAction {
                 ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
                 return;
             }
-            final RefreshTokenGrant refreshTokentokenGrant = (RefreshTokenGrant) grant;
-            if (refreshTokentokenGrant.getRefreshToken() != null
-                    && refreshTokentokenGrant.getRefreshToken().getValue() != null) {
-                final String tokenValue = refreshTokentokenGrant.getRefreshToken().getValue();
-                assert tokenValue != null;
-                final RefreshTokenClaimsSet refreshTokenClaimsSet = deserializeRefreshToken(profileRequestContext,
-                        tokenValue);
-                if (refreshTokenClaimsSet == null) {
-                    log.warn("{} Unwrapping refresh token failed", getLogPrefix());
-                    ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
-                    return;
-                }
+            if (tokenClaimsSet instanceof RefreshTokenClaimsSet refreshTokenClaimsSet) {
                 final String rootJti = refreshTokenClaimsSet.getRootTokenIdentifier();
                 final String rootJtiToUse;
                 if (StringSupport.trimOrNull(rootJti) == null) {
@@ -388,7 +340,6 @@ public class ValidateGrant extends AbstractOIDCTokenResponseAction {
                     ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
                     return;
                 }
-                tokenClaimsSet = refreshTokenClaimsSet;
                 final Instant authnTime = tokenClaimsSet.getAuthenticationTime();
                 assert authnTime != null;
                 if (Instant.now().isAfter(authnTime.plus(refreshTokenChainLifetime))) {
@@ -397,13 +348,16 @@ public class ValidateGrant extends AbstractOIDCTokenResponseAction {
                     ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
                     return;
                 }
+            } else {
+                log.error("{} Unexpected instance of token claims set for refresh token: {}",
+                        getLogPrefix(), tokenClaimsSet);
+                ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
+                return;
             }
 
         } else if (GrantType.CLIENT_CREDENTIALS.equals(grant.getType())) {
             final String proofThumbprint = dpopProofThumbprintLookupStrategy.apply(profileRequestContext);
             if (proofThumbprint != null) {
-                final OIDCAuthenticationResponseContext oidcResponseContext = getOidcResponseContext();
-                assert oidcResponseContext != null;
                 oidcResponseContext.setDpopProofJwkThumbprint(proofThumbprint);
             }
             return;
@@ -435,9 +389,6 @@ public class ValidateGrant extends AbstractOIDCTokenResponseAction {
             ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
             return;
         }
-        final OIDCAuthenticationResponseContext oidcResponseContext = getOidcResponseContext();
-        assert oidcResponseContext != null;
-        oidcResponseContext.setAuthorizationGrantClaimsSet(tokenClaimsSet);
         final String claimsSetThumbprint = GrantType.REFRESH_TOKEN.equals(grant.getType()) && !isPublicClient() ?
                 null : tokenClaimsSet.getDpopProofJwkThumbprint();
         final String proofThumbprint = dpopProofThumbprintLookupStrategy.apply(profileRequestContext);
@@ -481,29 +432,6 @@ public class ValidateGrant extends AbstractOIDCTokenResponseAction {
         return false;
     }
 
-    /**
-     * Attempt to deseriaalize a (serialized) refresh token value via configured deserializers.
-     * 
-     * @param profileRequestContext The profile request context given to the deserializers
-     * @param refreshToken The serialized refresh token value
-     * @return refresh token claims set, or null if it couldn't be parsed
-     */
-    protected RefreshTokenClaimsSet deserializeRefreshToken(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final String refreshToken) {
-        try {
-            return RefreshTokenClaimsSet.parse(refreshToken, dataSealer);
-        } catch (ParseException | DataSealerException e) {
-        }
-        for (final BiFunction<ProfileRequestContext, String, RefreshTokenClaimsSet> deserializer : 
-            refreshTokenDeserializers) {
-            final RefreshTokenClaimsSet deserializedSet = deserializer.apply(profileRequestContext, refreshToken);
-            if (deserializedSet != null) {
-                return deserializedSet;
-            }
-        }
-        return null;
-    }
-
     /**
      * Revokes the token chain with the given id, optionally with a given lifetime. If the given lifetime is null,
      * the default lifetime set to the {@link RevocationCache} is used.
@@ -521,4 +449,4 @@ public class ValidateGrant extends AbstractOIDCTokenResponseAction {
         return revocationCache.revoke(RevocationCacheContexts.AUTHORIZATION_CODE, id, lifetime);
     }
     
-}
\ No newline at end of file
+}
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract-api/oidc-abstract-api-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract-api/oidc-abstract-api-beans.xml
index 1aa04370..2a79c647 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract-api/oidc-abstract-api-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract-api/oidc-abstract-api-beans.xml
@@ -42,17 +42,24 @@
                         <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
                             p:claimName="iss">
                             <property name="valueToMatchLookupStrategy">
-                                <bean class="net.shibboleth.shared.logic.BiFunctionSupport"
-                                    factory-method="forFunctionOfFirstArg"
-                                    c:_0-ref="shibboleth.ResponderIdLookup.Simple" />
+                                <bean parent="shibboleth.BiFunctions.Constant" c:target-ref="shibboleth.oidc.issuer"/>
                             </property>
                         </bean>
                         <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
                             p:claimName="client_id">
                             <property name="valueToMatchLookupStrategy">
                                 <bean class="net.shibboleth.shared.logic.BiFunctionSupport"
-                                    factory-method="forFunctionOfFirstArg"
-                                    c:_0-ref="shibboleth.RelyingPartyIdLookup.Simple" />
+                                    factory-method="forFunctionOfFirstArg">
+                                    <constructor-arg>
+                                        <bean parent="shibboleth.Functions.Expression" c:expression="#custom.apply(#input).getValue()">
+                                            <property name="customObject">
+                                                <bean parent="shibboleth.Functions.Compose"
+                                                    c:g="#{getObject('shibboleth.ClientIDLookupStrategy') ?: getObject('shibboleth.RelyingPartyIdLookup.Simple')}"
+                                                    c:f-ref="shibboleth.MessageContextLookup.Inbound" />
+                                            </property>
+                                        </bean>
+                                    </constructor-arg>
+                                </bean>
                             </property>
                         </bean>
                         <bean class="net.shibboleth.oidc.security.jwt.claims.impl.AudienceClaimsValidator">
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
index 2fa145e9..af8c7aa6 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
@@ -143,13 +143,16 @@
         </property>
     </bean>
 
+    <bean id="DeserializePushedRequest"
+        class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.DeserializePushedRequest" scope="prototype"
+        p:pushedAuthorizationRequestUriDeserializers-ref="#{'%{idp.oauth2.par.deserializationStrategies:shibboleth.oidc.DefaultPushedAuthorizationRequestUriDeserializers}'.trim()}"/>
+
     <bean id="SetRequestObjectToResponseContext"
         class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.SetRequestObjectToResponseContext" scope="prototype"
         p:httpClient="#{getObject('shibboleth.oidc.NonBrowser.HttpClient') ?: getObject('shibboleth.InternalHttpClient')}"
         p:httpClientSecurityParameters="#{getObject('shibboleth.oidc.NonBrowser.HttpClientSecurityParameters')}"
         p:pushedAuthorizationRequestEnforcedPredicate-ref="RequirePushedAuthorizationRequestPredicate"
-        p:objectMapper-ref="#{'%{idp.oidc.logging.objectMapper:shibboleth.oidc.JSONObjectMapper}'.trim()}"
-        p:pushedAuthorizationRequestUriDeserializers-ref="#{'%{idp.oauth2.par.deserializationStrategies:shibboleth.oidc.DefaultPushedAuthorizationRequestUriDeserializers}'.trim()}">
+        p:objectMapper-ref="#{'%{idp.oidc.logging.objectMapper:shibboleth.oidc.JSONObjectMapper}'.trim()}">
         <property name="requestObjectEnforcedPredicate">
             <bean parent="shibboleth.Conditions.OR">
                 <constructor-arg>
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
index 4ffc9741..75973cf4 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
@@ -31,6 +31,7 @@
 
     <action-state id="PostDecodeMessage">
         <evaluate expression="PostDecodePopulateAuditContext" />
+        <evaluate expression="DeserializePushedRequest" />
         <evaluate expression="'proceed'" />
         <!-- DoMetadataLookup is expected to proceed to SelectConfiguration -->
         <transition on="proceed" to="DoMetadataLookup" />
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
index 602f119c..ef14c177 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
@@ -33,6 +33,10 @@
     <bean id="shibboleth.ClientIDLookupStrategy"
         class="net.shibboleth.idp.plugin.oidc.op.messaging.context.navigate.TokenRequestClientIDLookupFunction" />
 
+    <bean id="UnwrapGrant" 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()}"/>
+
     <bean id="ResolveAttributesForClientPredicate"
         class="net.shibboleth.profile.config.logic.ResolveAttributesPredicate" />
 
@@ -74,10 +78,8 @@
     <!-- Traditional third-party grant handling. -->
 
     <bean id="ValidateGrant" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateGrant" scope="prototype"
-        c:sealer-ref="#{'%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}"
         p:replayCache-ref="shibboleth.ReplayCache"
         p:revocationCache-ref="shibboleth.oidc.RevocationCache"
-        p:refreshTokenDeserializers-ref="#{'%{idp.oauth2.refreshToken.deserializers:shibboleth.oidc.DefaultRefreshTokenDeserializers}'.trim()}"
         p:tokenRevocationCondition="#{getObject('%{idp.oauth2.revocationCondition:shibboleth.BiConditions.FALSE}')}">
         <property name="chainRevocationLifetimeLookupStrategy">
             <bean class="net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultChainRevocationLifetimeLookupStrategy"
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-flow.xml
index 98e1d836..9eb45d3f 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-flow.xml
@@ -9,7 +9,17 @@
         <evaluate expression="InitializeOutboundMessageContext" />
         <evaluate expression="'proceed'" />
         
-        <transition on="proceed" to="DecodeMessage"/>
+        <transition on="proceed" to="DecodeMessage">
+            <set name="flowScope.transitionAfterDecode" value="'PostDecodeMessage'" />
+         </transition>
+    </action-state>
+
+    <action-state id="PostDecodeMessage">
+        <evaluate expression="UnwrapGrant" />
+        <evaluate expression="'proceed'" />
+        
+        <!-- DoMetadataLookup is expected to proceed to SelectConfiguration -->
+        <transition on="proceed" to="DoMetadataLookup" />
     </action-state>
 
     <action-state id="SelectConfiguration">
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java
index e2a48be1..1f2b0f55 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java
@@ -162,8 +162,9 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
     }
 
     @Test
-    public void testUntrustedClient() throws IOException, ParseException {
-        setHttpFormRequest("POST", createRequestParameters(null, "authorization_code", "mockCode", clientId + "2"));
+    public void testUntrustedClient() throws Exception {
+        setHttpFormRequest("POST", createRequestParameters(null, "authorization_code",
+                buildAuthorizationCode(clientId + "2", null, scope.toString()), clientId + "2"));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         assertErrorCode(result, OAuth2Error.INVALID_CLIENT_CODE);
     }
@@ -173,7 +174,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         setHttpFormRequest("POST", createRequestParameters(redirectUri, "authorization_code", "mockCode", clientId));
         storeMetadata(storageService, clientId, clientSecret, scope);
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
-        assertErrorCode(result, OAuth2Error.INVALID_CLIENT_CODE);
+        assertErrorCode(result, OAuth2Error.INVALID_GRANT_CODE);
     }
 
     @Test
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrantTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrantTest.java
index e3083841..e78c7587 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrantTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrantTest.java
@@ -25,17 +25,13 @@ import net.shibboleth.idp.profile.testing.ActionTestingSupport;
 import net.shibboleth.oidc.profile.core.OidcEventIds;
 import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.logic.PredicateSupport;
-import net.shibboleth.shared.security.DataSealerException;
 import net.shibboleth.shared.security.impl.SecureRandomIdentifierGenerationStrategy;
 
 import java.net.URI;
 import java.security.NoSuchAlgorithmException;
-import java.text.ParseException;
 import java.time.Duration;
 import java.time.Instant;
 import java.util.Collection;
-import java.util.List;
-import java.util.function.BiFunction;
 import java.util.function.Function;
 
 import org.mockito.Mockito;
@@ -50,7 +46,6 @@ import org.testng.annotations.Test;
 
 import com.nimbusds.oauth2.sdk.AuthorizationCode;
 import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
-import com.nimbusds.oauth2.sdk.AuthorizationGrant;
 import com.nimbusds.oauth2.sdk.ClientCredentialsGrant;
 import com.nimbusds.oauth2.sdk.RefreshTokenGrant;
 import com.nimbusds.oauth2.sdk.Scope;
@@ -70,10 +65,6 @@ public class ValidateGrantTest extends BaseOIDCResponseActionTest {
 
     TokenClaimsSet rfClaims;
 
-    AuthorizationGrant codeGrant;
-
-    RefreshTokenGrant rfGrant;
-
     URI callback;
     
     MemoryStorageService storageService;
@@ -92,23 +83,16 @@ public class ValidateGrantTest extends BaseOIDCResponseActionTest {
     }
 
     private void init(boolean refreshTokensEnabled) throws Exception {
-        init(refreshTokensEnabled, new MockRevocationCache(false, true), null);
+        init(refreshTokensEnabled, new MockRevocationCache(false, true), null, Instant.now());
     }
 
     private void init(final Instant authenticationTime) throws Exception {
-        init(true, new MockRevocationCache(false, true), null, authenticationTime, null);
-    }
-
-    private void init(boolean refreshTokensEnabled, final RevocationCache revocationCache,
-            final Function<ProfileRequestContext, Duration> revocationLifetimeLookup) throws Exception {
-        init(refreshTokensEnabled, revocationCache, revocationLifetimeLookup, Instant.now(), null);
+        init(true, new MockRevocationCache(false, true), null, authenticationTime);
     }
 
     @SuppressWarnings("null")
     private void init(boolean refreshTokensEnabled, final RevocationCache revocationCache,
-            final Function<ProfileRequestContext, Duration> revocationLifetimeLookup,
-            final Instant authenticationTime,
-            final List<BiFunction<ProfileRequestContext,String,RefreshTokenClaimsSet>> deserializers)
+            final Function<ProfileRequestContext, Duration> revocationLifetimeLookup, final Instant authenticationTime)
                     throws Exception {
         final Instant now = Instant.now();
         rootTokenId = "mockId" + now.toEpochMilli();
@@ -128,22 +112,12 @@ public class ValidateGrantTest extends BaseOIDCResponseActionTest {
         rfClaims = new RefreshTokenClaimsSet.Builder(acClaims, now, now.plusSeconds(100))
                 .setRootTokenIdentifier(rootTokenId)
                 .build();
-        final AuthorizationCode code = new AuthorizationCode(acClaims.serialize(getDataSealer()));
-        final RefreshToken rfToken = new RefreshToken(rfClaims.serialize(getDataSealer()));
         callback = new URI("https://client.com/callback");
-        codeGrant = new AuthorizationCodeGrant(code, callback);
-        rfGrant = new RefreshTokenGrant(rfToken);
-        // by default we create authz code request
-        final TokenRequest req = new TokenRequest(callback, new ClientID(clientId), codeGrant);
-        profileRequestCtx.ensureInboundMessageContext().setMessage(req);
-        action = new ValidateGrant(getDataSealer());
+        action = new ValidateGrant();
         if (revocationLifetimeLookup != null) {
             action.setChainRevocationLifetimeLookupStrategy(revocationLifetimeLookup);
         }
         action.setRevocationCache(revocationCache);
-        if (deserializers != null) {
-            action.setRefreshTokenDeserializers(deserializers);
-        }
         final StorageServiceReplayCache replayCache = new StorageServiceReplayCache();
         replayCache.setStorage(storageService);
         action.setReplayCache(replayCache);
@@ -153,27 +127,30 @@ public class ValidateGrantTest extends BaseOIDCResponseActionTest {
             action.setRefreshTokensEnabledPredicate(PredicateSupport.alwaysFalse());
         }
         action.initialize();
+        final TokenRequest req = new TokenRequest(callback, new ClientID(clientId), new AuthorizationCodeGrant(new AuthorizationCode("obsolete"), callback));
+        profileRequestCtx.ensureInboundMessageContext().setMessage(req);
+        profileRequestCtx.ensureOutboundMessageContext().ensureSubcontext(OIDCAuthenticationResponseContext.class).setAuthorizationGrantClaimsSet(acClaims);;
     }
 
-    static public AuthorizationCode buildAuthorizationCode(final String clientId, final String issuer,
+    static public TokenClaimsSet buildAuthorizationCode(final String clientId, final String issuer,
             final String userPrincipal, final String sub, final String callbackUrl) throws Exception {
         return buildAuthorizationCode(clientId, issuer, userPrincipal, sub, callbackUrl, null);
     }
 
-    static public AuthorizationCode buildAuthorizationCode(final String clientId, final String issuer,
+    static public TokenClaimsSet buildAuthorizationCode(final String clientId, final String issuer,
             final String userPrincipal, final String sub, final String callbackUrl, final String scope)
                     throws Exception {
         return buildAuthorizationCode(clientId, issuer, userPrincipal, sub, callbackUrl, null, scope);
     }
 
-    static public AuthorizationCode buildAuthorizationCode(final String clientId, final String issuer,
+    static public TokenClaimsSet buildAuthorizationCode(final String clientId, final String issuer,
             final String userPrincipal, final String sub, final String callbackUrl, final String codeChallenge,
             final String scope) throws Exception {
         return buildAuthorizationCode(clientId, issuer, userPrincipal, sub, callbackUrl, codeChallenge, null, null,
                 null, scope);
     }
 
-    static public AuthorizationCode buildAuthorizationCode(final String clientId, final String issuer,
+    static public TokenClaimsSet buildAuthorizationCode(final String clientId, final String issuer,
             final String userPrincipal, final String sub, final String callbackUrl, final String codeChallenge,
             final JSONObject deliveryClaims, final JSONObject deliveryClaimsIDToken,
             final JSONObject deliveryClaimsUserInfo, final String scope) throws Exception {
@@ -181,14 +158,13 @@ public class ValidateGrantTest extends BaseOIDCResponseActionTest {
                 deliveryClaimsIDToken, deliveryClaimsUserInfo, scope, null);
     }
 
-    static public AuthorizationCode buildAuthorizationCode(final String clientId, final String issuer,
+    static public TokenClaimsSet buildAuthorizationCode(final String clientId, final String issuer,
             final String userPrincipal, final String sub, final String callbackUrl, final String codeChallenge,
             final JSONObject deliveryClaims, final JSONObject deliveryClaimsIDToken,
             final JSONObject deliveryClaimsUserInfo, final String scope, final Collection<String> aud)
                     throws Exception {
-        final TokenClaimsSet acClaims = buildTokenClaimsSet(clientId, issuer, userPrincipal, sub, callbackUrl,
+        return buildTokenClaimsSet(clientId, issuer, userPrincipal, sub, callbackUrl,
                 codeChallenge, deliveryClaims, deliveryClaimsIDToken, deliveryClaimsUserInfo, scope, aud);
-        return new AuthorizationCode(acClaims.serialize(new ValidateGrantTest().getDataSealer()));
     }
 
     @SuppressWarnings("null")
@@ -245,46 +221,9 @@ public class ValidateGrantTest extends BaseOIDCResponseActionTest {
     @Test
     public void testRefreshTokenSuccess() throws Exception {
         init();
-        final TokenRequest req = new TokenRequest(callback, new ClientID(clientId), rfGrant);
-        profileRequestCtx.ensureInboundMessageContext().setMessage(req);
-        ActionTestingSupport.assertProceedEvent(action.execute(requestCtx));
-        final OIDCAuthenticationResponseContext arc =
-                profileRequestCtx.ensureOutboundMessageContext().ensureSubcontext(OIDCAuthenticationResponseContext.class);
-        Assert.assertNotNull(arc.getAuthorizationGrantClaimsSet());
-    }
-
-    @Test
-    public void testCustomRefreshTokenNoDeserializers() throws Exception {
-        init();
-        final RefreshToken customToken = new RefreshToken("customPrefix" + rfGrant.getRefreshToken().getValue());
-        final TokenRequest req = new TokenRequest(callback, new ClientID(clientId), new RefreshTokenGrant(customToken));
-        profileRequestCtx.ensureInboundMessageContext().setMessage(req);
-        ActionTestingSupport.assertEvent(action.execute(requestCtx), OidcEventIds.INVALID_GRANT);
-    }
-
-    @Test
-    public void testCustomRefreshTokenSuccess() throws Exception {
-        final List<BiFunction<ProfileRequestContext,String,RefreshTokenClaimsSet>> deserializers =
-                List.of(new BiFunction<>() {
-
-                    @Override
-                    public RefreshTokenClaimsSet apply(final ProfileRequestContext prc, final String value ) {
-                        try {
-                            final String strippedValue = value.substring("customPrefix".length());
-                            assert strippedValue != null;
-                            return RefreshTokenClaimsSet.parse(strippedValue, getDataSealer());
-                        } catch (NoSuchAlgorithmException | ParseException | DataSealerException
-                                | ComponentInitializationException e) {
-                            Assert.fail("Could not decrypt the custom refresh token", e);
-                        }
-                        return null;
-                    }
-            
-        });
-        init(true, new MockRevocationCache(false, true), null, Instant.now(), deserializers);
-        final RefreshToken customToken = new RefreshToken("customPrefix" + rfGrant.getRefreshToken().getValue());
-        final TokenRequest req = new TokenRequest(callback, new ClientID(clientId), new RefreshTokenGrant(customToken));
+        final TokenRequest req = new TokenRequest(callback, new ClientID(clientId), new RefreshTokenGrant(new RefreshToken("obsolete")));
         profileRequestCtx.ensureInboundMessageContext().setMessage(req);
+        profileRequestCtx.ensureOutboundMessageContext().ensureSubcontext(OIDCAuthenticationResponseContext.class).setAuthorizationGrantClaimsSet(rfClaims);;
         ActionTestingSupport.assertProceedEvent(action.execute(requestCtx));
         final OIDCAuthenticationResponseContext arc =
                 profileRequestCtx.ensureOutboundMessageContext().ensureSubcontext(OIDCAuthenticationResponseContext.class);
@@ -294,65 +233,55 @@ public class ValidateGrantTest extends BaseOIDCResponseActionTest {
     @Test
     public void testRefreshTokenChainExpired() throws Exception {
         init(Instant.now().minus(Duration.ofHours(2)));
-        final TokenRequest req = new TokenRequest(callback, new ClientID(clientId), rfGrant);
+        final TokenRequest req = new TokenRequest(callback, new ClientID(clientId), new RefreshTokenGrant(new RefreshToken("obsolete")));
         profileRequestCtx.ensureInboundMessageContext().setMessage(req);
+        profileRequestCtx.ensureOutboundMessageContext().ensureSubcontext(OIDCAuthenticationResponseContext.class).setAuthorizationGrantClaimsSet(rfClaims);;
         ActionTestingSupport.assertEvent(action.execute(requestCtx), OidcEventIds.INVALID_GRANT);
-        final OIDCAuthenticationResponseContext arc =
-                profileRequestCtx.ensureOutboundMessageContext().ensureSubcontext(OIDCAuthenticationResponseContext.class);
-        Assert.assertNull(arc.getAuthorizationGrantClaimsSet());
     }
 
     @Test
     public void testRefreshTokenNotEnabled() throws Exception {
         init(false);
-        final TokenRequest req = new TokenRequest(callback, new ClientID(clientId), rfGrant);
+        final TokenRequest req = new TokenRequest(callback, new ClientID(clientId), new RefreshTokenGrant(new RefreshToken("obsolete")));
         profileRequestCtx.ensureInboundMessageContext().setMessage(req);
+        profileRequestCtx.ensureOutboundMessageContext().ensureSubcontext(OIDCAuthenticationResponseContext.class).setAuthorizationGrantClaimsSet(rfClaims);;
         ActionTestingSupport.assertEvent(action.execute(requestCtx), OidcEventIds.INVALID_GRANT);
-        final OIDCAuthenticationResponseContext arc =
-                profileRequestCtx.ensureOutboundMessageContext().ensureSubcontext(OIDCAuthenticationResponseContext.class);
-        Assert.assertNull(arc.getAuthorizationGrantClaimsSet());
     }
 
     @Test
     public void testRefreshTokenReplayed() throws Exception {
         init();
-        final TokenRequest req = new TokenRequest(callback, new ClientID(clientId), rfGrant);
+        final TokenRequest req = new TokenRequest(callback, new ClientID(clientId), new RefreshTokenGrant(new RefreshToken("obsolete")));
         profileRequestCtx.ensureInboundMessageContext().setMessage(req);
+        profileRequestCtx.ensureOutboundMessageContext().ensureSubcontext(OIDCAuthenticationResponseContext.class).setAuthorizationGrantClaimsSet(rfClaims);;
         ActionTestingSupport.assertProceedEvent(action.execute(requestCtx));
         ActionTestingSupport.assertProceedEvent(action.execute(requestCtx));
-        final OIDCAuthenticationResponseContext arc =
-                profileRequestCtx.ensureOutboundMessageContext().ensureSubcontext(OIDCAuthenticationResponseContext.class);
-        Assert.assertNotNull(arc.getAuthorizationGrantClaimsSet());
     }
 
     @Test
     public void testRefreshTokenAuthorizationGrantRevoked() throws Exception {
         final RevocationCache revocationCache = buildRevocationCache();
-        init(true, revocationCache, null);
+        init(true, revocationCache, null, Instant.now());
         final String rootJti = rfClaims.getRootTokenIdentifier();
         assert rootJti != null;
         Assert.assertTrue(revocationCache.revoke(RevocationCacheContexts.AUTHORIZATION_CODE, rootJti));
-        final TokenRequest req = new TokenRequest(callback, new ClientID(clientId), rfGrant);
+        final TokenRequest req = new TokenRequest(callback, new ClientID(clientId), new RefreshTokenGrant(new RefreshToken("obsolete")));
         profileRequestCtx.ensureInboundMessageContext().setMessage(req);
+        profileRequestCtx.ensureOutboundMessageContext().ensureSubcontext(OIDCAuthenticationResponseContext.class).setAuthorizationGrantClaimsSet(rfClaims);;
         ActionTestingSupport.assertEvent(action.execute(requestCtx), OidcEventIds.INVALID_GRANT);
-        final OIDCAuthenticationResponseContext arc =
-                profileRequestCtx.ensureOutboundMessageContext().ensureSubcontext(OIDCAuthenticationResponseContext.class);
-        Assert.assertNull(arc.getAuthorizationGrantClaimsSet());
     }
 
     @Test
     public void testRefreshTokenRevokedShouldRevokeAuthorizationCode() throws Exception {
         final RevocationCache revocationCache = buildRevocationCache();
-        init(true, revocationCache, null);
+        init(true, revocationCache, null, Instant.now());
         final String jti = rfClaims.getID();
         assert jti != null;
         Assert.assertTrue(revocationCache.revoke(RevocationCacheContexts.SINGLE_ACCESS_OR_REFRESH_TOKENS, jti));
-        final TokenRequest req = new TokenRequest(callback, new ClientID(clientId), rfGrant);
+        final TokenRequest req = new TokenRequest(callback, new ClientID(clientId), new RefreshTokenGrant(new RefreshToken("obsolete")));
         profileRequestCtx.ensureInboundMessageContext().setMessage(req);
+        profileRequestCtx.ensureOutboundMessageContext().ensureSubcontext(OIDCAuthenticationResponseContext.class).setAuthorizationGrantClaimsSet(rfClaims);;
         ActionTestingSupport.assertEvent(action.execute(requestCtx), OidcEventIds.INVALID_GRANT);
-        final OIDCAuthenticationResponseContext arc =
-                profileRequestCtx.ensureOutboundMessageContext().ensureSubcontext(OIDCAuthenticationResponseContext.class);
-        Assert.assertNull(arc.getAuthorizationGrantClaimsSet());
         final String rootJti = rfClaims.getRootTokenIdentifier();
         assert rootJti != null;
         Assert.assertTrue(revocationCache.isRevoked(RevocationCacheContexts.AUTHORIZATION_CODE, rootJti));
@@ -376,13 +305,13 @@ public class ValidateGrantTest extends BaseOIDCResponseActionTest {
         Mockito.when(revocationCache.isRevoked(Mockito.matches(RevocationCacheContexts.SINGLE_ACCESS_OR_REFRESH_TOKENS),
                 Mockito.anyString())).thenReturn(true);
         Mockito.when(revocationCache.revoke(Mockito.anyString(), Mockito.anyString())).thenReturn(false);
-        init(true, revocationCache, prc -> null);
-        final TokenRequest req = new TokenRequest(callback, new ClientID(clientId), rfGrant);
+        init(true, revocationCache, prc -> null, Instant.now());
+        final TokenRequest req = new TokenRequest(callback, new ClientID(clientId),
+                new RefreshTokenGrant(new RefreshToken("obsolete")));
         profileRequestCtx.ensureInboundMessageContext().setMessage(req);
+        profileRequestCtx.ensureOutboundMessageContext().ensureSubcontext(OIDCAuthenticationResponseContext.class)
+            .setAuthorizationGrantClaimsSet(rfClaims);
         ActionTestingSupport.assertEvent(action.execute(requestCtx), IdPEventIds.INVALID_PROFILE_CONFIG);
-        final OIDCAuthenticationResponseContext arc =
-                profileRequestCtx.ensureOutboundMessageContext().ensureSubcontext(OIDCAuthenticationResponseContext.class);
-        Assert.assertNull(arc.getAuthorizationGrantClaimsSet());
     }
 
     @Test
@@ -392,23 +321,15 @@ public class ValidateGrantTest extends BaseOIDCResponseActionTest {
                 new RefreshTokenGrant(new RefreshToken(acClaims.serialize(getDataSealer()))));
         profileRequestCtx.ensureInboundMessageContext().setMessage(req);
         ActionTestingSupport.assertEvent(action.execute(requestCtx), OidcEventIds.INVALID_GRANT);
-        final OIDCAuthenticationResponseContext arc =
-                profileRequestCtx.ensureOutboundMessageContext().ensureSubcontext(OIDCAuthenticationResponseContext.class);
-        Assert.assertNull(arc.getAuthorizationGrantClaimsSet());
     }
 
     @Test
     public void testWrongClient() throws Exception {
         init();
-        final AuthorizationCode code =
-                buildAuthorizationCode("clientIdWrong", "issuer", "userPrin", "subject", "http://example.com");
-        final TokenRequest req =
-                new TokenRequest(callback, new ClientID(clientId), new AuthorizationCodeGrant(code, callback));
-        profileRequestCtx.ensureInboundMessageContext().setMessage(req);
+        profileRequestCtx.ensureOutboundMessageContext().ensureSubcontext(OIDCAuthenticationResponseContext.class)
+            .setAuthorizationGrantClaimsSet(
+                    buildAuthorizationCode("clientIdWrong", "issuer", "userPrin", "subject", "http://example.com"));
         ActionTestingSupport.assertEvent(action.execute(requestCtx), OidcEventIds.INVALID_GRANT);
-        final OIDCAuthenticationResponseContext arc =
-                profileRequestCtx.ensureOutboundMessageContext().ensureSubcontext(OIDCAuthenticationResponseContext.class);
-        Assert.assertNull(arc.getAuthorizationGrantClaimsSet());
     }
 
     @SuppressWarnings("null")
@@ -421,9 +342,6 @@ public class ValidateGrantTest extends BaseOIDCResponseActionTest {
                 new RefreshTokenGrant(new RefreshToken(rfClaims.serialize(getDataSealer()))));
         profileRequestCtx.ensureInboundMessageContext().setMessage(req);
         ActionTestingSupport.assertEvent(action.execute(requestCtx), OidcEventIds.INVALID_GRANT);
-        final OIDCAuthenticationResponseContext arc =
-                profileRequestCtx.ensureOutboundMessageContext().ensureSubcontext(OIDCAuthenticationResponseContext.class);
-        Assert.assertNull(arc.getAuthorizationGrantClaimsSet());
     }
 
     @Test
@@ -433,6 +351,8 @@ public class ValidateGrantTest extends BaseOIDCResponseActionTest {
                 new ClientSecretBasic(new ClientID(clientId), new Secret("foo")),
                 new ClientCredentialsGrant());
         profileRequestCtx.ensureInboundMessageContext().setMessage(req);
+        profileRequestCtx.ensureOutboundMessageContext().ensureSubcontext(OIDCAuthenticationResponseContext.class)
+            .setAuthorizationGrantClaimsSet(null);
         ActionTestingSupport.assertProceedEvent(action.execute(requestCtx));
         final OIDCAuthenticationResponseContext arc =
                 profileRequestCtx.ensureOutboundMessageContext().ensureSubcontext(OIDCAuthenticationResponseContext.class);
@@ -441,7 +361,7 @@ public class ValidateGrantTest extends BaseOIDCResponseActionTest {
     
     @Test(expectedExceptions = ComponentInitializationException.class)
     public void testNoRevocationCache() throws ComponentInitializationException, NoSuchAlgorithmException {
-        action = new ValidateGrant(getDataSealer());
+        action = new ValidateGrant();
         final StorageServiceReplayCache replayCache = new StorageServiceReplayCache();
         final MemoryStorageService storageService = new MemoryStorageService();
         storageService.setId("mockId");
@@ -453,7 +373,7 @@ public class ValidateGrantTest extends BaseOIDCResponseActionTest {
 
     @Test(expectedExceptions = ComponentInitializationException.class)
     public void testNoReplayCache() throws ComponentInitializationException, NoSuchAlgorithmException {
-        action = new ValidateGrant(getDataSealer());
+        action = new ValidateGrant();
         action.setRevocationCache(new MockRevocationCache(false, true));
         action.initialize();
     }

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


More information about the commits mailing list