[java-oidc-common] 08/20: JCOMOIDC-145 - Move authentication request handlers from the RP-Proxy into oidc-common

Codeberg noreply at shibboleth.net
Tue Feb 17 20:14:43 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/b3b1555fe5780ca026353d0c2ad7978514fdb1a7

commit b3b1555fe5780ca026353d0c2ad7978514fdb1a7
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Wed Oct 15 15:12:21 2025 +0100

    JCOMOIDC-145 - Move authentication request handlers from the RP-Proxy
    into oidc-common
    
     - Move the authn/authnz handlers across
    
    https://shibboleth.atlassian.net/browse/JCOMOIDC-145
---
 ...icationRequestParameterValueMessageHandler.java | 199 +++++++++++++++++++++
 ...uthenticationContextClassReferencesHandler.java |  57 ++++++
 .../messaging/handler/impl/AddDisplayHandler.java  |  56 ++++++
 .../handler/impl/AddEndpointURIHandler.java        |  54 ++++++
 .../impl/AddForceAuthenticationHandler.java        |  68 +++++++
 .../handler/impl/AddLoginHintHandler.java          |  48 +++++
 .../messaging/handler/impl/AddMaxAgeHandler.java   |  48 +++++
 .../messaging/handler/impl/AddNonceHandler.java    |  54 ++++++
 .../impl/AddPKCECodeVerifierAndChallenge.java      | 135 ++++++++++++++
 .../messaging/handler/impl/AddPromptHandler.java   |  53 ++++++
 .../handler/impl/AddRedirectURIHandler.java        |  52 ++++++
 .../handler/impl/AddRequestedClaimsHandler.java    |  63 +++++++
 .../messaging/handler/impl/AddResourceHandler.java |  53 ++++++
 .../handler/impl/AddResponseModeHandler.java       | 103 +++++++++++
 .../handler/impl/AddResponseTypeHandler.java       | 113 ++++++++++++
 .../messaging/handler/impl/AddScopesHandler.java   |  57 ++++++
 .../messaging/handler/impl/AddStateHandler.java    |  77 ++++++++
 .../handler/impl/AddUiLocalesHandler.java          |  66 +++++++
 .../handler/impl/BuildPlainRequestObjectJWT.java   |  63 +++++++
 .../messaging/handler/impl/PKCEOptions.java        |  72 ++++++++
 .../impl/SetAuthenticationRequestTimeHandler.java  |  47 +++++
 21 files changed, 1538 insertions(+)

diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AbstractAuthenticationRequestParameterValueMessageHandler.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AbstractAuthenticationRequestParameterValueMessageHandler.java
new file mode 100644
index 00000000..5b9aed72
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AbstractAuthenticationRequestParameterValueMessageHandler.java
@@ -0,0 +1,199 @@
+/*
+ * 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.messaging.handler.impl;
+
+import java.util.function.Function;
+
+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.handler.AbstractMessageHandler;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.opensaml.profile.context.navigate.ParentProfileRequestContextLookup;
+import org.slf4j.Logger;
+
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+import net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext;
+import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
+import net.shibboleth.oidc.profile.messaging.context.OIDCPeerEntityContext;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/** 
+ * Base class for message handlers that process and apply values of OpenID Connect authentication request parameters.
+ * 
+ * <p>
+ * This abstract class provides common functionality for locating:
+ * </p>
+ * <ul>
+ *   <li>the {@link OIDCAuthenticationRequest} associated with the current
+ *       {@link MessageContext},</li>
+ *   <li>the {@link OIDCProviderMetadata} describing the peer OpenID Provider,</li>
+ *   <li>and the parameter value to be extracted and validated against
+ *       the expected Java type.</li>
+ * </ul>
+ * </p>
+ * 
+ * @param <T> the authentication request parameter value type
+ */
+public abstract class AbstractAuthenticationRequestParameterValueMessageHandler<T> extends AbstractMessageHandler {
+    
+    /** Lookup function for parent ProfileRequestContext. */
+    @Nonnull protected static final ParentProfileRequestContextLookup<MessageContext> PRC_LOOKUP
+        = new ParentProfileRequestContextLookup<>();
+
+    /** Class logger. */
+    @Nonnull private final Logger log = 
+            LoggerFactory.getLogger(AbstractAuthenticationRequestParameterValueMessageHandler.class);
+    
+    /** Strategy used to locate the {@link OIDCAuthenticationRequest}.  */
+    @Nonnull private Function<MessageContext, OIDCAuthenticationRequest> authenticationRequestLookupStrategy;
+    
+    /** Lookup strategy to locate the OpenID Provider metadata to use.*/
+    @Nonnull private Function<MessageContext, OIDCProviderMetadataContext> providerMetadataLookupStrategy;
+    
+    @Nullable private Function<MessageContext, T> parameterValueLookupStrategy;
+    
+    /** The authentication request parameter value type.*/
+    @Nonnull private Class<T> type;
+    
+    /** The stashed {@link OIDCAuthenticationRequest}.*/
+    @NonnullBeforeExec private OIDCAuthenticationRequest authnRequest;  
+    
+    /** The stashed OpenID Provider metadata .*/
+    @NonnullBeforeExec private OIDCProviderMetadata providerMetadata;
+    
+    
+    /** Constructor.*/
+    protected AbstractAuthenticationRequestParameterValueMessageHandler(@Nonnull final Class<T> valueType) {
+        type = Constraint.isNotNull(valueType, "Authentication request parameter value type cannot be null");
+        authenticationRequestLookupStrategy = mc -> {
+            if (mc.getMessage() instanceof final OIDCAuthenticationRequest request) {
+                return request;
+            }
+            return null;
+        };
+        providerMetadataLookupStrategy = new ChildContextLookup<>(OIDCProviderMetadataContext.class).compose(
+                new ChildContextLookup<>(OIDCPeerEntityContext.class));  
+    }
+    
+    /**
+     * Get the authentication request.
+     * 
+     * @return the authentication request
+     */
+    @NonnullBeforeExec protected OIDCAuthenticationRequest getAuthenticationRequest() {
+        return authnRequest;
+    }
+    
+    /**
+     * Set the lookup strategy to locate the OpenID providers metadata.
+     * 
+     * @param strategy the strategy.
+     */
+    public void setProviderMetadataLookupStrategy(
+            @Nonnull final Function<MessageContext, OIDCProviderMetadataContext> strategy) {
+        checkSetterPreconditions();
+        
+        providerMetadataLookupStrategy = 
+                Constraint.isNotNull(strategy,"Provider metadata lookup strategy can not be null");
+    }
+    
+    /**
+     * Returns the OpenID Provider metadata. Should never be {@code null} after
+     * after {@code doPreExecute} has been called.
+     * 
+     * @return The provider metadata context.
+     */
+    @NonnullBeforeExec protected OIDCProviderMetadata getProviderMetadata() {
+        return providerMetadata;
+    }
+    
+    /**
+     * Set the parameter value lookup strategy used to find the value to set onto the authentication request.
+     * 
+     * @param strategy The parameter value lookup strategy to set.
+     */
+    public void setParameterValueLookupStrategy(@Nonnull final Function<MessageContext, T> strategy) {
+        checkSetterPreconditions();
+        parameterValueLookupStrategy = Constraint.isNotNull(strategy,
+                "ParameterValueLookupStrategy can not be null");
+    }
+    
+    /**
+     * Retrieves the parameter value or configuration options from the configured lookup strategy, 
+     * verifying at runtime that the result matches the type expected by the subclass.
+     *  
+     * @param context the message context to pass to the lookup function
+     * 
+     * @return the parameter value
+     * 
+     * @throws MessageHandlerException if the value is not the expected type
+     */
+    @Nullable protected T getParameterValue(final MessageContext context) 
+            throws MessageHandlerException {
+        final var localParameterValueLookupStrategy = parameterValueLookupStrategy;
+        if (localParameterValueLookupStrategy == null) {
+            return null;
+        }
+        final Object value = localParameterValueLookupStrategy.apply(context);
+        if (value == null) {
+            return null;
+        }
+        if (type.isInstance(value)) {
+            return type.cast(value);
+        }
+        throw new MessageHandlerException("Authentication request parameter value lookup returned the "
+                + "wrong value type");
+    }
+
+    /**
+     * Set the strategy used to locate the {@link OIDCAuthenticationRequest} to use.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setAuthenticationRequestLookupStrategy(
+            @Nonnull final Function<MessageContext, OIDCAuthenticationRequest> strategy) {
+    	checkSetterPreconditions();
+
+        authenticationRequestLookupStrategy =
+                Constraint.isNotNull(strategy, "AuthenticationRequestLookupStrategy lookup strategy cannot be null");
+    }
+    
+    @Override
+    protected boolean doPreInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+
+        authnRequest = authenticationRequestLookupStrategy.apply(messageContext);
+        if (authnRequest == null) {
+            throw new MessageHandlerException("OIDC authentication request is null");
+        }
+        final OIDCProviderMetadataContext providerMetadataContext = 
+                providerMetadataLookupStrategy.apply(messageContext);
+        if (providerMetadataContext == null) {
+            throw new MessageHandlerException("No provider metadata context found for peer");
+        }
+        providerMetadata = providerMetadataContext.getProviderInformation();
+        if (providerMetadata == null) {
+            throw new MessageHandlerException("No provider metadata found for peer");
+        }
+        
+        return super.doPreInvoke(messageContext);
+    }
+
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddAuthenticationContextClassReferencesHandler.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddAuthenticationContextClassReferencesHandler.java
new file mode 100644
index 00000000..416112c2
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddAuthenticationContextClassReferencesHandler.java
@@ -0,0 +1,57 @@
+/*
+ * 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.messaging.handler.impl;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import com.nimbusds.openid.connect.sdk.claims.ACR;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+
+/**
+ * A message handler that adds Authentication Context Class References (ACRs) to an authentication request.
+ *
+ * <p>If no ACRs are present in the request, nothing it set onto the request</p>
+ */
+public class AddAuthenticationContextClassReferencesHandler 
+                extends AbstractAuthenticationRequestParameterValueMessageHandler<List<ACR>> {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddAuthenticationContextClassReferencesHandler.class);
+    
+    /** Constructor.*/
+    public AddAuthenticationContextClassReferencesHandler() {
+        super((Class)List.class);
+    }
+    
+    @Override protected void doInvoke(@Nonnull final MessageContext messageContext) 
+            throws MessageHandlerException {
+        
+        final List<ACR> acrs = getParameterValue(messageContext);
+        if (acrs != null) {
+            log.trace("{} setting ACRs to '{}' ", getLogPrefix(), acrs);
+            getAuthenticationRequest().setAcrs(acrs);
+        } else {
+            log.trace("{} no ACRs requested", getLogPrefix());
+        }
+    }
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddDisplayHandler.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddDisplayHandler.java
new file mode 100644
index 00000000..feb7aa3f
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddDisplayHandler.java
@@ -0,0 +1,56 @@
+/*
+ * 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.messaging.handler.impl;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.openid.connect.sdk.Display;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+
+
+/** 
+ * Message handler that adds the optional 'display' request parameter. 
+ */
+public class AddDisplayHandler extends AbstractAuthenticationRequestParameterValueMessageHandler<String> {
+
+    /** Logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddDisplayHandler.class);
+    
+    /** Constructor. */
+    protected AddDisplayHandler() {
+        super(String.class);
+    }
+
+    @Override protected void doInvoke(@Nonnull final MessageContext messageContext) 
+            throws MessageHandlerException {   
+       
+       final String display = getParameterValue(messageContext);
+       if (StringSupport.trimOrNull(display) != null) {    
+           try {
+               getAuthenticationRequest().setDisplay(Display.parse(display));
+               log.trace("Adding 'display' request parameter value '{}'", display);
+            } catch (final ParseException e) {
+                throw new MessageHandlerException("Unable to add a 'display' value of: " + display);
+            }
+       }  
+    }
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddEndpointURIHandler.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddEndpointURIHandler.java
new file mode 100644
index 00000000..ebc91ad8
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddEndpointURIHandler.java
@@ -0,0 +1,54 @@
+/*
+ * 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.messaging.handler.impl;
+
+import java.net.URI;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+
+/** 
+ * A message handler that adds the authorization endpoint URI. If an authorization endpoint does
+ * not exist, an exception is thrown.
+ */
+public class AddEndpointURIHandler extends AbstractAuthenticationRequestParameterValueMessageHandler<URI> {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddEndpointURIHandler.class);
+    
+    /** Constructor.*/
+    public AddEndpointURIHandler() {
+        super(URI.class);
+    }
+    
+    @Override protected void doInvoke(@Nonnull final MessageContext messageContext) 
+            throws MessageHandlerException {
+        
+        final URI authzEndpoint = getParameterValue(messageContext);
+        if (authzEndpoint == null) {
+            throw new MessageHandlerException("OAuth 2.0 authorization endpoint URI not found in provider metadata");
+        }
+        
+        getAuthenticationRequest().setEndpointURI(authzEndpoint);
+        log.trace("{} Added authorization endpoint '{}' to authentication request",getLogPrefix(),
+                getAuthenticationRequest().getEndpointURI());
+    }
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddForceAuthenticationHandler.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddForceAuthenticationHandler.java
new file mode 100644
index 00000000..f2731170
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddForceAuthenticationHandler.java
@@ -0,0 +1,68 @@
+/*
+ * 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.messaging.handler.impl;
+
+import java.time.Duration;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.openid.connect.sdk.Prompt;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * An action that sets the 'prompt' parameter to 'login' and max_age to 0
+ * seconds, iff force authn was requested by the upstream SP (or is overridden
+ * in the profile config).
+ */
+public class AddForceAuthenticationHandler extends AbstractAuthenticationRequestParameterValueMessageHandler<Boolean> {
+
+    /** Class logger. */
+    @Nonnull
+    private final Logger log = LoggerFactory.getLogger(AddForceAuthenticationHandler.class);
+
+    /**
+     * Constructor.
+     */
+    protected AddForceAuthenticationHandler() {
+        super(Boolean.class);
+    }
+
+    @Override
+    protected void doInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+
+        final Boolean isForceAuthn = getParameterValue(messageContext);
+        if (isForceAuthn != null && isForceAuthn) {
+            log.trace("{} Setting prompt=login and max_age=0 (ForceAuthn) for OIDC AuthnRequest", getLogPrefix());
+            try {
+                getAuthenticationRequest().setPrompt(Prompt.parse(Prompt.Type.LOGIN.toString()));
+                getAuthenticationRequest().setMaxAge(Duration.ofSeconds(0));
+            } catch (final ParseException e) {
+                // This should never happen
+                throw new MessageHandlerException(
+                        "Unable to honour force-authn, " + "setting prompt to force-login as failed", e);
+            }
+        } else {
+            log.trace("{} No ForceAuthn requirement, so no prompt or max_age set", getLogPrefix());
+        }
+
+    }
+
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddLoginHintHandler.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddLoginHintHandler.java
new file mode 100644
index 00000000..54371548
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddLoginHintHandler.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.messaging.handler.impl;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+
+/** Message handler that adds the login_hint parameter.*/
+public class AddLoginHintHandler extends AbstractAuthenticationRequestParameterValueMessageHandler<String> {
+    
+    /** Logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddLoginHintHandler.class);
+    
+    public AddLoginHintHandler() {
+        super(String.class);
+    }
+
+    @Override
+    protected void doInvoke(final MessageContext messageContext) throws MessageHandlerException {
+        
+        final String loginHint = getParameterValue(messageContext);
+        
+        if (loginHint != null) {
+            log.trace("{} Added login_hint parameter '{}'", getLogPrefix(), loginHint);
+            getAuthenticationRequest().setLoginHint(loginHint);
+        }
+        
+    }
+
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddMaxAgeHandler.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddMaxAgeHandler.java
new file mode 100644
index 00000000..59b6d0c4
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddMaxAgeHandler.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.messaging.handler.impl;
+
+import java.time.Duration;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+
+/** Message handler that adds the max_age parameter.*/
+public class AddMaxAgeHandler extends AbstractAuthenticationRequestParameterValueMessageHandler<Duration> {
+
+    /** Logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddMaxAgeHandler.class);
+    
+    public AddMaxAgeHandler() {
+        super(Duration.class);
+    }
+
+    @Override protected void doInvoke(@Nonnull final MessageContext messageContext) 
+            throws MessageHandlerException {   
+       
+        final Duration maxAge = getParameterValue(messageContext);
+        
+        if (maxAge != null) {
+            log.trace("{} Added max_age parameter '{}'", getLogPrefix(), maxAge);
+            getAuthenticationRequest().setMaxAge(maxAge);
+        }  
+    }
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddNonceHandler.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddNonceHandler.java
new file mode 100644
index 00000000..eae74371
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddNonceHandler.java
@@ -0,0 +1,54 @@
+/*
+ * 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.messaging.handler.impl;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import com.nimbusds.openid.connect.sdk.Nonce;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/** 
+ * A message handler that adds a nonce from a lookup strategy to the authentication request.
+ * The nonce can be {@literal null}.
+ */
+public class AddNonceHandler extends AbstractAuthenticationRequestParameterValueMessageHandler<Nonce> {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddNonceHandler.class);
+    
+    /** Constructor.*/
+    public AddNonceHandler() {
+        super(Nonce.class);
+    }
+    
+    @Override protected void doInvoke(@Nonnull final MessageContext messageContext) 
+            throws MessageHandlerException {
+        
+        final Nonce nonce = getParameterValue(messageContext);
+        if (nonce != null) {
+            getAuthenticationRequest().setNonce(nonce);
+            log.trace("{} Added nonce '{}' to authentication request",getLogPrefix(),
+                    getAuthenticationRequest().getNonce());
+        }
+    }
+    
+    
+
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddPKCECodeVerifierAndChallenge.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddPKCECodeVerifierAndChallenge.java
new file mode 100644
index 00000000..f6acab08
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddPKCECodeVerifierAndChallenge.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.messaging.handler.impl;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import net.shibboleth.oidc.profile.core.OAuthAuthorizationRequest.CodeChallengeMethod;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.codec.Base64Support;
+import net.shibboleth.shared.codec.EncodingException;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Create an OAuth 2.0 PCKE code_verifier to use in the token request, and derives a code_challenge for immediate use in
+ * the authorization request.
+ */
+public class AddPKCECodeVerifierAndChallenge 
+        extends AbstractAuthenticationRequestParameterValueMessageHandler<PKCEOptions>  {
+    
+    /** Logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddPKCECodeVerifierAndChallenge.class);
+    
+    /** Constructor.*/
+    public AddPKCECodeVerifierAndChallenge() {
+        super(PKCEOptions.class);
+    }
+
+    @Override
+    protected void doInvoke(final MessageContext messageContext) throws MessageHandlerException {
+        
+        final PKCEOptions pkceOptions = getParameterValue(messageContext);
+        
+        if (pkceOptions != null && pkceOptions.isEnabled()) {
+            
+            log.trace("{} PKCE enabled, adding code_challenge to request", getLogPrefix());
+            
+            final CodeChallengeMethod method = 
+                    pkceOptions.isAllowPlain() ? CodeChallengeMethod.PLAIN :  CodeChallengeMethod.S256;
+            
+            final String codeVerifier = generateCodeVerifier(32);     
+            final String challenge = computeCodeChallenge(codeVerifier, method);
+            
+            if (log.isTraceEnabled()) {
+                log.trace("{} Created code verifier '...{}'", getLogPrefix(), 
+                        codeVerifier.substring(challenge.length()-3));
+                log.trace("{} Derived code challenge '...{}'", getLogPrefix(), 
+                        challenge.substring(challenge.length()-3));
+            }
+            getAuthenticationRequest().setCodeVerifier(codeVerifier);
+            getAuthenticationRequest().setCodeChallenge(challenge);
+            getAuthenticationRequest().setCodeChallengeMethod(method);
+
+        } else {            
+            log.trace("{} PKCE not enabled", getLogPrefix());            
+        }        
+    }
+    
+    /**
+     * Generates a code_verifier for use during Proof Key for Code Exchange. The generated bytes are base64 URL 
+     * encoded before they are returned. 
+     *  
+     * @param length the byte length of the code_verifier. Must be at least 32 bytes long (RFC7636 section 7.1).
+     * 
+     * @return the base64 URL encoded coder_verifier value.
+     * 
+     * @throws MessageHandlerException if there is an error generating the verifier. 
+     */
+    @Nonnull private static String generateCodeVerifier(@Nonnull final Integer length) throws MessageHandlerException {
+        if (length < 32) {
+            throw new MessageHandlerException("PKCE coder_verifier must be at least 32 bytes long");
+        }
+        try {
+            final SecureRandom secureRandom = new SecureRandom();
+            final byte[] verifierInBytes = new byte[length];
+            secureRandom.nextBytes(verifierInBytes);
+            return Base64Support.encodeURLSafe(verifierInBytes);
+        } catch (final Exception e) {
+            throw new MessageHandlerException(e);            
+        }
+    }
+    
+    /**
+     * Compute the code_challenge from the code_verifier. If the {@link CodeChallengeMethod#PLAIN} method is used, the
+     * codeVerifier is returned directly. If the {@link CodeChallengeMethod#S256} method is used, the bytes of the
+     * codeVerifier are SHA-256 hashed and base 64 URL encoded before being returned. 
+     * 
+     * @param codeVerifier the code_verifier to compute the code_challenge from
+     * @param method the code_challenge_method
+     * 
+     * @return the computed code_challenge
+     * 
+     * @throws MessageHandlerException on error computing the code_challenge
+     */
+    @Nonnull @NotEmpty private String computeCodeChallenge(@Nonnull @NotEmpty final String codeVerifier, 
+            @Nonnull final CodeChallengeMethod method) throws MessageHandlerException {
+        
+        if (method == CodeChallengeMethod.PLAIN) {
+            return codeVerifier;
+        }
+        try {            
+            final MessageDigest md = MessageDigest.getInstance("SHA-256");
+            final byte[] hash = md.digest(codeVerifier.getBytes());
+            assert hash != null;
+            return Base64Support.encodeURLSafe(hash);
+            
+        } catch (final NoSuchAlgorithmException | EncodingException e) {
+            throw new MessageHandlerException("Unable to compute code_challenge", e);
+        }
+
+        
+        
+    }
+
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddPromptHandler.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddPromptHandler.java
new file mode 100644
index 00000000..e281f135
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddPromptHandler.java
@@ -0,0 +1,53 @@
+/*
+ * 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.messaging.handler.impl;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import com.nimbusds.openid.connect.sdk.Prompt;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A message handler that sets the 'prompt' parameter to 'none' if passive
+ * authentication has been requested by the SP.
+ */
+public class AddPromptHandler extends AbstractAuthenticationRequestParameterValueMessageHandler<Prompt> {
+
+    /** Class logger. */
+    @Nonnull
+    private final Logger log = LoggerFactory.getLogger(AddPromptHandler.class);
+
+    /** Constructor.*/
+    public AddPromptHandler() {
+        super(Prompt.class);
+    }
+
+    @Override
+    protected void doInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+
+        final Prompt prompt = getParameterValue(messageContext);
+        if (prompt != null) {
+            if (log.isTraceEnabled()) {
+                log.trace("{} Setting 'prompt={}'", getLogPrefix(), prompt.toString());
+            }            
+            getAuthenticationRequest().setPrompt(prompt);            
+        }
+    }
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddRedirectURIHandler.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddRedirectURIHandler.java
new file mode 100644
index 00000000..3b5fdbd1
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddRedirectURIHandler.java
@@ -0,0 +1,52 @@
+/*
+ * 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.messaging.handler.impl;
+
+import java.net.URI;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/** 
+ * A message handler that adds a redirect_uri to the authentication request.
+ */
+public class AddRedirectURIHandler extends AbstractAuthenticationRequestParameterValueMessageHandler<URI> {
+
+    /** Logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddRedirectURIHandler.class);
+    
+    /** Constructor.*/
+    public AddRedirectURIHandler() {
+        super(URI.class);
+    }
+
+    @Override protected void doInvoke(@Nonnull final MessageContext messageContext) 
+            throws MessageHandlerException {   
+       
+        final URI redirectUri = getParameterValue(messageContext);
+        if (redirectUri == null) {
+            throw new MessageHandlerException("Redirect URI could not be located or created using the strategy");
+        }
+        log.trace("{} Created redirect_uri '{}'", getLogPrefix(), redirectUri);
+        getAuthenticationRequest().setRedirectURI(redirectUri);
+  
+    }
+
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddRequestedClaimsHandler.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddRequestedClaimsHandler.java
new file mode 100644
index 00000000..590e82a2
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddRequestedClaimsHandler.java
@@ -0,0 +1,63 @@
+/*
+ * 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.messaging.handler.impl;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import com.nimbusds.openid.connect.sdk.OIDCClaimsRequest;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/** 
+ * A message handler that adds requested claims to the authentication request.
+ * 
+ * <p>Also records in the request whether the OP supports the claims parameter, for later inspection by 
+ * downstream components that only has access to the request e.g. an encoder.</p>
+ */
+public class AddRequestedClaimsHandler 
+    extends AbstractAuthenticationRequestParameterValueMessageHandler<OIDCClaimsRequest> {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddRequestedClaimsHandler.class);
+    
+    /** Constructor.*/
+    public AddRequestedClaimsHandler() {
+        super(OIDCClaimsRequest.class);
+    }
+
+    @Override protected void doInvoke(@Nonnull final MessageContext messageContext) 
+            throws MessageHandlerException {
+        
+        // Stash whether the OP supports the claims parameter for later introspection
+        getAuthenticationRequest().setProviderSupportsClaimsParameter(getProviderMetadata().supportsClaimsParam());
+        
+        if (!getProviderMetadata().supportsClaimsParam()) {
+            log.trace("{} OpenID Provider does not support the 'claims' parameter", getLogPrefix());
+            return;
+        }
+        
+        final OIDCClaimsRequest requestedClaims = getParameterValue(messageContext);
+        if (requestedClaims != null) {
+            getAuthenticationRequest().setRequestedClaims(requestedClaims);
+            log.trace("{} Added requested claims '{}' to the authentication request",getLogPrefix(), requestedClaims);
+        } else {
+            log.trace("{} No individual claims requested", getLogPrefix());
+        }      
+    }   
+}
\ No newline at end of file
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddResourceHandler.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddResourceHandler.java
new file mode 100644
index 00000000..4ca2c91e
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddResourceHandler.java
@@ -0,0 +1,53 @@
+/*
+ * 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.messaging.handler.impl;
+
+import java.net.URI;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+
+/** 
+ * A message handler that adds OAuth 2.0 resource indicators to the authentication request.
+ */
+public class AddResourceHandler extends AbstractAuthenticationRequestParameterValueMessageHandler<List<URI>> {    
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddResourceHandler.class);
+    
+    /**
+     * Constructor.
+     */
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    protected AddResourceHandler() {
+        super((Class)List.class);
+    }
+    
+    @Override
+    protected void doInvoke(@Nonnull final MessageContext messageContext) 
+            throws MessageHandlerException {
+        final List<URI> resources = getParameterValue(messageContext);
+        log.debug("{} Adding resource indicators '{}'", getLogPrefix(), resources);
+        getAuthenticationRequest().setResources(resources);
+        
+    }
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddResponseModeHandler.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddResponseModeHandler.java
new file mode 100644
index 00000000..ece6812a
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddResponseModeHandler.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.messaging.handler.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import com.nimbusds.oauth2.sdk.ResponseMode;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A message handler that populates the authentication request response_type.
+ */
+public class AddResponseModeHandler extends AbstractAuthenticationRequestParameterValueMessageHandler<ResponseMode> {
+
+    /** Logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddResponseModeHandler.class);
+    
+    /** Constructor.*/
+    public AddResponseModeHandler() {
+        super(ResponseMode.class);
+    }    
+    
+    @Override protected void doInvoke(@Nonnull final MessageContext messageContext) 
+                throws MessageHandlerException {
+        
+        final ResponseMode responseModeFromLookup = getParameterValue(messageContext);
+        
+        log.trace("{} response mode configured as '{}'",getLogPrefix(), responseModeFromLookup);
+        
+        final ResponseMode compatibleMode = ResponseMode.resolve(null, getAuthenticationRequest().getResponseType());
+        if (compatibleMode == null) {
+            throw new MessageHandlerException("A compatible response_mode for response_type "
+                    + "'"+getAuthenticationRequest().getResponseType()+"' could not be found");
+        }
+        
+        checkProviderSupportsResponseMode(compatibleMode);
+        
+        log.trace("{} Compatible response_mode '{}' resolved from response_type '{}'", getLogPrefix(), compatibleMode, 
+                getAuthenticationRequest().getResponseType());
+        
+        if (responseModeFromLookup != null && !responseModeFromLookup.equals(compatibleMode)) {
+            log.debug("{} response_mode override '{}' exists from configuration and is different than the"
+                    + " default mode '{}' for response_type '{}'",
+                    getLogPrefix(), responseModeFromLookup, compatibleMode, 
+                    getAuthenticationRequest().getResponseType());  
+            
+            checkProviderSupportsResponseMode(responseModeFromLookup);
+            getAuthenticationRequest().setResponseMode(responseModeFromLookup);
+            
+        } else {
+            getAuthenticationRequest().setResponseMode(compatibleMode);
+        }
+
+        log.trace("{} response_mode '{}' selected", getLogPrefix(), getAuthenticationRequest().getResponseMode());
+    }
+    
+    /**
+     * Check the OpenID Provider supports the response_mode from its metadata value response_modes_supported. Throws
+     * an exception if not.
+     * 
+     * @param responseMode the response_mode to check is supported
+     * 
+     * @throws MessageHandlerException if the OpenID Provider does not support the response_mode
+     */
+    private void checkProviderSupportsResponseMode(@Nonnull final ResponseMode responseMode) 
+            throws MessageHandlerException {
+        final List<ResponseMode> responseModesSupportedOP = getProviderMetadata().getResponseModes();
+        final List<ResponseMode> responseModesSupported = new ArrayList<>(2);
+        if (responseModesSupportedOP == null || responseModesSupportedOP.isEmpty()) {
+            // Add the defaults from the specification
+            responseModesSupported.add(ResponseMode.QUERY);
+            responseModesSupported.add(ResponseMode.FRAGMENT);
+        } else {
+            responseModesSupportedOP.forEach(responseModesSupported::add);
+        }
+        if (!responseModesSupported.contains(responseMode)) {
+            throw new MessageHandlerException("OpenID Provider does not support chosen response_mode: "
+                    + responseMode.toString());
+        }
+        
+    }
+
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddResponseTypeHandler.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddResponseTypeHandler.java
new file mode 100644
index 00000000..77bfb62a
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddResponseTypeHandler.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.messaging.handler.impl;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import com.nimbusds.oauth2.sdk.ResponseMode;
+import com.nimbusds.oauth2.sdk.ResponseType;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A message handler that populates the authentication request response_type.
+ */
+public class AddResponseTypeHandler extends AbstractAuthenticationRequestParameterValueMessageHandler<ResponseType> {
+
+    /** Logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddResponseTypeHandler.class);
+    
+    /** Constructor.*/
+    public AddResponseTypeHandler() {
+        super(ResponseType.class);
+    }    
+    
+    @Override protected void doInvoke(@Nonnull final MessageContext messageContext) 
+                throws MessageHandlerException {
+        
+        final ResponseType responseType = getParameterValue(messageContext);
+        if (responseType == null){
+            throw new MessageHandlerException("response_type '"+responseType+"' is not supported");
+        }        
+        checkProviderSupportsResponseType(responseType);         
+        
+        getAuthenticationRequest().setResponseType(responseType);
+        log.trace("{} response_type '{}' selected", getLogPrefix(), getAuthenticationRequest().getResponseType());
+    }
+    
+    /**
+     * Check the OpenID Provider supports the response_type from its metadata value response_types_supported. Throws
+     * an exception if not.
+     * 
+     * @param responseType the response_type to check is supported
+     * 
+     * @throws MessageHandlerException if the OpenID Provider does not support the response_type
+     */
+    private void checkProviderSupportsResponseType(@Nonnull final ResponseType responseType) 
+            throws MessageHandlerException {
+        final List<ResponseType> responseTypesSupported = getProviderMetadata().getResponseTypes();
+        if (responseTypesSupported == null) {
+            throw new MessageHandlerException("OpenID Provider has not specified supported response types "
+                    + "(response_types_supported). It MUST.");
+        }
+        if (!responseTypesSupported.contains(responseType)) {
+            throw new MessageHandlerException("OpenID Provider does not support chosen response_type: "
+                    + responseType.toString());
+        }
+    }
+
+    
+ // Checkstyle: ReturnCount OFF
+    /**
+     * Parse the response_mode into a known {@link ResponseMode}.
+     * 
+     * @param responseModeFromProfile the response_mode as a string
+     * 
+     * @return the parsed {@link ResponseMode}, or {@literal null} if the input type is unknown
+     */
+    @Nullable private ResponseMode parseResponseMode(@Nullable final String responseModeFromProfile) {
+        
+        if (responseModeFromProfile == null) {
+            return null;
+        }
+        
+        if (responseModeFromProfile.equals(ResponseMode.FORM_POST.getValue())) {
+            return ResponseMode.FORM_POST;
+        } else if (responseModeFromProfile.equals(ResponseMode.FORM_POST_JWT.getValue())) {
+            return ResponseMode.FORM_POST_JWT;
+        } else if (responseModeFromProfile.equals(ResponseMode.QUERY.getValue())) {
+            return ResponseMode.QUERY;
+        } else if (responseModeFromProfile.equals(ResponseMode.FRAGMENT.getValue())) {
+            return ResponseMode.FRAGMENT;
+        } else if (responseModeFromProfile.equals(ResponseMode.FRAGMENT_JWT.getValue())) {
+            return ResponseMode.FRAGMENT_JWT;
+        } else if (responseModeFromProfile.equals(ResponseMode.JWT.getValue())) {
+            return ResponseMode.JWT;
+        } else if (responseModeFromProfile.equals(ResponseMode.QUERY_JWT.getValue())) {
+            return ResponseMode.QUERY_JWT;
+        } else {
+            return null;
+        }
+    }
+ // Checkstyle: ReturnCount ON
+
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddScopesHandler.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddScopesHandler.java
new file mode 100644
index 00000000..5d05d6e6
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddScopesHandler.java
@@ -0,0 +1,57 @@
+/*
+ * 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.messaging.handler.impl;
+
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+
+/** 
+ * A message handler that adds OAuth 2.0 scopes to the authentication request.
+ */
+public class AddScopesHandler extends AbstractAuthenticationRequestParameterValueMessageHandler<Set<String>> {    
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddScopesHandler.class);
+    
+    /**
+     * Constructor.
+     */
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    protected AddScopesHandler() {
+        super((Class)Set.class);
+    }
+    
+    @Override
+    protected void doInvoke(@Nonnull final MessageContext messageContext) 
+            throws MessageHandlerException {
+        final Set<String> scopes = getParameterValue(messageContext);
+        if (scopes != null && !scopes.isEmpty()) {
+            scopes.forEach(s -> getAuthenticationRequest().getScope().add(s));
+        }
+        log.trace("{} Added scopes '{}' to authentication request",getLogPrefix(), 
+                getAuthenticationRequest().getScope());
+    }
+    
+    
+
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddStateHandler.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddStateHandler.java
new file mode 100644
index 00000000..193666ce
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddStateHandler.java
@@ -0,0 +1,77 @@
+/*
+ * 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.messaging.handler.impl;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import com.nimbusds.oauth2.sdk.id.State;
+import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/** 
+ * Add an OAuth 2.0 / OpenID Connect {@code state} value to the authentication request URL and the request object 
+ * claims (if present). By default this is generated by concatenating the Hex value of the spring webflow execution 
+ * key with a secure random 32 character nonce. 
+ * 
+ *  * <p>
+ * The {@code state} parameter helps prevent cross-site request forgery
+ * (CSRF) attacks and can be used by clients to maintain request
+ * integrity.
+ * </p>
+ * 
+ */
+//TODO This need thinking about in the case of the RP-Full.
+public class AddStateHandler extends AbstractAuthenticationRequestParameterValueMessageHandler<String> {
+    
+    /** The 'state' claim name.*/
+    @Nonnull private static final String STATE_CLAIM = "state";
+
+    /** Logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddStateHandler.class);
+    
+    
+    /** Constructor.*/
+    public AddStateHandler() {
+        super(String.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+        
+        final String stateString = getParameterValue(messageContext);        
+        if (stateString == null) {
+            throw new MessageHandlerException("Generated state was null");
+        }
+        log.trace("{} Generated state '{}'", getLogPrefix(), stateString);
+        final State state = new State(stateString);
+        
+        // Add to outer request
+        getAuthenticationRequest().setState(state);
+        
+        // Add to Request Object if exists
+        final ClaimsSet claims = getAuthenticationRequest().getRequestObjectClaimsSet();
+        if (claims != null) {            
+            log.trace("{} Adding state to JWT RequestObject", getLogPrefix());
+            claims.setClaim(STATE_CLAIM, state);           
+        }               
+    }
+
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddUiLocalesHandler.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddUiLocalesHandler.java
new file mode 100644
index 00000000..6d071784
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddUiLocalesHandler.java
@@ -0,0 +1,66 @@
+/*
+ * 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.messaging.handler.impl;
+
+import java.util.List;
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import com.nimbusds.langtag.LangTag;
+import com.nimbusds.langtag.LangTagException;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A message handler that sets the 'ui_locales' parameter on the authentication request.
+ */
+public class AddUiLocalesHandler extends AbstractAuthenticationRequestParameterValueMessageHandler<List<String>> {
+
+    /** Class logger. */
+    @Nonnull
+    private final Logger log = LoggerFactory.getLogger(AddUiLocalesHandler.class);
+
+    /** Constructor.*/
+    public AddUiLocalesHandler() {
+        super((Class)List.class);
+    }
+
+    @Override
+    protected void doInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+
+        final List<String> locales = getParameterValue(messageContext);
+        if (locales != null && !locales.isEmpty()) {
+            if (log.isTraceEnabled()) {
+                log.trace("{} Setting 'ui_locales={}'", getLogPrefix(), locales);
+            }
+            final List<LangTag> uiLocals = locales.stream().map(tag -> {
+                try {
+                    return LangTag.parse(tag);
+                } catch (final LangTagException e) {
+                    log.warn("Can not parse language tag '{}'", tag);
+                }
+                return null;
+            }).filter(Objects::nonNull).toList();
+            
+            getAuthenticationRequest().setUiLocales(uiLocals);            
+        }
+    }
+
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/BuildPlainRequestObjectJWT.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/BuildPlainRequestObjectJWT.java
new file mode 100644
index 00000000..796cc5bd
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/BuildPlainRequestObjectJWT.java
@@ -0,0 +1,63 @@
+/*
+ * 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.messaging.handler.impl;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import com.nimbusds.jwt.PlainJWT;
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/** 
+ * If the Request Object claims are present in the authentication request, convert them 
+ * into a JWTClaimsSet inside a PlainJWT. 
+ */
+public class BuildPlainRequestObjectJWT extends AbstractAuthenticationRequestParameterValueMessageHandler<ClaimsSet> {
+
+    /** Logger. */
+    @Nonnull
+    private final Logger log = LoggerFactory.getLogger(BuildPlainRequestObjectJWT.class);
+   
+    /**
+     * Constructor.
+     */
+    protected BuildPlainRequestObjectJWT() {
+        super(ClaimsSet.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+
+        final ClaimsSet requestObjectClaims = getAuthenticationRequest().getRequestObjectClaimsSet();
+        if (requestObjectClaims == null) {
+            log.trace("{} RequestObject claims are not present, request object JWT skipped", getLogPrefix());
+            return;
+        }
+        try {
+            getAuthenticationRequest().setRequestObject(new PlainJWT(requestObjectClaims.toJWTClaimsSet()));
+            log.trace("{} Built Plain JWT RequestObject from claims", getLogPrefix());
+        } catch (final ParseException e) {
+            throw new MessageHandlerException("Unable to generate request object JWT", e);
+        }
+    }
+
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/PKCEOptions.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/PKCEOptions.java
new file mode 100644
index 00000000..53ca68e4
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/PKCEOptions.java
@@ -0,0 +1,72 @@
+/*
+ * 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.messaging.handler.impl;
+
+
+/**
+ * Configuration options for Proof Key for Code Exchange (PKCE).
+ */
+public class PKCEOptions {
+
+    /** Indicates whether PKCE is enabled for the client. */
+    private final boolean isEnabled;
+
+    /** Indicates whether the "plain" code challenge method is allowed. */
+    private final boolean allowPlain;
+
+    /**
+     * Constructor.
+     *
+     * @param enabled   whether PKCE is enabled
+     * @param plainAllowed  whether the "plain" code challenge method is allowed
+     */
+    public PKCEOptions(final boolean enabled, final boolean plainAllowed) {
+        isEnabled = enabled;
+        allowPlain = plainAllowed;
+    }
+
+    /**
+     * Returns whether PKCE is enabled.
+     *
+     * @return {@code true} if PKCE is enabled; {@code false} otherwise
+     */
+    public boolean isEnabled() {
+        return isEnabled;
+    }
+
+    /**
+     * Returns whether the "plain" code challenge method is allowed.
+     *
+     * @return {@code true} if "plain" is allowed; {@code false} otherwise
+     */
+    public boolean isAllowPlain() {
+        return allowPlain;
+    }
+
+    /**
+     * Returns a string representation of this PKCE configuration.
+     *
+     * @return a string describing the PKCE options
+     */
+    @Override
+    public String toString() {
+        return "PKCEOptions{" +
+                "isEnabled=" + isEnabled +
+                ", allowPlain=" + allowPlain +
+                '}';
+    }
+
+}
+
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/SetAuthenticationRequestTimeHandler.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/SetAuthenticationRequestTimeHandler.java
new file mode 100644
index 00000000..c6ea598d
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/SetAuthenticationRequestTimeHandler.java
@@ -0,0 +1,47 @@
+/*
+ * 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.messaging.handler.impl;
+
+import java.time.Instant;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+
+/** Handler that adds the authentication request time to the authentication request.*/
+public class SetAuthenticationRequestTimeHandler extends AbstractAuthenticationRequestParameterValueMessageHandler<Instant> {
+    
+    /** Logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(SetAuthenticationRequestTimeHandler.class);
+    
+    /**
+     * Constructor.
+     */
+    protected SetAuthenticationRequestTimeHandler() {
+        super(Instant.class);
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {        
+        getAuthenticationRequest().setAuthnRequestTime(Instant.now());              
+    }
+
+}

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


More information about the commits mailing list