[java-oidc-common] 01/08: Move in Token request encoders and response decoders and other functions

Codeberg noreply at shibboleth.net
Tue Feb 17 20:59:08 UTC 2026


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

codeberg pushed a commit to branch dev/JCOMOIDC-139
in repository java-oidc-common.

View the commit online:
https://codeberg.org/Shibboleth/java-oidc-common/commit/fc903ba27bda6419185c16ae3846f1a2b9e5c88f

commit fc903ba27bda6419185c16ae3846f1a2b9e5c88f
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Nov 7 14:33:25 2025 +0000

    Move in Token request encoders and response decoders and other functions
    
     - Move in token request encoders and response decoders
     - Move over more lookup functions
     - Move over some flow actions common with the RP Proxy
     - Copy over UserInfo lookup encoders and decoders from the RP
---
 ...tAuthenticationConfigurationLookupFunction.java | 191 ++++++++++++++++++++
 .../impl/AbstractTokenResponseLookupStrategy.java  |  64 +++++++
 .../claims/impl/ExtraAudiencesLookupStrategy.java  |  67 +++++++
 .../IDTokenFromResponseContextLookupStrategy.java  |  61 +++++++
 .../impl/ManyValuesIntegerComparisonPredicate.java |  27 +++
 .../jwt/claims/impl/MaxAgeLookupFunction.java      | 109 +++++++++++
 .../claims/impl/SubFromIDTokenLookupFunction.java  |  88 +++++++++
 .../config/logic/UserInfoLookupPredicate.java      |  49 +++++
 ...viderMetadataStringListValueLookupFunction.java |  83 +++++++++
 .../UserInfoHttpRequestMethodLookupStrategy.java   |  45 +++++
 .../AbstractAuthenticatableOIDCContext.java        |  49 +++++
 .../context/AccessTokenResponseContext.java        |  67 +++++++
 .../oidc/profile/context/EndUserClaimsContext.java |  85 +++++++++
 .../oidc/profile/context/OAuth2ClientContext.java  |  91 ++++++++++
 .../oidc/profile/context/OIDCAuthnContext.java     |  94 ++++++++++
 .../context/OutboundMessageHandlerContext.java     |  69 +++++++
 .../profile/context/UserInfoResponseContext.java   |  48 +++++
 .../AbstractTokenResponseLookupStrategy.java       |  64 +++++++
 .../navigate/AccessTokenLookupStrategy.java        |  61 +++++++
 .../ClientIDFromOAuth2ClientContextFunction.java   |  67 +++++++
 .../navigate/EncryptedIDTokenLookupStrategy.java   |  83 +++++++++
 .../IDTokenInAccessTokenUpdateStrategy.java        |  88 +++++++++
 .../navigate/IDTokenJOSEHeaderLookupStrategy.java  |  67 +++++++
 ...viderMetadataFromOuboundPeerLookupStrategy.java |  50 ++++++
 .../profile/core/OAuthAuthorizationRequest.java    |  13 +-
 oidc-common-profile-impl/pom.xml                   |  21 ++-
 .../impl/AbstractJSONResponseDecoderFunction.java  |  70 ++++++++
 .../decoding/impl/AccessTokenResponseDecoder.java  | 103 +++++++++++
 .../decoding/impl/UserInfoResponseDecoder.java     | 135 ++++++++++++++
 .../impl/AbstractRequestEncoderFunction.java       | 195 ++++++++++++++++++++
 .../encoding/impl/AuthCodeTokenRequestEncoder.java | 113 ++++++++++++
 .../encoding/impl/UserInfoRequestEncoder.java      | 200 +++++++++++++++++++++
 ...nitializeOAuth2ClientAuthenticationContext.java |  94 ++++++++++
 33 files changed, 2709 insertions(+), 2 deletions(-)

diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jose/impl/ClientAuthenticationConfigurationLookupFunction.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jose/impl/ClientAuthenticationConfigurationLookupFunction.java
new file mode 100644
index 00000000..4151023b
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jose/impl/ClientAuthenticationConfigurationLookupFunction.java
@@ -0,0 +1,191 @@
+/*
+ * 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.oidc.security.jose.impl;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.messaging.context.navigate.ContextDataLookupFunction;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.ParentProfileRequestContextLookup;
+import org.opensaml.security.config.SecurityConfiguration;
+import org.slf4j.Logger;
+
+import com.nimbusds.jose.Algorithm;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
+
+import net.shibboleth.oidc.profile.config.JSONSecurityConfiguration;
+import net.shibboleth.oidc.profile.config.OIDCAuthenticationRelyingPartyProfileConfiguration;
+import net.shibboleth.oidc.security.jose.SignatureSigningConfiguration;
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.relyingparty.RelyingPartyConfiguration;
+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.primitive.LoggerFactory;
+
+/**
+ * A function that returns a {@link SignatureSigningConfiguration} list for signing client authentication JWTs. 
+ * 
+ * <p>The configuration list is taken from the active security configuration, but the algorithms are filtered to only 
+ * allow those that are compatible with the client authentication type chosen. For example, the HMAC family 
+ * of algorithms if the client_secret_jwt method is used.</p>
+ */
+public class ClientAuthenticationConfigurationLookupFunction
+        implements ContextDataLookupFunction<MessageContext, List<SignatureSigningConfiguration>> {
+
+    /** Lookup function for parent ProfileRequestContext. */
+    @Nonnull private static final ParentProfileRequestContextLookup<MessageContext> PRC_LOOKUP = 
+                new ParentProfileRequestContextLookup<>();
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(ClientAuthenticationConfigurationLookupFunction.class);
+
+    /**
+     * Strategy used to locate the {@link RelyingPartyContext} associated with a
+     * given {@link ProfileRequestContext}.
+     */
+    @Nonnull private Function<ProfileRequestContext, RelyingPartyContext> relyingPartyContextLookupStrategy;
+
+
+    /** Constructor. */
+    public ClientAuthenticationConfigurationLookupFunction() {
+        relyingPartyContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class);
+    }
+
+    /**
+     * Set the strategy used to locate the {@link RelyingPartyContext} associated
+     * with a given {@link ProfileRequestContext}.
+     * 
+     * @param strategy
+     *            lookup strategy
+     */
+    public void setRelyingPartyContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, RelyingPartyContext> strategy) {
+        relyingPartyContextLookupStrategy = Constraint.isNotNull(strategy,
+                "RelyingPartyContext lookup strategy cannot be null");
+    }
+
+    @Override
+    @Nonnull @NonnullElements @NotLive @Unmodifiable
+    public List<SignatureSigningConfiguration> apply(@Nullable final MessageContext input) {
+
+        final List<SignatureSigningConfiguration> configs = new ArrayList<>();
+        String tokenEndpointAuthMethod = null;
+        final RelyingPartyContext rpc = relyingPartyContextLookupStrategy.apply(PRC_LOOKUP.apply(input));       
+        
+        if (rpc != null) {
+            final ProfileConfiguration pc = rpc.getProfileConfig();
+            if (pc != null && pc.getSecurityConfiguration(PRC_LOOKUP.apply(input)) instanceof final JSONSecurityConfiguration jsonSecConfig
+                    && jsonSecConfig.getJwtSignatureSigningConfiguration() != null) {
+                configs.add(jsonSecConfig.getJwtSignatureSigningConfiguration());                
+            }
+            if (pc instanceof final OIDCAuthenticationRelyingPartyProfileConfiguration oidcRpConfig) {
+                tokenEndpointAuthMethod = oidcRpConfig.getTokenEndpointAuthMethod(PRC_LOOKUP.apply(input));
+            }      
+            // Check for a per-profile default (relying party independent) config.
+            final RelyingPartyConfiguration rpConfig = rpc.getConfiguration();
+            if (rpConfig != null) {
+            	final SecurityConfiguration defaultConfig = rpConfig.getSecurityConfiguration(PRC_LOOKUP.apply(input));
+            	if (defaultConfig instanceof final JSONSecurityConfiguration jsonSecConfig
+                        && jsonSecConfig.getJwtSignatureSigningConfiguration() != null) {
+                    configs.add(jsonSecConfig.getJwtSignatureSigningConfiguration());
+                }
+            }
+        }
+
+        if (tokenEndpointAuthMethod == null) {
+            log.trace("Token endpoint client authentication method can not be found");
+            return CollectionSupport.emptyList();
+        }
+
+        final ClientAuthenticationMethod method = new ClientAuthenticationMethod(tokenEndpointAuthMethod);        
+        final List<SignatureSigningConfiguration> configsFiltered = new ArrayList<>();
+        
+        // Filter algorithms based on the type of JWT client authentication method chosen.
+        if (method.equals(ClientAuthenticationMethod.CLIENT_SECRET_JWT)) {            
+            for (final SignatureSigningConfiguration config : configs) {            
+                final List<String> filteredForMethodAlgs = filterAlgorithmsAgainstFamily(JWSAlgorithm.Family.HMAC_SHA, 
+                        config.getSignatureAlgorithms());
+                configsFiltered.add(createSignatureSigningConfiguration(config, filteredForMethodAlgs));           
+            }         
+        } else if (method.equals(ClientAuthenticationMethod.PRIVATE_KEY_JWT)) {
+            for (final SignatureSigningConfiguration config : configs) {                
+                final List<String> filteredForMethodAlgs = filterAlgorithmsAgainstFamily(JWSAlgorithm.Family.SIGNATURE, 
+                        config.getSignatureAlgorithms());
+                configsFiltered.add(createSignatureSigningConfiguration(config, filteredForMethodAlgs));
+            }  
+        }
+
+        return Collections.unmodifiableList(configsFiltered);
+    }
+    
+    /**
+     * Filter out any algorithms in the list that are not from the given algorithm family.  
+     * 
+     * @param algFamily the algorithm family to filter on
+     * @param algorithms the algorithms to filter
+     * 
+     * @return a filtered list of algorithms
+     */
+    @Nonnull @NonnullElements @NotLive @Unmodifiable private List<String> filterAlgorithmsAgainstFamily(
+            @Nonnull final JWSAlgorithm.Family algFamily, @Nullable final List<String> algorithms) {
+        
+        if (algorithms == null) {
+            return CollectionSupport.emptyList();
+        }
+        
+        final List<String> filtered = algorithms.stream()
+            .filter(Objects::nonNull).filter(Predicate.not(String::isEmpty)).map(JWSAlgorithm::parse)
+            .filter(algFamily::contains).map(Algorithm::getName).toList();
+        return Collections.unmodifiableList(filtered);
+    }
+
+    /**
+     * Create a copy of the signature signing configuration given, but replace the algorithms with those input.
+     * 
+     * @param signingConfig the signature signing configuration to copy
+     * @param algorithms the algorithms to add into the copied configuration
+     * 
+     * @return a copied signature signing configuration with the algorithms input
+     */
+    @Nonnull private BasicSignatureSigningConfiguration createSignatureSigningConfiguration(
+            @Nonnull final SignatureSigningConfiguration signingConfig, @Nonnull final List<String> algorithms) {
+        final BasicSignatureSigningConfiguration config = new BasicSignatureSigningConfiguration();
+        config.setExcludedAlgorithms(signingConfig.getExcludedAlgorithms());
+        config.setIncludedAlgorithms(signingConfig.getIncludedAlgorithms());
+        config.setIncludeExcludePrecedence(signingConfig.getIncludeExcludePrecedence());
+        config.setSigningCredentials(signingConfig.getSigningCredentials());
+        config.setIncludeMerge(signingConfig.isIncludeMerge());
+        config.setExcludeMerge(signingConfig.isExcludeMerge());
+        config.setSignatureAlgorithms(algorithms);
+        return config;
+    }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/AbstractTokenResponseLookupStrategy.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/AbstractTokenResponseLookupStrategy.java
new file mode 100644
index 00000000..4239fab3
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/AbstractTokenResponseLookupStrategy.java
@@ -0,0 +1,64 @@
+/*
+ * 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.oidc.security.jwt.claims.impl;
+
+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 net.shibboleth.oidc.profile.context.AccessTokenResponseContext;
+import net.shibboleth.shared.logic.Constraint;
+
+/** Base class for looking up the token response context.*/
+public abstract class AbstractTokenResponseLookupStrategy {
+    
+    /** Strategy used to locate the {@link AccessTokenResponseContext} to extract the id_token from.*/
+    @Nonnull 
+    private final Function<ProfileRequestContext, AccessTokenResponseContext> tokenResponseContextLookupStrategy;
+    
+    /** Constructor.*/
+    protected AbstractTokenResponseLookupStrategy() {
+        tokenResponseContextLookupStrategy =
+                new ChildContextLookup<>(AccessTokenResponseContext.class, true).compose(
+                        new InboundMessageContextLookup()); 
+    }
+    
+   /**
+    * 
+    * Constructor.
+    *
+    * @param strategy the AccessTokenResponseContext lookup strategy to use.
+    */
+    protected AbstractTokenResponseLookupStrategy(
+           @Nonnull final Function<ProfileRequestContext, AccessTokenResponseContext> strategy) {
+       tokenResponseContextLookupStrategy = 
+               Constraint.isNotNull(strategy, "AccessTokenResponseContext lookup strategy can not be null");
+    }
+   
+    /**
+     * Get the token response context lookup strategy.
+     * 
+     * @return the lookup strategy
+     */
+    @Nonnull 
+    protected Function<ProfileRequestContext, AccessTokenResponseContext> getTokenResponseContextLookupStrategy() {
+        return tokenResponseContextLookupStrategy;
+    }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ExtraAudiencesLookupStrategy.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ExtraAudiencesLookupStrategy.java
new file mode 100644
index 00000000..3323a732
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ExtraAudiencesLookupStrategy.java
@@ -0,0 +1,67 @@
+/*
+ * 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.oidc.security.jwt.claims.impl;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.profile.config.OIDCIDTokenProducingProfileConfiguration;
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Locate additional accepted audiences from the ID Token profile config value.
+ */
+public class ExtraAudiencesLookupStrategy implements BiFunction<ProfileRequestContext, JWTClaimsSet, Set<String>> {
+    
+    /** Lookup function for relying party context. */
+    @Nonnull private final Function<ProfileRequestContext,RelyingPartyContext> relyingPartyContextLookupStrategy;
+    
+    /**
+     * 
+     * Constructor.
+     *
+     * @param strategy the strategy used to lookup a relying party context
+     */
+    public ExtraAudiencesLookupStrategy(@ParameterName(name = "relyingPartyContextLookupStrategy")
+            @Nonnull final Function<ProfileRequestContext,RelyingPartyContext> strategy) {
+        relyingPartyContextLookupStrategy =
+                Constraint.isNotNull(strategy, "RelyingPartyContext lookup strategy cannot be null");
+    }
+
+    @Override
+    public Set<String> apply(final ProfileRequestContext prc, final JWTClaimsSet claims) {
+        
+        final RelyingPartyContext rpc = relyingPartyContextLookupStrategy.apply(prc);
+        if (rpc != null) {
+            final ProfileConfiguration pc = rpc.getProfileConfig();
+            if (pc instanceof OIDCIDTokenProducingProfileConfiguration config) {
+                return config.getAdditionalAudiencesForIdToken(prc);
+            }
+        }
+        return Collections.emptySet();
+    }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/IDTokenFromResponseContextLookupStrategy.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/IDTokenFromResponseContextLookupStrategy.java
new file mode 100644
index 00000000..ced1fefd
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/IDTokenFromResponseContextLookupStrategy.java
@@ -0,0 +1,61 @@
+/*
+ * 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.oidc.security.jwt.claims.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWT;
+
+import net.shibboleth.oidc.profile.context.AccessTokenResponseContext;
+import net.shibboleth.shared.annotation.ParameterName;
+
+/** Function that extracts the id_token from the {@link AccessTokenResponseContext}.*/
+ at ThreadSafe
+public class IDTokenFromResponseContextLookupStrategy extends AbstractTokenResponseLookupStrategy 
+                implements Function<ProfileRequestContext, JWT> {
+    
+    /** Constructor.*/
+    public IDTokenFromResponseContextLookupStrategy() {
+        super(); 
+    }
+    
+   /**
+    * 
+    * Constructor.
+    *
+    * @param strategy the AccessTokenResponseContext lookup strategy to use.
+    */
+   public IDTokenFromResponseContextLookupStrategy(@Nonnull @ParameterName(name="accessTokenContextLookupStrategy") 
+                           final Function<ProfileRequestContext, AccessTokenResponseContext> strategy) {
+       super(strategy);
+   }
+
+    @Override
+    @Nullable public JWT apply(@Nullable final ProfileRequestContext prc) {
+        final AccessTokenResponseContext tokenContext = getTokenResponseContextLookupStrategy().apply(prc);
+        final var tokenResponse = tokenContext != null ?  tokenContext.getTokenResponse() : null;
+        if (tokenResponse == null || tokenResponse.getTokens() == null) {
+            return null;
+        }
+        return tokenResponse.getOIDCTokens().getIDToken();
+    }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ManyValuesIntegerComparisonPredicate.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ManyValuesIntegerComparisonPredicate.java
new file mode 100644
index 00000000..0aba65a6
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ManyValuesIntegerComparisonPredicate.java
@@ -0,0 +1,27 @@
+/*
+ * 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.oidc.security.jwt.claims.impl;
+
+import java.util.function.IntPredicate;
+
+/** Predicate that returns true if the test integer is greater than 1.*/
+public class ManyValuesIntegerComparisonPredicate implements IntPredicate {
+
+    @Override
+    public boolean test(final int value) {
+        return value > 1;
+    }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/MaxAgeLookupFunction.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/MaxAgeLookupFunction.java
new file mode 100644
index 00000000..59363028
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/MaxAgeLookupFunction.java
@@ -0,0 +1,109 @@
+/*
+ * 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.oidc.security.jwt.claims.impl;
+
+import java.time.Duration;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidc.profile.config.OIDCAuthenticationProfileConfiguration;
+import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunction;
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.logic.Constraint;
+
+/** 
+ * Locate the maximum authentication age from the authentication request (first) or profile configuration (second). 
+ * Returning a default value if neither are found.
+ */
+public class MaxAgeLookupFunction extends AbstractRelyingPartyLookupFunction<Duration> {
+    
+    /** Default max authentication age if none can be found on the profile context.*/
+    @Nonnull private final Duration maxAgeDefault;
+    
+    /** 
+     * Strategy used to locate the {@link OIDCAuthenticationRequest}. 
+     * Defaults to the outbound message context of the PRC. 
+     */
+    @Nonnull private Function<ProfileRequestContext, OIDCAuthenticationRequest> authenticationRequestLookupStrategy;
+    
+    /**
+     * Constructor.
+     *
+     * @param defaultAge the default value to use for maximum authentication age
+     */
+    public MaxAgeLookupFunction(
+            @ParameterName(name = "maxAgeDefault") @Nonnull final Duration defaultAge) {
+        maxAgeDefault = Constraint.isNotNull(defaultAge, "Max Age default can not be null");
+        
+        authenticationRequestLookupStrategy = prc -> {
+            final MessageContext msgCtx = prc.getOutboundMessageContext();
+            if (msgCtx != null && msgCtx.getMessage() != null &&
+                    msgCtx.getMessage() instanceof final OIDCAuthenticationRequest request) {
+                return request;
+            }
+            return null;
+        };
+    }
+    
+    /**
+     * Set the authentication request lookup strategy to use.
+     * 
+     * @param strategy the strategy
+     */
+    public void setAuthenticationRequestLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, OIDCAuthenticationRequest> strategy) {
+        authenticationRequestLookupStrategy = Constraint.isNotNull(strategy,
+                "AuthenticationRequestLookupStrategy can not be null");
+    }
+
+    @Override
+    @Nonnull
+    public Duration apply(@Nullable final ProfileRequestContext input) {
+        
+        // Max_age from authentication request is authoritative over that from the profile config. Although if it 
+        // exists in the profile config it should have already been set on the authentication request.
+        
+        final OIDCAuthenticationRequest authnRequest = authenticationRequestLookupStrategy.apply(input);
+        final Duration authnRequestMaxAge = authnRequest != null ? authnRequest.getMaxAge() : null;
+        if (authnRequestMaxAge != null) {
+            return authnRequestMaxAge;
+        }
+        
+        // Check one was not specified in the relying party context
+        
+        final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
+        if (rpc != null) {
+            final ProfileConfiguration pc = rpc.getProfileConfig();
+            if (pc instanceof final OIDCAuthenticationProfileConfiguration config){
+                   final Duration maxAge = config.getMaxAuthenticationAge(input);
+                   if (maxAge == null) {
+                       return maxAgeDefault;
+                   } else {
+                       return maxAge;
+                   }
+            }
+        }
+        return maxAgeDefault;
+    }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/SubFromIDTokenLookupFunction.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/SubFromIDTokenLookupFunction.java
new file mode 100644
index 00000000..a8ddad89
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/SubFromIDTokenLookupFunction.java
@@ -0,0 +1,88 @@
+/*
+ * 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.oidc.security.jwt.claims.impl;
+
+import java.text.ParseException;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+
+import net.shibboleth.oidc.profile.context.AccessTokenResponseContext;
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A function that pulls the subject 'sub' out of the id_token in the {@link AccessTokenResponseContext}.
+ */
+ at ThreadSafe
+public class SubFromIDTokenLookupFunction extends AbstractTokenResponseLookupStrategy
+                                    implements BiFunction<ProfileRequestContext, JWTClaimsSet, String> {
+
+    /** Class logger. */
+    @Nonnull
+    private final Logger log = LoggerFactory.getLogger(SubFromIDTokenLookupFunction.class);
+
+
+    /** Constructor. */
+    public SubFromIDTokenLookupFunction() {
+        super();
+    }
+
+    /**
+     * 
+     * Constructor.
+     *
+     * @param strategy the AccessTokenResponseContext lookup strategy to use.
+     */
+    public SubFromIDTokenLookupFunction(@Nonnull @ParameterName(name="accessTokenContextLookupStrategy") 
+                    final Function<ProfileRequestContext, AccessTokenResponseContext> strategy) {
+        super(strategy);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * The input claims set is ignored because it belongs to the JWT that is being tested. Here we lookup
+     * the 'sub' claim from the id_token in a different context.
+     */
+    @Override
+    @Nullable
+    public String apply(@Nullable final ProfileRequestContext prc, @Nullable final JWTClaimsSet claimsSet) {
+        final AccessTokenResponseContext tokenContext = getTokenResponseContextLookupStrategy().apply(prc);
+        final OIDCTokenResponse tokenResponse = tokenContext != null ?  tokenContext.getTokenResponse() : null;
+        if (tokenResponse == null || tokenResponse.getOIDCTokens().getIDToken() == null) {
+            return null;
+        }
+        try {
+            final JWTClaimsSet claims = tokenResponse.getOIDCTokens().getIDToken().getJWTClaimsSet();
+            if (claims != null && claims.getSubject() != null) {
+                return claims.getSubject();
+            }
+        } catch (final ParseException e) {
+            log.warn("Unable to parse id_token claims, can not extract subject", e);
+        }
+        return null;
+    }
+
+}
diff --git a/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/config/logic/UserInfoLookupPredicate.java b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/config/logic/UserInfoLookupPredicate.java
new file mode 100644
index 00000000..a8aa6b53
--- /dev/null
+++ b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/config/logic/UserInfoLookupPredicate.java
@@ -0,0 +1,49 @@
+/*
+ * 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.oidc.profile.config.logic;
+
+import java.util.function.Predicate;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidc.profile.config.OIDCAuthenticationRelyingPartyProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+
+/**
+ * Checks whether the UserInfo endpoint should be accessed to retrieve claims about the
+ * authenticated end-user. Defaults to true, unless overridden in the profile configuration.
+ */
+public class UserInfoLookupPredicate implements Predicate<ProfileRequestContext> {
+
+    @Override
+    public boolean test(@Nullable final ProfileRequestContext prc) {
+        if (prc == null) {
+            return true;
+        }
+        final RelyingPartyContext rpCtx = prc.getSubcontext(RelyingPartyContext.class);
+        if (rpCtx != null && rpCtx.getProfileConfig() != null &&
+                rpCtx.getProfileConfig() instanceof OIDCAuthenticationRelyingPartyProfileConfiguration rpConfig) {
+            return rpConfig.isRetrieveUserInfoEndpointClaims(prc);
+        }
+        
+        // Perform user info lookup by default if no config found
+        return true;
+        
+        
+    }
+
+}
diff --git a/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/config/navigate/ProviderMetadataStringListValueLookupFunction.java b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/config/navigate/ProviderMetadataStringListValueLookupFunction.java
new file mode 100644
index 00000000..2eb078a1
--- /dev/null
+++ b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/config/navigate/ProviderMetadataStringListValueLookupFunction.java
@@ -0,0 +1,83 @@
+/*
+ * 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.oidc.profile.config.navigate;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Fetches the value for the configured key as List of {@link String}s. May be {@code null} if the value is not found 
+ * or the given {@link OIDCProviderMetadata} is null.
+ */
+public class ProviderMetadataStringListValueLookupFunction implements Function<OIDCProviderMetadata, List<String>> {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(ProviderMetadataStringListValueLookupFunction.class);
+
+    /** The key for which to fetch the value for. */
+    @Nonnull private final String keyName;
+
+    /**
+     * Constructor.
+     *
+     * @param name The key for which to fetch the value for.
+     */
+    public ProviderMetadataStringListValueLookupFunction(@ParameterName(name = "keyName") @Nonnull final String name) {
+        keyName = Constraint.isNotEmpty(name, "The key name cannot be empty");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable public List<String> apply(@Nullable final OIDCProviderMetadata metadata) {
+        if (metadata == null) {
+            log.trace("No provider metadata available");
+            return null;
+        }
+        final Object value = metadata.toJSONObject().get(keyName);
+        if (value == null) {
+            log.trace("No value found for the key {}", keyName);
+            return null;
+        }
+        if (value instanceof final String strValue) {
+            return List.of(strValue);
+        }
+        if (value instanceof List) {
+            final List<?> valueAsList = (List<?>)value;
+            return Collections.unmodifiableList(valueAsList.stream()
+                    .filter(Objects::nonNull)
+                    .filter(String.class::isInstance)
+                    .map(String.class::cast)
+                    .filter(Predicate.not(String::isEmpty))
+                    .toList());
+        }
+        
+        return null;
+    }
+
+}
diff --git a/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/config/navigate/UserInfoHttpRequestMethodLookupStrategy.java b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/config/navigate/UserInfoHttpRequestMethodLookupStrategy.java
new file mode 100644
index 00000000..5eb41718
--- /dev/null
+++ b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/config/navigate/UserInfoHttpRequestMethodLookupStrategy.java
@@ -0,0 +1,45 @@
+/*
+ * 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.oidc.profile.config.navigate;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidc.profile.config.OIDCAuthenticationRelyingPartyProfileConfiguration;
+import net.shibboleth.oidc.profile.oauth2.config.OAuth2AuthorizationProfileConfiguration.HttpRequestMethod;
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunction;
+
+/** 
+ * Locate the HTTP request method to use for the UserInfo request. Returns {@link HttpRequestMethod#GET} if not 
+ * found on the profile configuration.
+ */
+public class UserInfoHttpRequestMethodLookupStrategy extends AbstractRelyingPartyLookupFunction<HttpRequestMethod> {
+    
+    @Override
+    @Nullable public HttpRequestMethod apply(final ProfileRequestContext input) {
+        final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
+        if (rpc != null) {
+            final ProfileConfiguration pc = rpc.getProfileConfig();
+            if (pc instanceof OIDCAuthenticationRelyingPartyProfileConfiguration rpConfig){
+                   return rpConfig.getUserInfoHttpRequestMethod(input);
+            }
+        }
+        return HttpRequestMethod.GET;
+    }
+
+}
diff --git a/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/AbstractAuthenticatableOIDCContext.java b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/AbstractAuthenticatableOIDCContext.java
new file mode 100644
index 00000000..9a41609b
--- /dev/null
+++ b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/AbstractAuthenticatableOIDCContext.java
@@ -0,0 +1,49 @@
+/*
+ * 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.oidc.profile.context;
+
+import org.opensaml.messaging.context.BaseContext;
+
+/**
+ * An abstract base class for subcontexts that carry information which may be authenticated. For example,
+ * tokens received over TLS where server TLS credential validation was performed and successful.
+ */
+public class AbstractAuthenticatableOIDCContext extends BaseContext {
+    
+    /** Flag indicating whether the information contained in this context has been authenticated. */
+    private boolean authenticated;
+
+    /**
+     * Gets the flag indicating whether the information contained in this context has been authenticated.
+     * 
+     * @return Returns the authenticated flag.
+     */
+    public boolean isAuthenticated() {
+        return authenticated;
+    }
+
+    /**
+     * Sets the flag indicating whether the information contained in this context has been authenticated.
+     * 
+     * @param flag The flag to set.
+     * 
+     * @return this
+     */
+    public AbstractAuthenticatableOIDCContext setAuthenticated(final boolean flag) {
+        authenticated = flag;
+        return this;
+    }
+
+}
diff --git a/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/AccessTokenResponseContext.java b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/AccessTokenResponseContext.java
new file mode 100644
index 00000000..3ac0dbca
--- /dev/null
+++ b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/AccessTokenResponseContext.java
@@ -0,0 +1,67 @@
+/*
+ * 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.oidc.profile.context;
+
+import java.time.Instant;
+
+import javax.annotation.Nullable;
+
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+
+/** 
+ * A context to hold an OIDC token request response. If authenticated, the Token was received over a TLS protected
+ * channel where TLS credential validation was performed and successful.
+ */
+public class AccessTokenResponseContext extends AbstractAuthenticatableOIDCContext {
+    
+    /** The OAuth 2.0 and OIDC token response.*/
+    @Nullable private OIDCTokenResponse tokenResponse;
+    
+    /** The time the token response was set onto this context.*/
+    @Nullable private Instant tokenResponseCreatedAt;
+    
+    
+    /**
+     * Set the OAuth 2.0 and OIDC Token Response.
+     * 
+     * @param token the token response
+     * 
+     * @return this
+     */
+    public AccessTokenResponseContext setTokenResponse(@Nullable final OIDCTokenResponse token) {
+        tokenResponse = token;
+        tokenResponseCreatedAt = Instant.now();
+        return this;
+    }
+    
+    /**
+     * Get the OAuth 2.0 and OIDC Token Response.
+     * 
+     * @return the token response
+     */
+    @Nullable public OIDCTokenResponse getTokenResponse() {
+        return tokenResponse;
+    }
+    
+    /**
+     * Get the time the token response was set onto this context.
+     * 
+     * @return the time the token response was set onto this context
+     */
+    @Nullable public Instant getTokenResponseCreatedAt() {
+        return tokenResponseCreatedAt;
+    }
+
+}
diff --git a/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/EndUserClaimsContext.java b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/EndUserClaimsContext.java
new file mode 100644
index 00000000..8cfc49bf
--- /dev/null
+++ b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/EndUserClaimsContext.java
@@ -0,0 +1,85 @@
+/*
+ * 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.oidc.profile.context;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
+
+import net.shibboleth.shared.logic.Constraint;
+
+/** A context to hold the final set of claims associated with an authenticated end-user.*/
+public class EndUserClaimsContext extends BaseContext {
+    
+    /** 
+     * The claims associated with the authenticated end-user.
+     * Often an aggregate of id_token and UserInfo claims
+     */
+    @Nullable private ClaimsSet endUserClaims;
+    
+    /** The set of unprocessed id_token claims as returned from an id_token source.*/
+    @Nullable private JWTClaimsSet unprocessedIdTokenClaims;    
+  
+    /**
+     * Get the claims about the authenticated end-user.
+     * 
+     * @return the claims.
+     */
+    @Nullable public ClaimsSet getEndUserClaims() {
+        return endUserClaims;
+    }
+    
+    /**
+     * Set the claims about the authenticated end-user.
+     * 
+     * @param claims the claims.
+     * @return this
+     */
+    public EndUserClaimsContext setEndUserClaims(@Nonnull final ClaimsSet claims) {
+        endUserClaims = Constraint.isNotNull(claims, "Claims can not be null");
+        return this;
+    }
+    
+    /**
+     * Set the id_token claims about the authenticated end-user as returned from the id_token
+     * endpoint e.g. from a successful Token Response.
+     * 
+     * <p>In contrast, the endUserClaims may contain both an aggregation of claims obtained from other
+     * sources e.g. the UserInfo endpoint, and a subset of the id_token claims e.g. only 'identity' claims
+     * and not 'validation' claims.</p>
+     *  
+     * @param claims the id_token claims
+     * 
+     * @return this
+     */
+    public EndUserClaimsContext setUnprocessedIdTokenClaims(@Nonnull final JWTClaimsSet claims) {
+        unprocessedIdTokenClaims = Constraint.isNotNull(claims,"ID Token claims can not be null");
+        return this;
+    }
+    
+    /**
+     * Get the unproccessed id_token claims.
+     * 
+     * @return the unprocessed id_token claims. 
+     */
+    @Nullable public JWTClaimsSet getUnprocessedIdTokenClaims() {
+        return unprocessedIdTokenClaims;
+    }
+
+}
diff --git a/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/OAuth2ClientContext.java b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/OAuth2ClientContext.java
new file mode 100644
index 00000000..21490d67
--- /dev/null
+++ b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/OAuth2ClientContext.java
@@ -0,0 +1,91 @@
+/*
+ * 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.oidc.profile.context;
+
+import java.net.URI;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+
+import net.shibboleth.oidc.profile.messaging.context.OIDCPeerEntityContext;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * A context to store information pertaining to the OAuth2 client (Relying Party) to use in communication
+ * with a OpenID Provider.
+ * 
+ * <p>Typically a subcontext under {@link OIDCPeerEntityContext}, as it relates to the client
+ * associated pairwise with the upstream OP peer.</p>
+ * 
+ * @parent {@link OIDCPeerEntityContext}
+ * @added During an OAuth 2.0 authentication request attempt
+ */
+public class OAuth2ClientContext extends BaseContext {
+    
+    /** The client_id.*/
+    @Nullable private String clientId;
+    
+    
+    /** An redirect URI that should take preference over any automatically computed.*/
+    @Nullable private URI redirectUriOverride;
+    
+   
+    /**
+     * Set the redirect_uri to use in place of any other.
+     * 
+     * @param override the redirect_uri
+     * 
+     * @return this
+     */
+    public OAuth2ClientContext setRedirectUriOverride(@Nullable final URI override) {
+        redirectUriOverride = override;
+        return this;
+    }
+    
+    /**
+     * Get the redirect_uri which should be used in place of any other.
+     * 
+     * @return the redirect_uri
+     */
+    public URI getRedirectUriOverride() {
+        return redirectUriOverride;
+    }
+    
+    /**
+     * Set the client_id.
+     * 
+     * @param id the client_id
+     * 
+     * @return this
+     */
+    public OAuth2ClientContext setClientId(@Nonnull @NotEmpty final String id) {
+        clientId = Constraint.isNotEmpty(id, "ClientID can not be null or empty");
+        return this;
+    }
+    
+    /**
+     * Get the client_id.
+     * 
+     * @return the client_id
+     */
+    public String getClientId() {
+        return clientId;
+    }
+    
+
+}
diff --git a/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/OIDCAuthnContext.java b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/OIDCAuthnContext.java
new file mode 100644
index 00000000..e70a71a6
--- /dev/null
+++ b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/OIDCAuthnContext.java
@@ -0,0 +1,94 @@
+/*
+ * 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.oidc.profile.context;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+import org.opensaml.messaging.decoder.MessageDecoder;
+import org.opensaml.messaging.handler.MessageHandler;
+import org.opensaml.profile.action.ProfileAction;
+
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Manages state during proxied OIDC authentication via a Spring Controller.
+ */
+public class OIDCAuthnContext extends BaseContext {
+    
+    /** Outbound message handler to run prior to encoding. */
+    @Nullable private MessageHandler outboundMessageHandler;
+    
+    /** Profile action to execute to produce outbound message response. */
+    @Nonnull private final ProfileAction encodeMessageAction;
+    
+    /** The function to use to obtain a decoder. */
+    @Nonnull private final Function<String,MessageDecoder> decoderFactory;
+    
+    /**
+     * Constructor.
+     *
+     * @param action message-encoding profile action
+     * @param factory the message descoder factory
+     */
+    public OIDCAuthnContext(@Nonnull final ProfileAction action,
+            @Nonnull final Function<String,MessageDecoder> factory) {
+        encodeMessageAction = Constraint.isNotNull(action, "Profile action cannot be null");
+        decoderFactory = Constraint.isNotNull(factory, "MessageDecoder factory cannot be null");
+    }
+    
+    /**
+     * Get the message-encoding profile action.
+     * 
+     * @return profile action
+     */
+    @Nonnull public ProfileAction getEncodeMessageAction() {
+        return encodeMessageAction;
+    }
+    
+    /**
+     * Get the outbound {@link MessageHandler} to run prior to encoding.
+     * 
+     * @return the outbound {@link MessageHandler}
+     */
+    @Nullable public MessageHandler getOutboundMessageHandler() {
+        return outboundMessageHandler;
+    }
+    
+    /**
+     * Set the outbound {@link MessageHandler} to run prior to encoding.
+     * 
+     * @param handler outbound {@link MessageHandler} to set
+     * 
+     * @return this context
+     */
+    @Nonnull public OIDCAuthnContext setOutboundMessageHandler(@Nullable final MessageHandler handler) {
+        outboundMessageHandler = handler;        
+        return this;
+    }
+    
+    /**
+     * Get the factory function to obtain message decoders.
+     * 
+     * @return factory function
+     */
+    @Nonnull public Function<String,MessageDecoder> getMessageDecoderFactory() {
+        return decoderFactory;
+    }
+
+}
diff --git a/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/OutboundMessageHandlerContext.java b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/OutboundMessageHandlerContext.java
new file mode 100644
index 00000000..0c42af1d
--- /dev/null
+++ b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/OutboundMessageHandlerContext.java
@@ -0,0 +1,69 @@
+/*
+ * 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.oidc.profile.context;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+
+import net.shibboleth.shared.logic.Constraint;
+
+/** 
+ * A context to stash controller parameters for use by message handlers.
+ * For example, the OIDC RP preEncodeMessageHandlers.
+ */
+public class OutboundMessageHandlerContext extends BaseContext {
+    
+    /** The spring webflow key.*/
+    @Nullable private String webflowKey;
+
+    /**
+     * Convenience constructor.
+     *
+     * @param key the swf key
+     */
+    public OutboundMessageHandlerContext(@Nonnull final String key) {
+        super();       
+        webflowKey = Constraint.isNotNull(key, "Spring Webflow Key can not be null");
+    }
+    
+    /** Constructor to allow no-arg construction.*/
+    public OutboundMessageHandlerContext() {
+        // Do nothing
+    }
+    
+    /**
+     * Set the Spring Webflow execution key.
+     *  
+     * @param key the swf execution key
+     * 
+     * @return this
+     */
+    public OutboundMessageHandlerContext setWebflowKey(@Nonnull final String key) {
+        webflowKey = Constraint.isNotNull(key, "Spring Webflow Key can not be null");
+        return this;
+    }
+
+    /**
+     * Get the Spring Webflow execution key.
+     * 
+     * @return Returns the webflowKey.
+     */
+    @Nullable public String getWebflowKey() {
+        return webflowKey;
+    } 
+
+}
diff --git a/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/UserInfoResponseContext.java b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/UserInfoResponseContext.java
new file mode 100644
index 00000000..797c7f91
--- /dev/null
+++ b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/UserInfoResponseContext.java
@@ -0,0 +1,48 @@
+/*
+ * 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.oidc.profile.context;
+
+import javax.annotation.Nullable;
+
+import com.nimbusds.openid.connect.sdk.UserInfoSuccessResponse;
+
+/** A context to hold the response from the UserInfo endpoint.*/
+public class UserInfoResponseContext  extends AbstractAuthenticatableOIDCContext {
+    
+    /** The UserInfo response.*/
+    @Nullable private UserInfoSuccessResponse userInfo;
+    
+    /**
+     * Get the user info response.
+     * 
+     * @return the user info.
+     */
+    @Nullable public UserInfoSuccessResponse getUserInfo() {
+        return userInfo;
+    }
+    
+    /**
+     * Set the user info response.
+     * 
+     * @param info the user info response.
+     * 
+     * @return this
+     */
+    public UserInfoResponseContext setUserInfo(@Nullable final UserInfoSuccessResponse info) {
+        userInfo = info;
+        return this;
+    }
+
+}
diff --git a/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/navigate/AbstractTokenResponseLookupStrategy.java b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/navigate/AbstractTokenResponseLookupStrategy.java
new file mode 100644
index 00000000..0a28dde9
--- /dev/null
+++ b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/navigate/AbstractTokenResponseLookupStrategy.java
@@ -0,0 +1,64 @@
+/*
+ * 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.oidc.profile.context.navigate;
+
+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 net.shibboleth.oidc.profile.context.AccessTokenResponseContext;
+import net.shibboleth.shared.logic.Constraint;
+
+/** Base class for looking up the token response context.*/
+public abstract class AbstractTokenResponseLookupStrategy {
+    
+    /** Strategy used to locate the {@link AccessTokenResponseContext} to extract the id_token from.*/
+    @Nonnull 
+    private final Function<ProfileRequestContext, AccessTokenResponseContext> tokenResponseContextLookupStrategy;
+    
+    /** Constructor.*/
+    protected AbstractTokenResponseLookupStrategy() {
+        tokenResponseContextLookupStrategy =
+                new ChildContextLookup<>(AccessTokenResponseContext.class, true).compose(
+                        new InboundMessageContextLookup()); 
+    }
+    
+   /**
+    * 
+    * Constructor.
+    *
+    * @param strategy the AccessTokenResponseContext lookup strategy to use.
+    */
+    protected AbstractTokenResponseLookupStrategy(
+           @Nonnull final Function<ProfileRequestContext, AccessTokenResponseContext> strategy) {
+       tokenResponseContextLookupStrategy = 
+               Constraint.isNotNull(strategy, "AccessTokenResponseContext lookup strategy can not be null");
+    }
+   
+    /**
+     * Get the token response context lookup strategy.
+     * 
+     * @return the lookup strategy
+     */
+    @Nonnull 
+    protected Function<ProfileRequestContext, AccessTokenResponseContext> getTokenResponseContextLookupStrategy() {
+        return tokenResponseContextLookupStrategy;
+    }
+
+}
diff --git a/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/navigate/AccessTokenLookupStrategy.java b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/navigate/AccessTokenLookupStrategy.java
new file mode 100644
index 00000000..4411aec4
--- /dev/null
+++ b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/navigate/AccessTokenLookupStrategy.java
@@ -0,0 +1,61 @@
+/*
+ * 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.oidc.profile.context.navigate;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.oauth2.sdk.token.AccessToken;
+
+import net.shibboleth.oidc.profile.context.AccessTokenResponseContext;
+import net.shibboleth.shared.annotation.ParameterName;
+
+/** Function that extracts the access_token from the {@link AccessTokenResponseContext}.*/
+ at ThreadSafe
+public class AccessTokenLookupStrategy extends AbstractTokenResponseLookupStrategy 
+                                        implements Function<ProfileRequestContext, AccessToken> {
+   
+    /** Constructor.*/
+    public AccessTokenLookupStrategy() {
+        super(); 
+    }
+    
+   /**
+    * 
+    * Constructor.
+    *
+    * @param strategy the AccessTokenResponseContext lookup strategy to use.
+    */
+   public AccessTokenLookupStrategy(@Nonnull @ParameterName(name="accessTokenContextLookupStrategy") 
+                           final Function<ProfileRequestContext, AccessTokenResponseContext> strategy) {
+       super(strategy);
+   }
+
+    @Override
+    @Nullable public AccessToken apply(@Nullable final ProfileRequestContext prc) {
+        final AccessTokenResponseContext tokenContext = getTokenResponseContextLookupStrategy().apply(prc);
+        final var tokenResponse = tokenContext != null ?  tokenContext.getTokenResponse() : null;
+        if (tokenResponse == null || tokenResponse.getTokens() == null) {
+            return null;
+        }
+        return tokenResponse.getTokens().getAccessToken();
+    }
+
+}
diff --git a/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/navigate/ClientIDFromOAuth2ClientContextFunction.java b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/navigate/ClientIDFromOAuth2ClientContextFunction.java
new file mode 100644
index 00000000..7f16168d
--- /dev/null
+++ b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/navigate/ClientIDFromOAuth2ClientContextFunction.java
@@ -0,0 +1,67 @@
+/*
+ * 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.oidc.profile.context.navigate;
+
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.profile.context.OAuth2ClientContext;
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * A function that pulls the client_id out of the {@link OAuth2ClientContext}.
+ */
+ at ThreadSafe
+public class ClientIDFromOAuth2ClientContextFunction 
+                implements BiFunction<ProfileRequestContext, JWTClaimsSet, String> {
+
+    /** Lookup strategy that to return a {@link OAuth2ClientContext}. */
+    @Nonnull 
+    private final Function<ProfileRequestContext, OAuth2ClientContext> oauth2ClientContextLookupStrategy;
+
+    /**
+     * 
+     * Constructor.
+     *
+     * @param strgy the strategy used to locate the {@link OAuth2ClientContext}
+     */
+    public ClientIDFromOAuth2ClientContextFunction(
+            @Nonnull @ParameterName(name="oauth2ClientContextLookupStrategy")
+            final Function<ProfileRequestContext, OAuth2ClientContext> strgy) {
+        oauth2ClientContextLookupStrategy = Constraint.isNotNull(strgy, 
+                "OAuth2 client context lookup strategy cannot be null");
+    }
+
+
+    /** {@inheritDoc} */
+    @Override @Nullable
+    public String apply(@Nullable final ProfileRequestContext prc, @Nullable final JWTClaimsSet claimsSet) {
+        final OAuth2ClientContext clientContext = oauth2ClientContextLookupStrategy.apply(prc);
+        if (clientContext == null) {
+            return null;
+        }
+        return clientContext.getClientId();
+    }
+
+}
diff --git a/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/navigate/EncryptedIDTokenLookupStrategy.java b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/navigate/EncryptedIDTokenLookupStrategy.java
new file mode 100644
index 00000000..7cd83648
--- /dev/null
+++ b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/navigate/EncryptedIDTokenLookupStrategy.java
@@ -0,0 +1,83 @@
+/*
+ * 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.oidc.profile.context.navigate;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import com.nimbusds.jwt.EncryptedJWT;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+
+import net.shibboleth.oidc.profile.context.AccessTokenResponseContext;
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/** 
+ * Function that extracts the id_token from the {@link AccessTokenResponseContext} iff it is an {@link EncryptedJWT} 
+ * type. If not {@code null} is returned.
+ */
+ at ThreadSafe
+public class EncryptedIDTokenLookupStrategy extends AbstractTokenResponseLookupStrategy 
+                                                    implements Function<ProfileRequestContext, JWT>{
+    
+    /** Logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(EncryptedIDTokenLookupStrategy.class);
+    
+    /** Constructor.*/
+    public EncryptedIDTokenLookupStrategy() {
+        super();
+    }
+    
+   /**
+    * 
+    * Constructor.
+    *
+    * @param strategy the AccessTokenResponseContext lookup strategy to use.
+    */
+    public EncryptedIDTokenLookupStrategy(@ParameterName(name="accessTokenContextLookupStrategy")
+           @Nonnull final Function<ProfileRequestContext, AccessTokenResponseContext> strategy) {
+       super(strategy);
+    }
+    
+    @Override
+    @Nullable public JWT apply(@Nullable final ProfileRequestContext prc) {
+        final AccessTokenResponseContext tokenContext = getTokenResponseContextLookupStrategy().apply(prc);
+        final OIDCTokenResponse tokenResponse = tokenContext != null ? tokenContext.getTokenResponse() : null;
+        if (tokenResponse == null) {
+            return null;
+        }
+        final JWT token = tokenResponse.getOIDCTokens().getIDToken();
+        if (token instanceof EncryptedJWT) {
+            log.trace("EncryptedIDToken Lookup: ID Token is encrypted using algorithm '{}'", 
+                    token.getHeader().getAlgorithm());
+            return token;
+        } else if (token instanceof SignedJWT){
+            log.trace("EncryptedIDToken Lookup: ID Token is signed and not encrypted, nothing to return");
+            return null;
+        } else {
+            log.trace("EncryptedIDToken Lookup: ID Token is neither signed nor encrypted, nothing to return");
+            return null;
+        }
+    }
+
+}
diff --git a/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/navigate/IDTokenInAccessTokenUpdateStrategy.java b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/navigate/IDTokenInAccessTokenUpdateStrategy.java
new file mode 100644
index 00000000..d3b812b6
--- /dev/null
+++ b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/navigate/IDTokenInAccessTokenUpdateStrategy.java
@@ -0,0 +1,88 @@
+/*
+ * 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.oidc.profile.context.navigate;
+
+import java.util.function.BiConsumer;
+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 com.nimbusds.jwt.JWT;
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+
+import net.minidev.json.JSONObject;
+import net.shibboleth.oidc.profile.context.AccessTokenResponseContext;
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/** Consumer to update the id_token in the {@link AccessTokenResponseContext}.*/
+public class IDTokenInAccessTokenUpdateStrategy implements BiConsumer<ProfileRequestContext, JWT> {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(IDTokenInAccessTokenUpdateStrategy.class);
+    
+    /** Strategy used to look up the {@link AccessTokenResponseContext} to set id_token on. */
+    @Nonnull private final Function<ProfileRequestContext, AccessTokenResponseContext> 
+            tokenResponseContextLookupStrategy;
+    
+    /**
+     * 
+     * Constructor.
+     *
+     * @param strategy the strategy used look up the {@link AccessTokenResponseContext}.
+     */
+    public IDTokenInAccessTokenUpdateStrategy(@ParameterName(name="accessTokenContextLookupStrategy") final
+        Function<ProfileRequestContext, AccessTokenResponseContext> strategy) {
+        
+        tokenResponseContextLookupStrategy = 
+                Constraint.isNotNull(strategy, "accessTokenContextLookupStrategy can not be null");
+    }
+    
+    /** Constructor.*/
+    public IDTokenInAccessTokenUpdateStrategy() {
+        tokenResponseContextLookupStrategy =
+                new ChildContextLookup<>(AccessTokenResponseContext.class, true).compose(
+                        new InboundMessageContextLookup()); 
+    }
+
+    @Override
+    public void accept(final ProfileRequestContext profileRequestContext, final JWT idToken) {
+        
+        final AccessTokenResponseContext context = 
+                tokenResponseContextLookupStrategy.apply(profileRequestContext);  
+        final OIDCTokenResponse tokenReponse = context != null ? context.getTokenResponse() : null;
+        if (context != null && tokenReponse != null) {            
+            try {
+                // Create a new token response with the update id_token from the existing response
+                final JSONObject jsonToken = tokenReponse.toJSONObject();
+                jsonToken.put("id_token", idToken.serialize());
+                context.setTokenResponse(OIDCTokenResponse.parse(jsonToken));
+            } catch (final ParseException e) {
+                log.warn("Unable to set id_token back onto access token response", e);
+            }
+        } else {
+            log.warn("Unable to set id_token back onto access token response context");
+        }
+        
+    }
+
+}
diff --git a/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/navigate/IDTokenJOSEHeaderLookupStrategy.java b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/navigate/IDTokenJOSEHeaderLookupStrategy.java
new file mode 100644
index 00000000..2d0a15f5
--- /dev/null
+++ b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/navigate/IDTokenJOSEHeaderLookupStrategy.java
@@ -0,0 +1,67 @@
+/*
+ * 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.oidc.profile.context.navigate;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jose.Header;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+
+import net.shibboleth.oidc.profile.context.AccessTokenResponseContext;
+import net.shibboleth.shared.annotation.ParameterName;
+
+/** Function that extracts the JWS JOSE header from the id_token inside the {@link AccessTokenResponseContext}.*/
+ at ThreadSafe
+public class IDTokenJOSEHeaderLookupStrategy extends AbstractTokenResponseLookupStrategy 
+                                            implements Function<ProfileRequestContext, JWSHeader> {
+    
+    /** Constructor.*/
+    public IDTokenJOSEHeaderLookupStrategy() {
+        super();
+    }
+    
+   /**
+    * 
+    * Constructor.
+    *
+    * @param strategy the AccessTokenResponseContext lookup strategy to use.
+    */
+   public IDTokenJOSEHeaderLookupStrategy(@Nonnull @ParameterName(name="accessTokenContextLookupStrategy") 
+                   final Function<ProfileRequestContext, AccessTokenResponseContext> strategy) {
+       super(strategy);
+   }
+
+    @Override
+    @Nullable public JWSHeader apply(@Nullable final ProfileRequestContext prc) {
+        final AccessTokenResponseContext tokenContext = getTokenResponseContextLookupStrategy().apply(prc);
+        final OIDCTokenResponse tokenResponse = tokenContext != null ? tokenContext.getTokenResponse() : null;
+        if (tokenResponse == null || tokenResponse.getOIDCTokens().getIDToken() == null) {
+            return null;
+        }
+        final Header header = tokenResponse.getOIDCTokens().getIDToken().getHeader();
+        if (header instanceof final JWSHeader jwsHeader) {
+            return jwsHeader;
+        }
+        return null;
+    }
+
+}
diff --git a/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/navigate/OIDCProviderMetadataFromOuboundPeerLookupStrategy.java b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/navigate/OIDCProviderMetadataFromOuboundPeerLookupStrategy.java
new file mode 100644
index 00000000..725808a2
--- /dev/null
+++ b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/context/navigate/OIDCProviderMetadataFromOuboundPeerLookupStrategy.java
@@ -0,0 +1,50 @@
+/*
+ * 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.oidc.profile.context.navigate;
+
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext;
+import net.shibboleth.oidc.profile.messaging.context.OIDCPeerEntityContext;
+
+/** 
+ * Lookup function to extract the {@link OIDCProviderMetadataContext} from the {@link OIDCPeerEntityContext} in the
+ * outbound message.
+ */
+public class OIDCProviderMetadataFromOuboundPeerLookupStrategy 
+                    implements Function<ProfileRequestContext, OIDCProviderMetadataContext>{
+
+    @Override
+    @Nullable public OIDCProviderMetadataContext apply(@Nullable final ProfileRequestContext prc) {
+        if (prc == null) {
+            return null;
+        }
+        final MessageContext outboundMsgContext = prc.getOutboundMessageContext();
+        if (outboundMsgContext == null) {
+            return null;
+        }
+        final OIDCPeerEntityContext peerContext = outboundMsgContext.getSubcontext(OIDCPeerEntityContext.class);
+        if (peerContext == null) {
+            return null;
+        }
+        return peerContext.getSubcontext(OIDCProviderMetadataContext.class);
+    }
+
+}
diff --git a/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/core/OAuthAuthorizationRequest.java b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/core/OAuthAuthorizationRequest.java
index 88a4b812..dc40caa2 100644
--- a/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/core/OAuthAuthorizationRequest.java
+++ b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/core/OAuthAuthorizationRequest.java
@@ -169,13 +169,24 @@ public class OAuthAuthorizationRequest {
     }
 
     /**
-     * Set the state. Optional. 
+     * Set the state token. Optional. 
      * 
      * @param theState The state to set.
      */
     public void setState(@Nullable final StateToken theState) {
         state = theState;
     }
+    
+    /**
+     * Set the state string component of the state token. Optional. 
+     * 
+     * @param theState The state to set.
+     */
+    public void setState(@Nullable final State theState) {
+        if (theState != null){
+            state = new StateToken(theState.getValue(), null);
+        }
+    }
 
     /**
      * Get the redirect_uri.
diff --git a/oidc-common-profile-impl/pom.xml b/oidc-common-profile-impl/pom.xml
index f597369e..92443963 100644
--- a/oidc-common-profile-impl/pom.xml
+++ b/oidc-common-profile-impl/pom.xml
@@ -122,7 +122,26 @@
             <artifactId>jakarta.servlet-api</artifactId>
             <scope>provided</scope>
         </dependency>
-
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.datatype</groupId>
+            <artifactId>jackson-datatype-jsr310</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-annotations</artifactId>
+            <scope>provided</scope>
+        </dependency>
         <!--  Test Dependencies -->
         <dependency>
             <groupId>${opensaml.groupId}</groupId>
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/AbstractJSONResponseDecoderFunction.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/AbstractJSONResponseDecoderFunction.java
new file mode 100644
index 00000000..0cd2fe6c
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/AbstractJSONResponseDecoderFunction.java
@@ -0,0 +1,70 @@
+/*
+ * 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.oidc.profile.decoding.impl;
+
+import javax.annotation.Nonnull;
+
+import org.apache.hc.core5.http.io.HttpClientResponseHandler;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.component.AbstractInitializableComponent;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Abstract class for JSON based Http client response decoders. 
+ * 
+ * <p>Note, any input stream obtained by the decoder MUST ensure the stream is closed.</p>
+ *
+ * @param <T> the return type of the function.
+ */
+public abstract class AbstractJSONResponseDecoderFunction<T> extends AbstractInitializableComponent 
+                                                                        implements HttpClientResponseHandler<T>{
+    
+    /** JSON object mapper. */
+    @NonnullAfterInit private ObjectMapper objectMapper;
+    
+    /**
+     * Set the JSON Object Mapper to use.
+     * 
+     * @param mapper the object mapper.
+     */
+    public void setObjectMapper(@Nonnull final ObjectMapper mapper) {
+    	checkSetterPreconditions();
+
+        objectMapper = Constraint.isNotNull(mapper, "ObjectMapper cannot be null");
+    }
+    
+    /**
+     * Get the object mapper.
+     * 
+     * @return the object mapper.
+     */
+    @NonnullAfterInit protected ObjectMapper getObjectMapper() {
+        return objectMapper;
+    }
+    
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        if (objectMapper == null) {
+            throw new ComponentInitializationException("objectMapper cannot be null");
+        }
+    }
+
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/AccessTokenResponseDecoder.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/AccessTokenResponseDecoder.java
new file mode 100644
index 00000000..0fc96489
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/AccessTokenResponseDecoder.java
@@ -0,0 +1,103 @@
+/*
+ * 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.oidc.profile.decoding.impl;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpStatus;
+import org.slf4j.Logger;
+import org.springframework.http.MediaType;
+import org.springframework.util.MimeType;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.nimbusds.oauth2.sdk.TokenErrorResponse;
+import com.nimbusds.oauth2.sdk.TokenResponse;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+
+import net.minidev.json.JSONObject;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/** 
+ * Default access token response decoder which converts a successful HTTP response into an
+ * {@link OIDCTokenResponse} and a unsuccessful response into an {@link TokenErrorResponse}. 
+ * Any decoding error is logged and {@code null} is returned.
+ */
+public class AccessTokenResponseDecoder extends AbstractJSONResponseDecoderFunction<TokenResponse> {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AccessTokenResponseDecoder.class);
+
+    /** {@inheritDoc} */
+    @Override
+    public TokenResponse handleResponse(final ClassicHttpResponse httpResponse) throws HttpException, IOException {
+        try {            
+            if (httpResponse == null) {
+                log.warn("HttpResponse was null, can not process response");
+                return null;
+            }
+            final HttpEntity entity = httpResponse.getEntity();
+            if (entity == null) {
+                log.warn("HTTP response did not contain an entity");
+                return null;
+            }
+            
+            final ContentType contentType = ContentType.parse(httpResponse.getEntity().getContentType());
+            if (contentType == null || contentType.getMimeType() == null) {
+                log.warn("HTTP response did not contain a content-type, must contain a content-type");
+                return null;
+            }
+            final String mimeType = contentType.getMimeType();
+            assert mimeType != null;
+            if (MediaType.APPLICATION_JSON.compareTo(MimeType.valueOf(mimeType)) != 0) {
+               log.warn("Wrong content type header, expected 'application/json' found '{}'", contentType.getMimeType());
+               return null;
+            }
+            
+            try (final InputStream input = httpResponse.getEntity().getContent()) {
+                if (input == null) {
+                    log.warn("HTTP response does not contain a message entity, nothing to decode");
+                    return null;
+                }
+                
+                final Map<String, Object> tokenResponseAsMap = getObjectMapper().readValue(
+                        input, new TypeReference<Map<String, Object>>() {});
+                if (log.isTraceEnabled()) {
+                    log.trace("Token Response: {}", tokenResponseAsMap);
+                }
+                final int httpStatusCode = httpResponse.getCode();
+                
+                if (httpStatusCode != HttpStatus.SC_OK) {
+                    return TokenErrorResponse.parse(new JSONObject(tokenResponseAsMap));                
+                }   
+               
+                return OIDCTokenResponse.parse(new JSONObject(tokenResponseAsMap));
+            }
+          
+        } catch (final Exception e) {
+            log.warn("Unable to decode response", e);
+            return null;
+        }
+    }
+
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/UserInfoResponseDecoder.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/UserInfoResponseDecoder.java
new file mode 100644
index 00000000..773e4d84
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/UserInfoResponseDecoder.java
@@ -0,0 +1,135 @@
+/*
+ * 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.oidc.profile.decoding.impl;
+
+import java.io.InputStream;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.HttpStatus;
+import org.slf4j.Logger;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.nimbusds.common.contenttype.ContentType;
+import com.nimbusds.jose.util.IOUtils;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTParser;
+import com.nimbusds.openid.connect.sdk.UserInfoErrorResponse;
+import com.nimbusds.openid.connect.sdk.UserInfoResponse;
+import com.nimbusds.openid.connect.sdk.UserInfoSuccessResponse;
+import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
+import com.nimbusds.openid.connect.sdk.claims.UserInfo;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+
+/** 
+ * A UserInfo response decoder. Supports both plain JSON Object and JWT responses. 
+ * 
+ * <p>Importantly,the decoder *must not ever* decode a JWT response as a plain response type, otherwise the signature 
+ * check may not be performed downstream - although other validation for the plain object type should. That is, we 
+ * can not rely solely on the content-type header in-case of content-type header injection attacks — the logic 
+ * that builds either the JWT or plain response should fail, or at least present an invalid UserInfo response token.
+ * Any decoding error is logged and {@code null} is returned.</p>
+ */
+public class UserInfoResponseDecoder extends AbstractJSONResponseDecoderFunction<UserInfoResponse> {
+    
+    /** The UserInfo response header that carries error information.*/
+    @Nonnull public static final String USERINFO_ERROR_RESPONSE_HEADER = "WWW-Authenticate";
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(UserInfoResponseDecoder.class);    
+    
+ // Checkstyle: CyclomaticComplexity|ReturnCount|MethodLength OFF
+    @Override
+    public UserInfoResponse handleResponse(@Nullable final ClassicHttpResponse httpResponse) {
+        
+        if (httpResponse == null) {
+            log.error("HttpResponse was null, can not process response");
+            return null;
+        }
+        
+        try {
+            final int httpStatusCode = httpResponse.getCode();
+            
+            if (httpStatusCode != HttpStatus.SC_OK) {  
+                final Header errorHeader = httpResponse.getHeader(USERINFO_ERROR_RESPONSE_HEADER);
+                
+                if (errorHeader != null) {
+                    return UserInfoErrorResponse.parse(errorHeader.getValue());
+                } else {
+                    log.warn("HTTP status code implies error response, but no error given");
+                    return null;
+                }
+                
+            } else {
+                // Response indicates success
+                final HttpEntity entity = httpResponse.getEntity();
+                if (entity == null) {
+                    log.warn("HTTP response did not contain a response entity, nothing to decode");
+                    return null;
+                }
+                final String contentTypeString = entity.getContentType();
+                if (contentTypeString == null) {
+                    log.warn("HTTP response did not contain a content-type, must contain a content-type");
+                    return null;
+                } 
+                
+                final ContentType contentType = ContentType.parse(contentTypeString);
+                if (contentType == null) {
+                    log.warn("HTTP response did not contain a valid content-type");
+                    return null;
+                }            
+                
+                // Is a JWT type or plain JSON object
+                if (ContentType.APPLICATION_JWT.matches(contentType)) {
+                    
+                    try (InputStream input = entity.getContent()) {
+                        final String content = IOUtils.readInputStreamToString(input);
+                        final JWT parsedJwt = JWTParser.parse(content);
+                        return new UserInfoSuccessResponse(parsedJwt);
+                    }
+                    
+                } else if (ContentType.APPLICATION_JSON.matches(contentType)){
+                    
+                    try (InputStream input = entity.getContent()) {
+                        final String content = IOUtils.readInputStreamToString(input);
+                        final Map<String, Object> claims = getObjectMapper().readValue(
+                                content, new TypeReference<Map<String, Object>>() {});
+                        final ClaimsSet claimsSet = new ClaimsSet();
+                        claimsSet.putAll(claims);          
+                        return new UserInfoSuccessResponse(new UserInfo(claimsSet.toJSONObject()));
+                    }
+                } 
+            }
+          
+        } catch (final IllegalArgumentException e) {
+            log.warn("Error creating UserInfo claims set", e);
+            return null;
+        } catch (final Exception e) {
+            log.warn("Unable to decode UserInfo response", e);
+            return null;
+        }
+        log.warn("Unknown UserInfo response type");
+        return null;
+        
+    }
+ // Checkstyle: CyclomaticComplexity|ReturnCount|MethodLength ON
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/encoding/impl/AbstractRequestEncoderFunction.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/encoding/impl/AbstractRequestEncoderFunction.java
new file mode 100644
index 00000000..6907d7ea
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/encoding/impl/AbstractRequestEncoderFunction.java
@@ -0,0 +1,195 @@
+/*
+ * 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.oidc.profile.encoding.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
+import org.slf4j.Logger;
+
+import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+import net.shibboleth.oidc.authn.context.OAuth2ClientAuthenticationContext;
+import net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext;
+import net.shibboleth.oidc.profile.messaging.context.OIDCPeerEntityContext;
+import net.shibboleth.shared.component.AbstractInitializableComponent;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/** Abstract request encoder function that pulls out various contexts and request/response messages.*/
+public abstract class AbstractRequestEncoderFunction extends AbstractInitializableComponent 
+                                implements Function<ProfileRequestContext, ClassicHttpRequest> {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractRequestEncoderFunction.class);
+    
+    /** Lookup strategy to locate the OP metadata to use.*/
+    @Nonnull private Function<ProfileRequestContext, OIDCProviderMetadataContext> providerMetadataLookupStrategy; 
+    
+    /** The strategy used to lookup or create the {@link OAuth2ClientAuthenticationContext}.*/
+    @Nonnull
+    private Function<ProfileRequestContext, OAuth2ClientAuthenticationContext> 
+                                            oauth2ClientAuthenticationContextLookupStrategy;
+
+    /** OIDC authentication response from upstream OP. */
+    @Nullable private AuthenticationSuccessResponse authnResponse;
+    
+    /** OIDC Metadata context. */
+    @Nullable private OIDCProviderMetadataContext providerMetadataContext;
+    
+    /** 
+     * The context used to store client authentication information for communication with a
+     * upstream OP.
+     */
+    @Nullable private OAuth2ClientAuthenticationContext clientAuthnContext;
+    
+    /** Constructor.*/
+    protected AbstractRequestEncoderFunction() {
+        providerMetadataLookupStrategy = new ChildContextLookup<>(OIDCProviderMetadataContext.class).compose(
+                new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
+                        new OutboundMessageContextLookup()));
+        
+        oauth2ClientAuthenticationContextLookupStrategy = 
+                new ChildContextLookup<>(OAuth2ClientAuthenticationContext.class).compose(
+                new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
+                        new OutboundMessageContextLookup()));
+    }
+    
+    /**
+     * Set the strategy to lookup the {@link OAuth2ClientAuthenticationContext}.
+     * 
+     * @param strgy the strategy.
+     */
+    public void setOAuth2ClientAuthenticationContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, OAuth2ClientAuthenticationContext> strgy) {
+    	checkSetterPreconditions();
+
+        oauth2ClientAuthenticationContextLookupStrategy = Constraint.isNotNull(strgy, 
+                "OAuth2 client context lookup strategy cannot be null");
+    }
+    
+    /**
+     * Set the lookup strategy to locate the OpenID providers metadata.
+     * 
+     * @param strategy the strategy.
+     */
+    public void setProviderMetadataLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, OIDCProviderMetadataContext> strategy) {
+    	checkSetterPreconditions();
+        
+        providerMetadataLookupStrategy = 
+                Constraint.isNotNull(strategy,"Provider metadata lookup strategy can not be null");
+    }
+
+    /**
+     * Returns the authentication response from the upstream OP.
+     * 
+     * @return the authentication response.
+     */
+    @Nullable protected AuthenticationSuccessResponse getAuthenticationResponse() {
+        return authnResponse;
+    }
+
+    /**
+     * Get the client authentication context.
+     * 
+     * @return the client context.
+     */
+    @Nullable protected OAuth2ClientAuthenticationContext getClientAuthenticationContext() {
+        return clientAuthnContext;
+    }
+    
+    /**
+     * Returns the OIDC provider metadata context.
+     * 
+     * @return The provider metadata context.
+     */
+    @Nullable protected OIDCProviderMetadataContext getProviderMetadataContext() {
+        return providerMetadataContext;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * <p>Creates the HTTP request. Any error in creating the request should return {@code null} to indicate 
+     * failure.</p>
+     */
+    @Override
+    @Nullable public ClassicHttpRequest apply(@Nullable final ProfileRequestContext profileRequestContext) {
+
+        if (profileRequestContext == null) {
+            log.error("Profile request context is null, unable to encode request");
+            return null;
+        }
+
+        final MessageContext inboundMessageCtx = profileRequestContext.getInboundMessageContext();
+        if (inboundMessageCtx == null) {
+            log.error("No inbound message context");
+            return null;
+        }
+        if (inboundMessageCtx.getMessage() == null) {
+            log.error("No inbound message");
+            return null;
+        }
+        
+        if (!(inboundMessageCtx.getMessage() instanceof AuthenticationSuccessResponse)) {
+            log.error("No inbound authentication success response");
+            return null;
+        }
+        authnResponse = (AuthenticationSuccessResponse)inboundMessageCtx.getMessage();
+        
+        providerMetadataContext = providerMetadataLookupStrategy.apply(profileRequestContext);
+        if (providerMetadataContext == null) {
+            log.error("No provider metadata context found for peer");
+            return null;
+        }
+        assert providerMetadataContext != null;
+        final var providerMetadata = providerMetadataContext.getProviderInformation();
+        if (providerMetadata == null) {
+            log.error("No provider metadata found for peer");
+            return null;
+        }
+        
+        clientAuthnContext = oauth2ClientAuthenticationContextLookupStrategy.apply(profileRequestContext);
+        if (clientAuthnContext == null) {
+            log.error("No OAuth 2.0 client authentication context found");
+            return null;
+        }
+        
+        return doApply(profileRequestContext, providerMetadata);
+
+    }
+
+    /**
+     * Encode a ClassicHttpRequest from the given context. Implementations should override this method.
+     * 
+     * @param profileRequestContext the profile request context.
+     * @param providerMetadata the provider metadata.
+     * 
+     * @return the request to execute.
+     */
+    @Nullable protected abstract ClassicHttpRequest doApply(@Nonnull final ProfileRequestContext profileRequestContext, 
+            @Nonnull final OIDCProviderMetadata providerMetadata);
+
+    
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/encoding/impl/AuthCodeTokenRequestEncoder.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/encoding/impl/AuthCodeTokenRequestEncoder.java
new file mode 100644
index 00000000..71c35a30
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/encoding/impl/AuthCodeTokenRequestEncoder.java
@@ -0,0 +1,113 @@
+/*
+ * 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.oidc.profile.encoding.impl;
+
+import java.net.URI;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.hc.client5.http.classic.methods.HttpUriRequest;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import com.nimbusds.jose.util.StandardCharset;
+import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
+import com.nimbusds.oauth2.sdk.AuthorizationGrant;
+import com.nimbusds.oauth2.sdk.TokenRequest;
+import com.nimbusds.oauth2.sdk.http.HTTPRequest;
+import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+import net.shibboleth.oidc.authn.context.OAuth2ClientAuthenticationContext;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/** 
+ * A token request encoder that builds an OAuth2.0 Access Token Request for an authorization_code grant and returns an
+ * {@link HttpUriRequest}. 
+ * */
+public class AuthCodeTokenRequestEncoder extends AbstractRequestEncoderFunction {
+    
+    /** Class logger. */
+    @Nonnull
+    private final Logger log = LoggerFactory.getLogger(AuthCodeTokenRequestEncoder.class);
+    
+    @Override
+    @Nullable public ClassicHttpRequest doApply(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final OIDCProviderMetadata providerMetadata) {
+
+        try {
+            final OAuth2ClientAuthenticationContext authnContext = getClientAuthenticationContext();
+            if (authnContext == null) {
+                log.warn("No client authentication context to base token request off");
+                return null;
+            }
+            final AuthenticationSuccessResponse authnResponse = getAuthenticationResponse();
+            if (authnResponse == null) {
+                log.warn("No authentication response from upstream OpenID Provider to base token request off");
+                return null;
+            }
+           // final var authnRequest = getAuthenticationRequest();
+           // if (authnRequest == null) {
+            //    log.warn("No authentication request to base token request off");
+            //    return null;
+            //}
+            
+            // If PKCE was set in the request (is not null) use it, else set it to null
+            final AuthorizationGrant codeGrant =
+                    new AuthorizationCodeGrant(authnResponse.getAuthorizationCode(), new URI("http://redirect/"));  //TODO REDIRECT_URI
+                           // authnRequest.getCodeVerifier() != null ? new CodeVerifier(authnRequest.getCodeVerifier()) 
+                            //        : null);
+            
+            final TokenRequest tokenRequest = new TokenRequest(providerMetadata.getTokenEndpointURI(),
+                            authnContext.getClientAuthentication(), codeGrant);
+          
+            final var httpRequest = tokenRequest.toHTTPRequest();
+            assert httpRequest != null;
+            return convertHttpRequest(httpRequest);
+            
+        } catch (final Exception e) {
+            log.warn("Unable to encode token request", e);
+        }
+        return null;
+    }
+    
+    /**
+     * Convert the internally used {@link HTTPRequest} to the externally presented {@link HttpUriRequest}.
+     * 
+     * @param request the HTTP request to convert
+     * 
+     * @return the convert HTTP request
+     */
+    @Nullable private ClassicHttpRequest convertHttpRequest(@Nonnull final HTTPRequest request) {
+        
+        if (request.getMethod() != HTTPRequest.Method.POST) {
+            // Should never happen as HTTPRequest should always use POST
+            log.warn("Token Request must use the HTTP POST method, is trying to use '{}'", request.getMethod());
+            return null;
+        }
+        final ClassicRequestBuilder rb = ClassicRequestBuilder.post().setUri(request.getURI()).setHeader(
+                "Content-Type", request.getEntityContentType().toString())
+                .setCharset(StandardCharset.UTF_8);
+        
+        request.getQueryParameters().forEach((k,v) -> v.stream().forEach(value -> rb.addParameter(k, value)));
+        if (request.getAuthorization() != null && !request.getAuthorization().isEmpty()) {
+            rb.addHeader("Authorization", request.getAuthorization());
+        }
+        return rb.build();    
+    }
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/encoding/impl/UserInfoRequestEncoder.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/encoding/impl/UserInfoRequestEncoder.java
new file mode 100644
index 00000000..fa95ada8
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/encoding/impl/UserInfoRequestEncoder.java
@@ -0,0 +1,200 @@
+/*
+ * 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.oidc.profile.encoding.impl;
+
+import java.net.URI;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.NotThreadSafe;
+
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
+import org.apache.hc.core5.net.URIBuilder;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.messaging.encoder.MessageEncodingException;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
+import org.slf4j.Logger;
+import org.springframework.http.HttpMethod;
+
+import com.nimbusds.jose.util.StandardCharset;
+import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+import net.shibboleth.oidc.profile.config.navigate.UserInfoHttpRequestMethodLookupStrategy;
+import net.shibboleth.oidc.profile.context.AccessTokenResponseContext;
+import net.shibboleth.oidc.profile.oauth2.config.OAuth2AuthorizationProfileConfiguration.HttpRequestMethod;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Encoder responsible for constructing an HTTP request to the OpenID Connect (OIDC) UserInfo endpoint.
+ * Supports either GET or POST requests.
+ */
+ at NotThreadSafe
+public class UserInfoRequestEncoder extends AbstractRequestEncoderFunction {
+    
+    /** The HTTPS scheme.*/
+    @Nonnull @NotEmpty private static final String HTTPS = "https";
+    
+    /** Class logger. */
+    @Nonnull
+    private final Logger log = LoggerFactory.getLogger(UserInfoRequestEncoder.class);
+    
+    /** Strategy used to look up the {@link AccessTokenResponseContext} to set the parameters for. */
+    @Nonnull private Function<ProfileRequestContext, AccessTokenResponseContext> 
+            tokenResponseContextLookupStrategy;
+   
+    /** Strategy used to look up the {@link HttpMethod} used for this request.*/
+    @Nonnull private Function<ProfileRequestContext, HttpRequestMethod> httpMethodLookupStrategy;
+    
+    /** Constructor.*/
+    public UserInfoRequestEncoder() {
+        tokenResponseContextLookupStrategy =
+                new ChildContextLookup<>(AccessTokenResponseContext.class, true).compose(
+                        new InboundMessageContextLookup());
+        
+        httpMethodLookupStrategy = new UserInfoHttpRequestMethodLookupStrategy();
+    }
+    
+    /**
+     * Set the strategy used to lookup the {@link AccessTokenResponseContext}.
+     * 
+     * @param strategy the strategy
+     */
+    public void setTokenResponseContextLookupStrategy(
+            final Function<ProfileRequestContext, AccessTokenResponseContext> strategy) {
+    	checkSetterPreconditions();
+
+        tokenResponseContextLookupStrategy = Constraint.isNotNull(strategy,
+                "tokenResponseContextLookupStrategy can not be null");
+    }
+    
+    /**
+     * Set the strategy used to lookup the HTTP method to use in this request.
+     * 
+     * @param strategy the strategy
+     */
+    public void setHttpMethodLookupStrategy(final Function<ProfileRequestContext, HttpRequestMethod> strategy) {
+    	checkSetterPreconditions();
+
+        httpMethodLookupStrategy = Constraint.isNotNull(strategy,
+                "httpMethodLookupStrategy can not be null");
+    }
+   
+    
+    @Override
+    @Nullable public ClassicHttpRequest doApply(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final OIDCProviderMetadata providerMetadata) {
+        
+        try {
+            final HttpRequestMethod requestMethod = httpMethodLookupStrategy.apply(profileRequestContext);
+            
+            final AccessTokenResponseContext responseCtx = 
+                    tokenResponseContextLookupStrategy.apply(profileRequestContext);
+            if (responseCtx == null) {
+                log.debug("No TokenResponseContext returned by lookup strategy");
+                return null;
+            }            
+            
+            final URI uri = new URIBuilder().setScheme(HTTPS)
+                    .setPort(providerMetadata.getUserInfoEndpointURI().getPort())
+                    .setHost(providerMetadata.getUserInfoEndpointURI().getHost())
+                    .setPath(providerMetadata.getUserInfoEndpointURI().getPath())
+                    .build();                 
+            
+            // Add headers and create request. 
+            ClassicRequestBuilder rb = null;
+            if (requestMethod == HttpRequestMethod.GET) {
+                rb = ClassicRequestBuilder.get().setUri(uri)
+                        .setHeader("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType())
+                        .setCharset(StandardCharset.UTF_8);
+                 
+                assert rb != null;
+                addBearerTokenToGet(rb, responseCtx);      
+            } else if (requestMethod == HttpRequestMethod.POST) {
+
+                rb = ClassicRequestBuilder.post().setUri(uri)
+                        .setHeader("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType())
+                        .setCharset(StandardCharset.UTF_8);
+
+                assert rb != null;
+                addBearerTokenToPost(rb, responseCtx);   
+            } else {
+                log.error("Unable to construct UserInfo request, unknown request method: {}", requestMethod);
+                return null;
+            }
+
+            final ClassicHttpRequest request = rb.build();                  
+            log.debug("UserInfo request URL '{}'",request);            
+            return request;
+            
+        } catch (final Exception e) {
+            log.warn("Unable to encode token request", e);
+        }
+        return null;
+    }
+    
+    /**
+     * Add the bearer token to the 'access_token' parameter, used when issuing HTTP POST requests.
+     * 
+     * @param rb the request builder to use.
+     * @param responseCtx the response context to find the access_token from.
+     * 
+     * @throws MessageEncodingException if there is an issue adding the bearer token to the 'access_token' parameter.
+     */
+    private void addBearerTokenToPost(@Nonnull final ClassicRequestBuilder rb, 
+        @Nonnull final AccessTokenResponseContext responseCtx) throws MessageEncodingException {
+    
+        final OIDCTokenResponse tokenResponse = responseCtx.getTokenResponse();
+        if (tokenResponse == null) {
+            throw new MessageEncodingException("No access token response found");
+        }
+        final BearerAccessToken bearer = tokenResponse.getTokens().getBearerAccessToken();
+        if (bearer == null) {
+            throw new MessageEncodingException("Access token was not Bearer type");
+        }
+        rb.addParameter("access_token", bearer.getValue());
+    }
+    
+    /** 
+     * Add the bearer token to the Authorization header, used when issuing HTTP GET requests.
+     * 
+     * @param rb the request builder to use.
+     * @param responseCtx the response context to find the access_token from.
+     * 
+     * @throws MessageEncodingException if there is an issue adding the bearer token to the Authorization header.
+     */
+    private void addBearerTokenToGet(@Nonnull final ClassicRequestBuilder rb, 
+            @Nonnull final AccessTokenResponseContext responseCtx) throws MessageEncodingException {
+        
+        final OIDCTokenResponse tokenResponse = responseCtx.getTokenResponse();
+        if (tokenResponse == null) {
+            throw new MessageEncodingException("No access token response found");
+        }
+        final BearerAccessToken bearer = tokenResponse.getTokens().getBearerAccessToken();
+        if (bearer == null) {
+            throw new MessageEncodingException("Access token was not Bearer type");
+        }
+        rb.addHeader("Authorization", bearer.toAuthorizationHeader());        
+    }
+
+
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/impl/InitializeOAuth2ClientAuthenticationContext.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/impl/InitializeOAuth2ClientAuthenticationContext.java
new file mode 100644
index 00000000..78994d55
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/impl/InitializeOAuth2ClientAuthenticationContext.java
@@ -0,0 +1,94 @@
+/*
+ * 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.oidc.profile.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.messaging.handler.AbstractMessageHandler;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import net.shibboleth.oidc.authn.context.OAuth2ClientAuthenticationContext;
+import net.shibboleth.oidc.profile.messaging.context.OIDCPeerEntityContext;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * An {@link AbstractMessageHandler action} that initializes an {@link OAuth2ClientAuthenticationContext} for later use.
+ * 
+ * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ * @post create an {@link OAuth2ClientAuthenticationContext}
+ */
+public class InitializeOAuth2ClientAuthenticationContext extends AbstractMessageHandler {
+
+    /** Class logger. */
+    @Nonnull
+    private final Logger log = LoggerFactory.getLogger(InitializeOAuth2ClientAuthenticationContext.class);
+    
+    /** 
+     * The strategy used to lookup or create the {@link OAuth2ClientAuthenticationContext} 
+     * for storing the client authentication.
+     */
+    @Nonnull private Function<MessageContext, OAuth2ClientAuthenticationContext> 
+                                                    oauth2ClientAuthenticationContextLookupStrategy;
+
+    
+    /** Constructor.*/
+    public InitializeOAuth2ClientAuthenticationContext() {       
+
+        // Default under the OIDC Peer Entity Context, create is true
+        oauth2ClientAuthenticationContextLookupStrategy  = 
+                new ChildContextLookup<>(OAuth2ClientAuthenticationContext.class, true).compose(
+                new ChildContextLookup<>(OIDCPeerEntityContext.class));
+    }
+
+    /**
+     * Set the strategy to lookup the {@link OAuth2ClientAuthenticationContext} 
+     * from the {@link ProfileRequestContext}.
+     * 
+     * @param strgy the strategy.
+     */
+    public void setOAuth2ClientAuthenticationContextLookupStrategy(
+            @Nonnull final Function<MessageContext, OAuth2ClientAuthenticationContext> strgy) {
+    	checkSetterPreconditions();
+
+        oauth2ClientAuthenticationContextLookupStrategy = Constraint.isNotNull(strgy, 
+                "OAuth2 client authentication context lookup strategy cannot be null");
+    }
+    
+    
+    @Override
+    protected void doInvoke(final MessageContext messageContext) throws MessageHandlerException {
+        
+        final OAuth2ClientAuthenticationContext context = 
+                oauth2ClientAuthenticationContextLookupStrategy.apply(messageContext);
+        
+        if (context == null) {
+            throw new MessageHandlerException("No OAuth2 client authentication context found or created");
+        }
+
+        log.debug("{} Initialized OAuth2 Client Authentication Context",getLogPrefix());
+    }
+    
+    
+    
+}

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


More information about the commits mailing list