[java-idp-plugin-oidc-rp] 01/01: JOIDCRP-29 - Support client_secret_jwt and private_key_jwt client authentication

Phil Smart philip.smart at jisc.ac.uk
Tue Jun 20 14:22:45 UTC 2023


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

philsmart pushed a commit to branch dev/JOIDCRP-29
in repository java-idp-plugin-oidc-rp.

View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-oidc-rp.git;a=commit;h=94e4e6a81904d2500aa6807180c3183b515868e8

commit 94e4e6a81904d2500aa6807180c3183b515868e8
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Mon Jun 19 17:18:41 2023 +0100

    JOIDCRP-29 - Support client_secret_jwt and private_key_jwt client
    authentication
    
     - Cleanup security context location in the context tree
     - Support client authentication using a JWT bearer token
    
    https://shibboleth.atlassian.net/browse/JOIDCRP-29
---
 ...viderMetadataStringListValueLookupFunction.java |  87 ++++
 ...earerTokenForClientAuthenticationPredicate.java |  69 +++
 ...nitializeOAuth2ClientAuthenticationContext.java | 181 +-------
 ...izeOAuth2ClientAuthenticationMethodHandler.java | 504 +++++++++++++++++++++
 ...tAuthenticationConfigurationLookupFunction.java | 211 +++++++++
 .../META-INF/net.shibboleth.idp/postconfig.xml     |  47 ++
 .../oidc-relying-party-authn-beans.xml             |  49 +-
 .../oidc-relying-party-authn-flow.xml              |   2 +-
 .../authn/oidc/rp/conf/authn/oidc-rp.properties    |   3 +-
 .../authn/oidc/rp/impl/AbstractOIDCTest.java       |  18 +-
 ...Auth2ClientAuthenticationMethodHandlerTest.java | 257 +++++++++++
 .../OIDCRPFlowFromAuthenticationResponseTest.java  | 128 ++++++
 .../plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java  |   2 +-
 ...henticationConfigurationLookupFunctionTest.java | 166 +++++++
 .../resources/metadata/test-provider-standard.json |   8 +-
 15 files changed, 1556 insertions(+), 176 deletions(-)

diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProviderMetadataStringListValueLookupFunction.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProviderMetadataStringListValueLookupFunction.java
new file mode 100644
index 0000000..2e3ae53
--- /dev/null
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProviderMetadataStringListValueLookupFunction.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.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 java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+import net.shibboleth.utilities.java.support.annotation.ParameterName;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * 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 String) {
+            return List.of((String)value);
+        }
+        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))
+                    .collect(Collectors.toList()));
+        }
+        
+        return null;
+    }
+
+}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/logic/JWTBearerTokenForClientAuthenticationPredicate.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/logic/JWTBearerTokenForClientAuthenticationPredicate.java
new file mode 100644
index 0000000..307de51
--- /dev/null
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/logic/JWTBearerTokenForClientAuthenticationPredicate.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.logic;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.ParentProfileRequestContextLookup;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
+
+import net.shibboleth.idp.profile.config.ProfileConfiguration;
+import net.shibboleth.idp.profile.context.RelyingPartyContext;
+import net.shibboleth.idp.profile.logic.messaging.AbstractRelyingPartyPredicate;
+import net.shibboleth.oidc.profile.config.OIDCAuthenticationRelyingPartyProfileConfiguration;
+
+/** A predicate that determines if the client authentication method chosen is a JWT type.*/
+public class JWTBearerTokenForClientAuthenticationPredicate extends AbstractRelyingPartyPredicate {
+    
+    /** Class logger. */
+    @Nonnull
+    private final Logger log = LoggerFactory.getLogger(JWTBearerTokenForClientAuthenticationPredicate.class);
+
+    @Override
+    public boolean test(@Nullable final MessageContext input) {
+        
+        final ParentProfileRequestContextLookup<MessageContext> lookup = new ParentProfileRequestContextLookup<>();
+        final ProfileRequestContext prc = lookup.apply(input);
+        
+        final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
+        if (rpc != null) {
+            final ProfileConfiguration pc = rpc.getProfileConfig();
+            if (pc instanceof OIDCAuthenticationRelyingPartyProfileConfiguration) {
+                final String authMethod = 
+                        ((OIDCAuthenticationRelyingPartyProfileConfiguration) pc).getTokenEndpointAuthMethod(prc);
+                final ClientAuthenticationMethod method = new ClientAuthenticationMethod(authMethod);
+                if (method.equals(ClientAuthenticationMethod.CLIENT_SECRET_JWT) || 
+                        method.equals(ClientAuthenticationMethod.PRIVATE_KEY_JWT)) {
+                   
+                    return true;
+                } else {
+                    log.trace("Configured client authentication method '{}' does not require a signed JWT bearer token"
+                            , authMethod);
+                }
+            }
+        }        
+        return false;
+    }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOAuth2ClientAuthenticationContext.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOAuth2ClientAuthenticationContext.java
index 96d541c..2fa6e52 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOAuth2ClientAuthenticationContext.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOAuth2ClientAuthenticationContext.java
@@ -17,49 +17,32 @@
 
 package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
 
-import java.nio.charset.StandardCharsets;
 import java.util.function.Function;
 
 import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import javax.crypto.SecretKey;
 
+import org.opensaml.messaging.context.MessageContext;
 import org.opensaml.messaging.context.navigate.ChildContextLookup;
-import org.opensaml.profile.action.ActionSupport;
+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.opensaml.profile.context.navigate.OutboundMessageContextLookup;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
-import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
-import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
-import com.nimbusds.oauth2.sdk.auth.ClientSecretPost;
-import com.nimbusds.oauth2.sdk.auth.Secret;
-import com.nimbusds.oauth2.sdk.id.ClientID;
-
-import net.shibboleth.idp.profile.AbstractProfileAction;
-import net.shibboleth.idp.profile.IdPEventIds;
-import net.shibboleth.idp.profile.context.RelyingPartyContext;
 import net.shibboleth.oidc.authn.context.OAuth2ClientAuthenticationContext;
-import net.shibboleth.oidc.profile.config.OIDCAuthenticationRelyingPartyProfileConfiguration;
 import net.shibboleth.oidc.profile.messaging.context.OIDCPeerEntityContext;
-import net.shibboleth.oidc.security.credential.ClientSecretCredential;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 
 /**
- * An {@link AbstractProfileAction action} that resolves the Client Authentication method for the chosen 
- * upstream OpenID Provider (issuer) from the profile configuration.
+ * An {@link AbstractMessageHandler action} that initializes an {@link OAuth2ClientAuthenticationContext} for later use.
  * 
  * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
- * @event {@link IdPEventIds#INVALID_PROFILE_CONFIG}
  * @event {@link EventIds#INVALID_PROFILE_CTX}
- * @event {@link IdPEventIds#INVALID_RELYING_PARTY_CONFIG}
- * @post Add the {@link ClientAuthenticationMethod} to the {@link OAuth2ClientAuthenticationContext}
+ * @post create a {@link OAuth2ClientAuthenticationContext}
  */
-public class InitializeOAuth2ClientAuthenticationContext extends AbstractProfileAction {
+public class InitializeOAuth2ClientAuthenticationContext extends AbstractMessageHandler {
 
     /** Class logger. */
     @Nonnull
@@ -69,17 +52,8 @@ public class InitializeOAuth2ClientAuthenticationContext extends AbstractProfile
      * The strategy used to lookup or create the {@link OAuth2ClientAuthenticationContext} 
      * for storing the client authentication.
      */
-    @Nonnull private Function<ProfileRequestContext, OAuth2ClientAuthenticationContext> 
+    @Nonnull private Function<MessageContext, OAuth2ClientAuthenticationContext> 
                                                     oauth2ClientAuthenticationContextLookupStrategy;
-        
-    /** The stashed OAuth2 client authentication context.*/
-    @Nullable private OAuth2ClientAuthenticationContext oauth2ClientAuthenticationContext;
-    
-    /** Lookup function for relying party context. */
-    @Nonnull private Function<ProfileRequestContext,RelyingPartyContext> relyingPartyContextLookupStrategy;
-    
-    /** Applicable stashed profile configuration. */
-    @Nullable private OIDCAuthenticationRelyingPartyProfileConfiguration profileConfiguration;
 
     
     /** Constructor.*/
@@ -88,27 +62,9 @@ public class InitializeOAuth2ClientAuthenticationContext extends AbstractProfile
         // Default under the OIDC Peer Entity Context, create is true
         oauth2ClientAuthenticationContextLookupStrategy  = 
                 new ChildContextLookup<>(OAuth2ClientAuthenticationContext.class, true).compose(
-                new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
-                        new OutboundMessageContextLookup()));
-        
-        relyingPartyContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class);
-
-    }
-    
-    /**
-     * Set lookup strategy for relying party context.
-     * 
-     * @param strategy  lookup strategy
-     */
-    public void setRelyingPartyContextLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext,RelyingPartyContext> strategy) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
-        relyingPartyContextLookupStrategy =
-                Constraint.isNotNull(strategy, "RelyingPartyContext lookup strategy cannot be null");
+                new ChildContextLookup<>(OIDCPeerEntityContext.class));
     }
-    
-    
+
     /**
      * Set the strategy to lookup the {@link OAuth2ClientAuthenticationContext} 
      * from the {@link ProfileRequestContext}.
@@ -116,7 +72,7 @@ public class InitializeOAuth2ClientAuthenticationContext extends AbstractProfile
      * @param strgy the strategy.
      */
     public void setOAuth2ClientAuthenticationContextLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext, OAuth2ClientAuthenticationContext> strgy) {
+            @Nonnull final Function<MessageContext, OAuth2ClientAuthenticationContext> strgy) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
         ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
 
@@ -124,125 +80,20 @@ public class InitializeOAuth2ClientAuthenticationContext extends AbstractProfile
                 "OAuth2 client authentication context lookup strategy cannot be null");
     }
     
-    @Override
-    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-        
-        if (!super.doPreExecute(profileRequestContext)) {
-            return false;
-        }
-        
-        oauth2ClientAuthenticationContext = 
-                oauth2ClientAuthenticationContextLookupStrategy.apply(profileRequestContext);
-        if (oauth2ClientAuthenticationContext == null) {
-            log.error("{} No OAuth2 client authentication context found or created", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
-            return false;
-        }
-        
-        final RelyingPartyContext rpCtx = relyingPartyContextLookupStrategy.apply(profileRequestContext);     
-        if (rpCtx != null && rpCtx.getConfiguration() != null &&
-                rpCtx.getProfileConfig() instanceof OIDCAuthenticationRelyingPartyProfileConfiguration) {
-            profileConfiguration = (OIDCAuthenticationRelyingPartyProfileConfiguration) rpCtx.getProfileConfig();
-        }
-        if (profileConfiguration == null) {
-            log.error("{} Profile configuration not found", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
-            return false;
-        }
-        
-        return true;
-        
-    }
     
     @Override
-    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-        super.doExecute(profileRequestContext);
-        
-        final String clientAuthMethod = 
-                profileConfiguration.getTokenEndpointAuthMethod(profileRequestContext);
-        if (clientAuthMethod.isEmpty()) {
-            log.error("{} No client authentication method found from profile configuration", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CONFIG);
-            return;
-        }
-       
-        final String clientId = 
-                profileConfiguration.getClientId(profileRequestContext);
-        if (clientId == null) {
-            log.error("{} No client_id found from profile configuration", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CONFIG);
-            return;
-        }
+    protected void doInvoke(final MessageContext messageContext) throws MessageHandlerException {
         
-        final ClientSecretCredential clientCredential = 
-                profileConfiguration.getClientCredential(profileRequestContext);
-        if (clientCredential == null) {
-            log.error("{} No client secret credential found from profile configuration", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CONFIG);
-            return;
-        }
-        
-        final ClientAuthentication clientAuthentication = 
-                constructClientAuthentication(clientId, clientAuthMethod, clientCredential);
+        final OAuth2ClientAuthenticationContext context = 
+                oauth2ClientAuthenticationContextLookupStrategy.apply(messageContext);
         
-        if (clientAuthentication == null) {
-            log.error("{} No client authentication could be constructed", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CONFIG);
-            return;
+        if (context == null) {
+            throw new MessageHandlerException("No OAuth2 client authentication context found or created");
         }
 
-        oauth2ClientAuthenticationContext.setClientAuthentication(clientAuthentication);   
-        log.debug("{} Initialized OAuth2 Client Authentication Context: Found client authentication mode "
-                + "'{}' for client '{}'",getLogPrefix(), clientAuthentication.getMethod(), 
-                clientAuthentication.getClientID());
-    }
-    
-    /**
-     * Construct the client authentication from the given client authentication record.
-     * 
-     * @param clientId the client_id
-     * @param tokenEndpointAuthMethod the token endpoint authentication method
-     * @param clientCredential the client credential
-     * 
-     * @return the constructed client authentication
-     */
-    @Nullable protected ClientAuthentication constructClientAuthentication(
-            @Nonnull final String clientId, @Nonnull final String tokenEndpointAuthMethod,
-            @Nonnull final ClientSecretCredential clientCredential) {
-        
-        // TODO support JWT types    
-        final Secret secret = new Secret(clientCredential.getSecret());
-        
-        // TODO redundent check for now, as the secret can not expire. Add back?
-        if (secret.expired()) {
-            log.warn("{} Client secret has expired for client '{}'", getLogPrefix(), clientId);
-            return null;
-        }
-        
-        final ClientAuthenticationMethod method = new ClientAuthenticationMethod(tokenEndpointAuthMethod);
-        if (method.equals(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)) {
-            return new ClientSecretBasic(new ClientID(clientId), secret);
-        } else if (method.equals(ClientAuthenticationMethod.CLIENT_SECRET_POST)) {
-            return new ClientSecretPost(new ClientID(clientId), secret);
-        }  
-        log.warn("{}: Client authentication method '{}' not supported for client '{}'", getLogPrefix(), 
-                tokenEndpointAuthMethod, clientId);
-        return null;
-        
+        log.debug("{} Initialized OAuth2 Client Authentication Context",getLogPrefix());
     }
     
-    /**
-     * Convert the encoded byte array representing the secret into a UTF-8 String.
-     * 
-     * @param key the key to convert
-     * @return the UTF-8 encoded string value of the secret. 
-     */
-    @Nullable private String convertSecretKeyToString(@Nullable final SecretKey key) {
-        if (key == null || key.getEncoded() == null) {
-            return null;
-        }
-        return new String(key.getEncoded(),StandardCharsets.UTF_8);
-    }
     
     
 }
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOAuth2ClientAuthenticationMethodHandler.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOAuth2ClientAuthenticationMethodHandler.java
new file mode 100644
index 0000000..ace2306
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOAuth2ClientAuthenticationMethodHandler.java
@@ -0,0 +1,504 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import java.nio.charset.StandardCharsets;
+import java.security.interfaces.ECPrivateKey;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.crypto.SecretKey;
+
+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.opensaml.profile.context.navigate.ParentProfileRequestContextLookup;
+import org.opensaml.security.credential.Credential;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jose.Algorithm;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JOSEObjectType;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.JWSObject.State;
+import com.nimbusds.jose.JWSSigner;
+import com.nimbusds.jose.crypto.ECDSASigner;
+import com.nimbusds.jose.crypto.MACSigner;
+import com.nimbusds.jose.crypto.RSASSASigner;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
+import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
+import com.nimbusds.oauth2.sdk.auth.ClientSecretJWT;
+import com.nimbusds.oauth2.sdk.auth.ClientSecretPost;
+import com.nimbusds.oauth2.sdk.auth.PrivateKeyJWT;
+import com.nimbusds.oauth2.sdk.auth.Secret;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+import net.shibboleth.idp.profile.IdPEventIds;
+import net.shibboleth.idp.profile.context.RelyingPartyContext;
+import net.shibboleth.oidc.authn.context.OAuth2ClientAuthenticationContext;
+import net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext;
+import net.shibboleth.oidc.profile.config.OIDCAuthenticationRelyingPartyProfileConfiguration;
+import net.shibboleth.oidc.profile.messaging.context.OIDCPeerEntityContext;
+import net.shibboleth.oidc.security.CredentialConversionUtil;
+import net.shibboleth.oidc.security.credential.ClientSecretCredential;
+import net.shibboleth.oidc.security.credential.JWKCredential;
+import net.shibboleth.oidc.security.jose.SignatureSigningParameters;
+import net.shibboleth.oidc.security.jose.context.SecurityParametersContext;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * An {@link AbstractMessageHandler action} that resolves the Client Authentication method for the chosen 
+ * upstream OpenID Provider (issuer) from the profile configuration, and adds it to the 
+ * {@link OAuth2ClientAuthenticationContext}. 
+ *
+ * <p>If a JWT client authentication type, the security parameters context is used create a signed JWT client assertion.
+ * </p>
+ * 
+ * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
+ * @event {@link IdPEventIds#INVALID_PROFILE_CONFIG}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ * @event {@link IdPEventIds#INVALID_RELYING_PARTY_CONFIG}
+ * @post Add the {@link ClientAuthenticationMethod} to the {@link OAuth2ClientAuthenticationContext}
+ */
+public class InitializeOAuth2ClientAuthenticationMethodHandler extends AbstractMessageHandler {
+
+    /** Class logger. */
+    @Nonnull
+    private final Logger log = LoggerFactory.getLogger(InitializeOAuth2ClientAuthenticationMethodHandler.class);
+    
+    /** Lookup function for parent ProfileRequestContext. */
+    @Nonnull private static final ParentProfileRequestContextLookup<MessageContext> PRC_LOOKUP
+        = new ParentProfileRequestContextLookup<>();
+    
+    /** 
+     * The strategy used to lookup or create the {@link OAuth2ClientAuthenticationContext} 
+     * for storing the client authentication.
+     */
+    @Nonnull private Function<MessageContext, OAuth2ClientAuthenticationContext> 
+                                                    oauth2ClientAuthenticationContextLookupStrategy;
+        
+    /** The stashed OAuth2 client authentication context.*/
+    @Nullable private OAuth2ClientAuthenticationContext oauth2ClientAuthenticationContext;
+    
+    /** Lookup strategy to locate the OP metadata to use.*/
+    @Nonnull private Function<MessageContext, OIDCProviderMetadataContext> providerMetadataLookupStrategy;
+    
+    /** Lookup function for relying party context. */
+    @Nonnull private Function<ProfileRequestContext,RelyingPartyContext> relyingPartyContextLookupStrategy;
+    
+    /** Applicable stashed profile configuration. */
+    @Nullable private OIDCAuthenticationRelyingPartyProfileConfiguration profileConfiguration;
+    
+    /** Strategy used to look up the {@link SecurityParametersContext} to set the parameters for. */
+    @Nonnull private Function<MessageContext, SecurityParametersContext> securityParametersContextLookupStrategy;
+    
+    /** The offset to add to the 'exp' claim time for JWT client authentication methods. Default is 30 seconds.*/
+    @Nonnull private Duration jwtBearerExpiryOffset;
+    
+    /** 
+     * Stashed security parameters context for JWT Bearer Token client authentication methods. Can be {@code null} if 
+     * those client authentication methods are not used.
+     */
+    @Nullable private SecurityParametersContext jwtBearerClientAuthSecurityParameters;
+    
+    /** The stashed provider metadata.*/
+    @Nullable private OIDCProviderMetadata providerMetadata;
+    
+    /** The stashed client_secret to use if required.*/
+    @Nullable private ClientSecretCredential clientCredential;
+    
+    /** The stashed client authentication method to use.*/
+    @Nullable private String clientAuthMethod;
+    
+    /** The stashed client identifier for this request.*/
+    @Nullable private String clientId;
+
+    
+    /** Constructor.*/
+    public InitializeOAuth2ClientAuthenticationMethodHandler() {
+        // Default under the OIDC Peer Entity Context, create is true
+        oauth2ClientAuthenticationContextLookupStrategy  = 
+                new ChildContextLookup<>(OAuth2ClientAuthenticationContext.class, true).compose(
+                new ChildContextLookup<>(OIDCPeerEntityContext.class));        
+        relyingPartyContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class);        
+        securityParametersContextLookupStrategy = new ChildContextLookup<>(SecurityParametersContext.class);        
+        providerMetadataLookupStrategy = new ChildContextLookup<>(OIDCProviderMetadataContext.class).compose(
+                new ChildContextLookup<>(OIDCPeerEntityContext.class));
+        jwtBearerExpiryOffset = Duration.ofSeconds(30);
+    }
+    
+    /**
+     * Set the JWT expiry time offset for appropriate client authentication methods.
+     * 
+     * @param expiry the JWT 'exp' claim offset
+     */
+    public void setJwtBearerExpiryOffset(@Nonnull final Duration expiry) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+        jwtBearerExpiryOffset = Constraint.isNotNull(expiry, "jwtBearerExpiryOffset can not be null");
+    }
+    
+    /**
+     * Set the lookup strategy to locate the OpenID providers metadata.
+     * 
+     * @param strategy the strategy.
+     */
+    public void setProviderMetadataLookupStrategy(
+            @Nonnull final Function<MessageContext, OIDCProviderMetadataContext> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        providerMetadataLookupStrategy = 
+                Constraint.isNotNull(strategy,"Provider metadata lookup strategy can not be null");
+    }
+    
+    /**
+     * Set the strategy used to look up the {@link SecurityParametersContext}.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setSecurityParametersContextLookupStrategy(
+            @Nonnull final Function<MessageContext, SecurityParametersContext> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+        securityParametersContextLookupStrategy =
+                Constraint.isNotNull(strategy, "JWTSecurityParametersContext lookup strategy cannot be null");
+    }
+    
+    /**
+     * Set lookup strategy for relying party context.
+     * 
+     * @param strategy  lookup strategy
+     */
+    public void setRelyingPartyContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext,RelyingPartyContext> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        relyingPartyContextLookupStrategy =
+                Constraint.isNotNull(strategy, "RelyingPartyContext lookup strategy cannot be null");
+    }
+    
+    
+    /**
+     * Set the strategy to lookup the {@link OAuth2ClientAuthenticationContext} 
+     * from the {@link MessageContext}.
+     * 
+     * @param strgy the strategy.
+     */
+    public void setOAuth2ClientAuthenticationContextLookupStrategy(
+            @Nonnull final Function<MessageContext, OAuth2ClientAuthenticationContext> strgy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+        oauth2ClientAuthenticationContextLookupStrategy = Constraint.isNotNull(strgy, 
+                "OAuth2 client authentication context lookup strategy cannot be null");
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+        
+        if (!super.doPreInvoke(messageContext)) {
+            return false;
+        }  
+                  
+        oauth2ClientAuthenticationContext = 
+                oauth2ClientAuthenticationContextLookupStrategy.apply(messageContext);
+        if (oauth2ClientAuthenticationContext == null) {
+            log.error("{} No OAuth2 client authentication context found or created", getLogPrefix());
+            throw new MessageHandlerException("No OAuth2 client authentication context found or created");            
+        }
+        
+        final RelyingPartyContext rpCtx = relyingPartyContextLookupStrategy.compose(PRC_LOOKUP).apply(messageContext);     
+        if (rpCtx != null && rpCtx.getConfiguration() != null &&
+                rpCtx.getProfileConfig() instanceof OIDCAuthenticationRelyingPartyProfileConfiguration) {
+            profileConfiguration = (OIDCAuthenticationRelyingPartyProfileConfiguration) rpCtx.getProfileConfig();
+        }
+        if (profileConfiguration == null) {
+            log.error("{} Profile configuration not found", getLogPrefix());   
+            throw new MessageHandlerException("No OAuth2 client authentication context found or created");             
+        }
+        
+        // Can be null
+        jwtBearerClientAuthSecurityParameters = securityParametersContextLookupStrategy.apply(messageContext);
+        
+        final OIDCProviderMetadataContext providerCtx = providerMetadataLookupStrategy.apply(messageContext);
+        if (providerCtx == null || providerCtx.getProviderInformation() == null) {
+            log.error("{} Provider metadata not found", getLogPrefix());            
+            throw new MessageHandlerException("Provider metadata not found"); 
+        }
+        providerMetadata = providerCtx.getProviderInformation();
+        
+        clientAuthMethod = profileConfiguration.getTokenEndpointAuthMethod(PRC_LOOKUP.apply(messageContext));
+        if (clientAuthMethod.isEmpty()) {
+            throw new MessageHandlerException("No client authentication method found from profile configuration");
+        }
+       
+        clientId = profileConfiguration.getClientId(PRC_LOOKUP.apply(messageContext));
+        if (clientId == null) {
+            throw new MessageHandlerException("No client_id found from profile configuration");
+        }
+        
+        clientCredential = profileConfiguration.getClientCredential(PRC_LOOKUP.apply(messageContext));
+        
+        return true;
+         
+    }
+    
+    @Override
+    protected void doInvoke(final MessageContext messageContext) throws MessageHandlerException {
+        
+        final ClientAuthenticationMethod method = new ClientAuthenticationMethod(clientAuthMethod);
+        ClientAuthentication clientAuthentication = null;
+        
+        if (method.equals(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) || 
+                method.equals(ClientAuthenticationMethod.CLIENT_SECRET_POST)) {
+            
+            if (clientCredential == null) {
+                throw new MessageHandlerException("No client secret credential found from profile configuration, "
+                        + "can not construct client authenticaton");
+            }            
+            final Secret secret = new Secret(clientCredential.getSecret());            
+            // TODO redundent check for now, as the secret can not expire. Add back?
+            if (secret.expired()) {
+                log.warn("{} Client secret has expired for client '{}'", getLogPrefix(), clientId);
+                throw new MessageHandlerException("Client secret has expired");
+            }
+            if (method.equals(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)) {
+                clientAuthentication = new ClientSecretBasic(new ClientID(clientId), secret);
+            } else {
+                clientAuthentication = new ClientSecretPost(new ClientID(clientId), secret);
+            }
+            
+        } else if (method.equals(ClientAuthenticationMethod.CLIENT_SECRET_JWT)) {
+            verifySuitableClientSecretJWTSecurityContext();
+            clientAuthentication = new ClientSecretJWT(buildClientAuthenticationJwt());
+        } else if (method.equals(ClientAuthenticationMethod.PRIVATE_KEY_JWT)) {
+            verifySuitablePrivateKetJWTSecurityContext();
+            clientAuthentication = new PrivateKeyJWT(buildClientAuthenticationJwt());
+        } else {
+            log.warn("{}: Client authentication method '{}' not supported for client '{}'", getLogPrefix(), 
+                    method, clientId);
+        }
+        
+        if (clientAuthentication == null) {
+            throw new MessageHandlerException("Client authentication could be constructed");
+        }
+
+        oauth2ClientAuthenticationContext.setClientAuthentication(clientAuthentication);   
+        log.debug("{} Initialized OAuth2 Client Authentication Context: Found client authentication mode "
+                + "'{}' for client '{}'",getLogPrefix(), clientAuthentication.getMethod(), 
+                clientAuthentication.getClientID());
+        
+    }
+    
+    /**
+     * Check the populated security context is using the correct algorithm family for client_secret_jwt client 
+     * authentication.
+     * 
+     * @throws MessageHandlerException if the wrong algorithm family is specified in the security context
+     */
+    private void verifySuitableClientSecretJWTSecurityContext() throws MessageHandlerException {
+        if (jwtBearerClientAuthSecurityParameters == null ||
+                jwtBearerClientAuthSecurityParameters.getSignatureSigningParameters() == null) {
+            throw new MessageHandlerException("Missing security parameters needed to build client_secret_jwt");
+        }
+        if (jwtBearerClientAuthSecurityParameters.getSignatureSigningParameters().getSigningCredential() == null) {
+            throw new MessageHandlerException("Missing credential needed to build client_secret_jwt");
+        }
+        final Algorithm jwsAlgorithm = 
+                resolveAlgorithm(jwtBearerClientAuthSecurityParameters.getSignatureSigningParameters());
+        if (!JWSAlgorithm.Family.HMAC_SHA.contains(jwsAlgorithm)) {
+            throw new MessageHandlerException("Trying to construct client_secret_jwt using the wrong algorithm: "
+                    + jwsAlgorithm);
+        }        
+    }
+    
+    
+    /**
+     * Check the populated security context is using the correct algorithm family for private_key_jwt client 
+     * authentication.
+     * 
+     * @throws MessageHandlerException if the wrong algorithm family is specified in the security context
+     */
+    private void verifySuitablePrivateKetJWTSecurityContext() throws MessageHandlerException {
+        if (jwtBearerClientAuthSecurityParameters == null ||
+                jwtBearerClientAuthSecurityParameters.getSignatureSigningParameters() == null) {
+            throw new MessageHandlerException("Missing security parameters needed to private_key_jwt");
+        }
+        if (jwtBearerClientAuthSecurityParameters.getSignatureSigningParameters().getSigningCredential() == null) {
+            throw new MessageHandlerException("Missing credential needed to build private_key_jwt");
+        }
+        final Algorithm jwsAlgorithm = 
+                resolveAlgorithm(jwtBearerClientAuthSecurityParameters.getSignatureSigningParameters());
+        if (!JWSAlgorithm.Family.SIGNATURE.contains(jwsAlgorithm)) {
+            throw new MessageHandlerException("Trying to construct private_key_jwt using the wrong algorithm: "
+                    + jwsAlgorithm);
+        }        
+    }
+
+    /**
+     * Build the claim values required for a client authentication bearer JWT.
+     * 
+     * @param clientId the client identifier
+     * 
+     * @return the constructed JWT claims set
+     */
+    private JWTClaimsSet buildClientAuthenticationJwtClaims() {        
+        return new JWTClaimsSet.Builder()
+                .subject(clientId)
+                .issuer(clientId)
+                .audience(providerMetadata.getTokenEndpointURI().toString())
+                .jwtID(OIDCProxySupport.generateNonce(32))
+                .issueTime(Date.from(Instant.now()))
+                .expirationTime(Date.from(Instant.now().plus(jwtBearerExpiryOffset)))                
+                .build();
+    }
+    
+    /**
+     * Build a signed JWT bearer token for client authentication using the populated security parameters. Relies on
+     * the correct alg and credential existing in the security context ahead of time for the correct SignedJWT to be
+     * returned e.g. for either client_secret_jwt or private_key_jwt.
+     * 
+     * @return a signed JWT bearer token, or {@code null} if there was an error during construction
+     */
+    @Nullable private SignedJWT buildClientAuthenticationJwt() {
+        if (jwtBearerClientAuthSecurityParameters == null || 
+                jwtBearerClientAuthSecurityParameters.getSignatureSigningParameters() == null) {
+            log.error("{} Requested client_secret_jwt client authentication, but signing parameters could not "
+                    + "be found",getLogPrefix());
+            return null;
+        }
+        final JWTClaimsSet claims = buildClientAuthenticationJwtClaims();
+        final SignedJWT signed = signClaims(claims, jwtBearerClientAuthSecurityParameters);
+        if (signed == null) {
+            log.trace("Could not construct client_secret_jwt client authentication");
+            return null;
+        }
+        return signed;
+    }
+
+    /**
+     * Sign the given JWT claims set using the signing parameters from the context.
+     * 
+     * @param jwtClaimSetToSign the claims to sign
+     * @param secContext the security context to determine the signing algorithm and keys
+     * 
+     * @return a signed JWT or {@code null} if an error occurs.
+     */
+    @Nullable private SignedJWT signClaims(@Nonnull final JWTClaimsSet jwtClaimSetToSign, 
+            @Nonnull final SecurityParametersContext secContext) {
+        try {
+            SignedJWT jwt = null; 
+            final Credential credential = secContext.getSignatureSigningParameters().getSigningCredential();
+            final Algorithm jwsAlgorithm = resolveAlgorithm(secContext.getSignatureSigningParameters());
+            final JWSSigner signer = getSigner(jwsAlgorithm, credential);
+            final JWSHeader.Builder headerBuilder = new JWSHeader.Builder(new JWSAlgorithm(jwsAlgorithm.getName()))
+                    .keyID(CredentialConversionUtil.resolveKid(credential));
+            headerBuilder.type(JOSEObjectType.JWT);
+            jwt = new SignedJWT(headerBuilder.build(), jwtClaimSetToSign);
+            jwt.sign(signer);
+            if (log.isDebugEnabled() && !log.isTraceEnabled()) {
+                log.debug("{} Signed JWT Bearer Token for client authentication using kid '{}'", getLogPrefix(), 
+                        CredentialConversionUtil.resolveKid(credential));
+            } else if (log.isTraceEnabled()) {
+                log.trace("{} Signed JWT Bearer Token for client authentication using kid '{}': {}", getLogPrefix(), 
+                        CredentialConversionUtil.resolveKid(credential),jwt.serialize());
+            }            
+            
+            if (jwt.getState() != State.SIGNED) {
+                // Should not really happen, as JOSEException should be thrown
+                log.error("{} JWT Bearer Token for client authentication was not signed", getLogPrefix());
+                return null;
+            }
+            return jwt;
+            
+        } catch (final JOSEException e) {
+            log.error("{} Error signing claims set: {}", getLogPrefix(), e.getMessage());
+            return null;
+        }
+    }
+    
+    /**
+     * Returns correct implementation of signer based on algorithm type.
+     * 
+     * @param jwsAlgorithm JWS algorithm
+     * @param credential the credential to use
+     * @return signer for algorithm and private key
+     * @throws JOSEException if algorithm cannot be supported
+     */
+    private JWSSigner getSigner(final Algorithm jwsAlgorithm, final Credential credential) throws JOSEException {
+        if (JWSAlgorithm.Family.EC.contains(jwsAlgorithm)) {
+            return new ECDSASigner((ECPrivateKey) credential.getPrivateKey());
+        }
+        if (JWSAlgorithm.Family.RSA.contains(jwsAlgorithm)) {
+            return new RSASSASigner(credential.getPrivateKey());
+        }
+        if (JWSAlgorithm.Family.HMAC_SHA.contains(jwsAlgorithm)) {
+            return new MACSigner(credential.getSecretKey());
+        }
+        throw new JOSEException("Unsupported algorithm " + jwsAlgorithm.getName());
+    }
+    
+    /**
+     * Resolves JWS algorithm from signature signing parameters.
+     * 
+     * @param params the signature signing parameters
+     * @return JWS algorithm
+     */
+    protected JWSAlgorithm resolveAlgorithm(@Nonnull final SignatureSigningParameters params) {
+
+        final JWSAlgorithm algorithm = new JWSAlgorithm(params.getSignatureAlgorithm());
+        final Credential credential = params.getSigningCredential();
+        if (credential instanceof JWKCredential && !algorithm.equals(((JWKCredential) credential).getAlgorithm())) {
+            log.debug("{} Signature signing algorithm {} differs from JWK algorithm '{}'", getLogPrefix(),
+                    algorithm.getName(), ((JWKCredential) credential).getAlgorithm() != null ? 
+                            ((JWKCredential) credential).getAlgorithm() : "not specified");            
+        }
+        log.trace("{} Algorithm resolved {}", getLogPrefix(), algorithm.getName());
+        return algorithm;
+    }
+
+    /**
+     * Convert the encoded byte array representing the secret into a UTF-8 String.
+     * 
+     * @param key the key to convert
+     * @return the UTF-8 encoded string value of the secret. 
+     */
+    @Nullable private String convertSecretKeyToString(@Nullable final SecretKey key) {
+        if (key == null || key.getEncoded() == null) {
+            return null;
+        }
+        return new String(key.getEncoded(),StandardCharsets.UTF_8);
+    }
+    
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/security/impl/ClientAuthenticationConfigurationLookupFunction.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/security/impl/ClientAuthenticationConfigurationLookupFunction.java
new file mode 100644
index 0000000..cb1fb0b
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/security/impl/ClientAuthenticationConfigurationLookupFunction.java
@@ -0,0 +1,211 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.security.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 java.util.stream.Collectors;
+
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jose.Algorithm;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
+
+import net.shibboleth.idp.profile.config.ProfileConfiguration;
+import net.shibboleth.idp.profile.config.SecurityConfiguration;
+import net.shibboleth.idp.profile.context.RelyingPartyContext;
+import net.shibboleth.idp.relyingparty.RelyingPartyConfigurationResolver;
+import net.shibboleth.oidc.profile.config.JSONSecurityConfiguration;
+import net.shibboleth.oidc.profile.config.OIDCAuthenticationRelyingPartyProfileConfiguration;
+import net.shibboleth.oidc.security.jose.SignatureSigningConfiguration;
+import net.shibboleth.oidc.security.jose.impl.BasicSignatureSigningConfiguration;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotLive;
+import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * 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;
+
+    /** A resolver for default security configurations. */
+    @Nullable private RelyingPartyConfigurationResolver rpResolver;
+
+    /** Constructor. */
+    public ClientAuthenticationConfigurationLookupFunction() {
+        relyingPartyContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class);
+    }
+
+    /**
+     * Set the resolver for default security configurations.
+     * 
+     * @param resolver
+     *            the resolver to use
+     */
+    public void setRelyingPartyConfigurationResolver(@Nullable final RelyingPartyConfigurationResolver resolver) {
+        rpResolver = resolver;
+    }
+
+    /**
+     * 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
+    @Nullable
+    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 JSONSecurityConfiguration
+                    && ((JSONSecurityConfiguration) pc.getSecurityConfiguration(PRC_LOOKUP.apply(input)))
+                            .getJwtSignatureSigningConfiguration() != null) {
+                configs.add(((JSONSecurityConfiguration) pc.getSecurityConfiguration(PRC_LOOKUP.apply(input)))
+                        .getJwtSignatureSigningConfiguration());                
+            }
+            if (pc instanceof OIDCAuthenticationRelyingPartyProfileConfiguration) {
+                tokenEndpointAuthMethod = ((OIDCAuthenticationRelyingPartyProfileConfiguration) pc)
+                        .getTokenEndpointAuthMethod(PRC_LOOKUP.apply(input));
+            }            
+        }
+
+        // Check for a per-profile default (relying party independent) config.
+        if (input != null && rpResolver != null) {
+            final SecurityConfiguration defaultConfig = rpResolver
+                    .getDefaultSecurityConfiguration(PRC_LOOKUP.apply(input).getProfileId());
+            if (defaultConfig instanceof JSONSecurityConfiguration
+                    && ((JSONSecurityConfiguration) defaultConfig).getJwtSignatureSigningConfiguration() != null) {
+                configs.add(((JSONSecurityConfiguration) defaultConfig).getJwtSignatureSigningConfiguration());
+            }
+        }
+
+        if (tokenEndpointAuthMethod == null) {
+            log.trace("Token endpoint client authentication method can not be found");
+            return Collections.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 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 Collections.emptyList();
+        }
+        
+        final List<String> filtered = algorithms.stream()
+            .filter(Objects::nonNull).filter(Predicate.not(String::isEmpty)).map(JWSAlgorithm::parse)
+            .filter(algFamily::contains).map(Algorithm::getName).collect(Collectors.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/idp-oidc-rp-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index e55efe6..314efaf 100644
--- a/idp-oidc-rp-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/idp-oidc-rp-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -25,6 +25,16 @@
         class="org.opensaml.messaging.context.navigate.ChildContextLookup"
         c:type="#{ T(net.shibboleth.oidc.profile.messaging.context.OIDCPeerEntityContext) }" />
         
+    <bean id="shibboleth.ChildLookupOrCreate.OAuth2ClientAuthenticationContext"
+        class="org.opensaml.messaging.context.navigate.ChildContextLookup"
+        c:type="#{ T(net.shibboleth.oidc.authn.context.OAuth2ClientAuthenticationContext) }" 
+        c:createContext="true"/>        
+        
+    <bean id="shibboleth.ChildLookupOrCreate.SecurityParametersContext"
+        class="org.opensaml.messaging.context.navigate.ChildContextLookup"
+        c:type="#{ T(net.shibboleth.oidc.security.jose.context.SecurityParametersContext) }"
+        c:createContext="true" />
+        
     <bean id="shibboleth.ChildLookup.AccessTokenResponseContext"
         class="org.opensaml.messaging.context.navigate.ChildContextLookup"
         c:type="#{ T(net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext) }" />
@@ -38,6 +48,15 @@
         </constructor-arg>
     </bean>
     
+    <bean id="shibboleth.ChildLookup.OIDCProviderMetadataContextFromMessageContext" parent="shibboleth.Functions.Compose">
+        <constructor-arg name="g">
+            <ref bean="shibboleth.ChildLookup.OIDCProviderMetadataContext" />
+        </constructor-arg>
+        <constructor-arg name="f">
+            <ref bean="shibboleth.ChildLookup.OIDCPeerEntityContext" />
+        </constructor-arg>
+    </bean>
+    
     <bean id="shibboleth.ChildLookup.OIDCProviderMetadataContextFromInbound" parent="shibboleth.Functions.Compose">
         <constructor-arg name="g">
             <ref bean="shibboleth.ChildLookup.OIDCProviderMetadataContext" />
@@ -46,6 +65,24 @@
             <ref bean="shibboleth.ChildLookup.OIDCPeerEntityFromInbound" />
         </constructor-arg>
     </bean>
+    
+    <bean id="shibboleth.ChildLookupOrCreate.OAuth2ClientAuthenticationContextFromOIDCPeer" parent="shibboleth.Functions.Compose">
+        <constructor-arg name="g">
+            <ref bean="shibboleth.ChildLookupOrCreate.OAuth2ClientAuthenticationContext" />
+        </constructor-arg>
+        <constructor-arg name="f">
+            <ref bean="shibboleth.ChildLookup.OIDCPeerEntityContext" />
+        </constructor-arg>
+    </bean>
+    
+     <bean id="shibboleth.ChildLookupOrCreate.SecurityParametersFromOAuth2ClientAuthenticationContext" parent="shibboleth.Functions.Compose">
+        <constructor-arg name="g">
+            <ref bean="shibboleth.ChildLookupOrCreate.SecurityParametersContext" />
+        </constructor-arg>
+        <constructor-arg name="f">
+            <ref bean="shibboleth.ChildLookupOrCreate.OAuth2ClientAuthenticationContextFromOIDCPeer" />
+        </constructor-arg>
+    </bean>
 
     <bean id="shibboleth.ChildLookup.ProviderMetadataFromProviderContext" 
     class="net.shibboleth.idp.profile.context.navigate.SpringExpressionContextLookupFunction"
@@ -127,6 +164,16 @@
             <ref bean="shibboleth.MessageContextLookup.Outbound" />
         </constructor-arg>
     </bean>
+    
+    <bean id="shibboleth.ChildLookupOrCreate.SecurityParametersContextInAccessTokenResponseContext" 
+            parent="shibboleth.Functions.Compose">
+        <constructor-arg name="g">
+            <ref bean="shibboleth.ChildLookupOrCreate.SecurityParametersContext" />
+        </constructor-arg>
+        <constructor-arg name="f">
+            <ref bean="shibboleth.ChildLookup.AccessTokenResponseContext" />
+        </constructor-arg>
+    </bean>
         
     <bean id="shibboleth.MessageLookup.AuthenticationResponse"
         class="org.opensaml.messaging.context.navigate.MessageLookup"
diff --git a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml
index 05042c1..4d7b50e 100644
--- a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml
+++ b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml
@@ -370,9 +370,52 @@
 
     <!-- CODE flow beans -->
 
-    <bean id="InitializeOAuth2ClientAuthenticationContext" scope="prototype"
-        class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.InitializeOAuth2ClientAuthenticationContext"
-        p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext" />
+     <bean id="InitializeOAuth2ClientAuthenticationContextHandler" parent="NestedWebFlowMessageHandlerAdaptor"
+        scope="prototype" c:executionDirection="OUTBOUND">
+         <constructor-arg>
+            <bean class="org.opensaml.messaging.handler.impl.BasicMessageHandlerChain">
+                <property name="handlers">
+                    <list>
+                        <bean id="InitializeOAuth2ClientAuthenticationContext" scope="prototype"
+                            class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.InitializeOAuth2ClientAuthenticationContext"
+                            p:oAuth2ClientAuthenticationContextLookupStrategy-ref="shibboleth.ChildLookupOrCreate.OAuth2ClientAuthenticationContextFromOIDCPeer"/>
+                        <bean id="PopulateJWTClientAuthenticationSignatureSigningParameters" scope="prototype"
+					        class="net.shibboleth.oidc.profile.impl.PopulateJWTSignatureSigningParametersHandler"
+					        p:noResultIsError="true"
+					        p:securityParametersContextLookupStrategy-ref="shibboleth.ChildLookupOrCreate.SecurityParametersFromOAuth2ClientAuthenticationContext"
+					        p:providerMetadataContextLookupStrategy-ref="shibboleth.ChildLookup.OIDCProviderMetadataContextFromMessageContext">
+					        <!-- Is only required for client_secret_jwt and private_key_jwt -->
+					        <property name="activationCondition">
+				                <bean id="JWTBearerTokenForClientAuthenticationCondition"
+				                    class="net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.logic.JWTBearerTokenForClientAuthenticationPredicate"/>
+				            </property>
+					        <property name="configurationLookupStrategy">
+					           <bean class="net.shibboleth.idp.plugin.authn.oidc.rp.security.impl.ClientAuthenticationConfigurationLookupFunction"
+					               scope="prototype" />
+					        </property>
+					        <property name="signatureSigningParametersResolver">
+					               <bean class="net.shibboleth.oidc.security.jose.impl.RelyingPartySigningParametersResolver"
+					               scope="prototype">
+					               <property name="providerMetadataAlgorithmLookupStrategy">					                 
+		                                <bean
+		                                    class="net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.ProviderMetadataStringListValueLookupFunction"
+		                                    c:keyName="token_endpoint_auth_signing_alg_values_supported" />                           
+					               </property>
+					               </bean>
+					        </property> 
+					    </bean>
+                         <bean id="InitializeOAuth2ClientAuthenticationMethodHandler" scope="prototype"
+                            class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.InitializeOAuth2ClientAuthenticationMethodHandler"
+                            p:securityParametersContextLookupStrategy-ref="shibboleth.ChildLookupOrCreate.SecurityParametersFromOAuth2ClientAuthenticationContext"
+                            p:jwtBearerExpiryOffset="%{idp.authn.oidc.rp.client.authenticationMethod.jwt.expiryOffset:PT30S}"/>
+                    </list>
+                </property>            
+            </bean>
+         </constructor-arg>
+          <property name="errorEvent">
+            <util:constant static-field="org.opensaml.profile.action.EventIds.INVALID_MESSAGE" />
+          </property>
+    </bean>
 
     <bean id="ExchangeCodeForAccessToken" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ExchangeCodeForAccessToken"
diff --git a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml
index 5b3c8be..bbdbca8 100644
--- a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml
+++ b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml
@@ -111,7 +111,7 @@
     </decision-state>
 
     <action-state id="AuthorizationCodeFlow">        
-        <evaluate expression="InitializeOAuth2ClientAuthenticationContext" />
+        <evaluate expression="InitializeOAuth2ClientAuthenticationContextHandler" />
         <evaluate expression="ExchangeCodeForAccessToken" />
         <evaluate expression="ValidateOAuthAccessTokenResponse" />
         <evaluate expression="PopulateIDTokenDecryptionParameters" />
diff --git a/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/oidc-rp.properties b/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/oidc-rp.properties
index 662dd20..ef03dfd 100644
--- a/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/oidc-rp.properties
+++ b/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/oidc-rp.properties
@@ -13,8 +13,9 @@ idp.authn.oidc.rp.client.redirecturl.allowedOrigins = https://localhost:8443
 
 ## Override the default response_mode for the given response_type
 #idp.authn.oidc.rp.client.responseMode = query
-## Client authentication method. Currently client_secret_basic and client_secret_post
+## Client authentication method.
 #idp.authn.oidc.rp.client.authenticationMethod = client_secret_basic
+#idp.authn.oidc.rp.client.authenticationMethod.jwt.expiryOffset = PT30S
 ## Comma seperated list of additional scopes e.g. profile or email. The openid scope is added by default
 #idp.authn.oidc.rp.client.scopes =
 
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java
index f39d4e8..07e1c12 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java
@@ -20,7 +20,6 @@ package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
 import java.net.URI;
 import java.time.Duration;
 import java.util.Map;
-import java.util.Set;
 
 import javax.annotation.Nonnull;
 
@@ -59,10 +58,10 @@ public abstract class AbstractOIDCTest {
     private static final String REDIRECT_URI_OVERRIDE = "https://localhost/callback";
     
     /** The client_id.*/
-    private static final String CLIENT_ID = "demo_rp";
+    protected static final String CLIENT_ID = "demo_rp";
     
     /** The client_secret.*/
-    private static final String CLIENT_SECRET = "Xp2s5v8y/B?E(H+MbQeThWmYq3t6w9z$";
+    protected static final String CLIENT_SECRET = "Xp2s5v8y/B?E(H+MbQeThWmYq3t6w9z$";
     
     /** Provider configuration.*/
     protected final String GOOD_PROVIDER_CONFIGURATION_INFO = 
@@ -286,7 +285,7 @@ public abstract class AbstractOIDCTest {
         partyConfig = new DefaultOIDCAuthorizationConfiguration();  
         partyContext.setProfileConfig(partyConfig);
         partyConfig.setClientCredential(TestCredentialHelper.createClientSecretCredential(CLIENT_SECRET));
-        partyConfig.setTokenEndpointAuthMethods(Set.of("client_secret_basic"));
+        partyConfig.setTokenEndpointAuthMethod("client_secret_basic");
         partyConfig.setClientId(CLIENT_ID);
         partyConfig.setRedirectUriOverride(REDIRECT_URI_OVERRIDE);
         final RelyingPartyConfiguration rPartyConfig = new RelyingPartyConfiguration();
@@ -319,5 +318,16 @@ public abstract class AbstractOIDCTest {
         return new JSONObject(tokenResponseAsMap);
     }
     
+    /**
+     * Return the Profile configuration from the relying party context.
+     * 
+     * @param prc the prc
+     * @return the relying party configuration
+     */
+    protected DefaultOIDCAuthorizationConfiguration getRelyingPartyProfileConfig(final ProfileRequestContext prc) {
+        return (DefaultOIDCAuthorizationConfiguration) prc.getSubcontext(RelyingPartyContext.class)
+                .getProfileConfig();
+    }
+    
 
 }
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOAuth2ClientAuthenticationMethodHandlerTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOAuth2ClientAuthenticationMethodHandlerTest.java
new file mode 100644
index 0000000..a98ab78
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOAuth2ClientAuthenticationMethodHandlerTest.java
@@ -0,0 +1,257 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.opensaml.security.credential.BasicCredential;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jose.jwk.KeyUse;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
+import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
+import com.nimbusds.oauth2.sdk.auth.ClientSecretJWT;
+import com.nimbusds.oauth2.sdk.auth.ClientSecretPost;
+import com.nimbusds.oauth2.sdk.auth.PrivateKeyJWT;
+
+import net.shibboleth.oidc.authn.context.OAuth2ClientAuthenticationContext;
+import net.shibboleth.oidc.profile.messaging.context.OIDCPeerEntityContext;
+import net.shibboleth.oidc.security.credential.DefaultClientSecretCredential;
+import net.shibboleth.oidc.security.jose.SignatureSigningParameters;
+import net.shibboleth.oidc.security.jose.context.SecurityParametersContext;
+
+/** Tests for {@link InitializeOAuth2ClientAuthenticationMethodHandler}.*/
+public class InitializeOAuth2ClientAuthenticationMethodHandlerTest extends AbstractOIDCTest {
+    
+    private InitializeOAuth2ClientAuthenticationMethodHandler handler;
+    
+    @Override
+    @BeforeMethod
+    public void setup() throws Exception {
+        super.setup();
+        handler = new InitializeOAuth2ClientAuthenticationMethodHandler();
+        
+    }
+    
+    @Test
+    public void testInitialiseClientSecretBasic_Success() throws Exception {
+        partyConfig.setTokenEndpointAuthMethod("client_secret_basic");
+        handler.initialize();
+        handler.invoke(prc.getOutboundMessageContext());
+        
+        final var context = prc.getOutboundMessageContext().getSubcontext(OIDCPeerEntityContext.class)
+                .getSubcontext(OAuth2ClientAuthenticationContext.class);
+        assertNotNull(context);
+        assertNotNull(context.getClientAuthentication());
+        assertTrue(context.getClientAuthentication() instanceof ClientSecretBasic);
+        final var basicClientAuth = (ClientSecretBasic) context.getClientAuthentication();
+        assertEquals(basicClientAuth.getClientSecret().getValue(),CLIENT_SECRET);
+    }
+    
+    @Test(expectedExceptions = MessageHandlerException.class)
+    public void testInitialiseClientSecretBasic_NoCredential() throws Exception {
+        partyConfig.setTokenEndpointAuthMethod("client_secret_basic");
+        partyConfig.setClientCredential(null);
+        
+        handler.initialize();
+        handler.invoke(prc.getOutboundMessageContext());        
+        
+    }
+    
+    @Test(expectedExceptions = MessageHandlerException.class)
+    public void testInitialiseUnsportedClientAuthenticationMethod() throws Exception {
+        partyConfig.setTokenEndpointAuthMethod("unsupported");
+        partyConfig.setClientCredential(null);
+        
+        handler.initialize();
+        handler.invoke(prc.getOutboundMessageContext());        
+        
+    }
+    
+    @Test
+    public void testInitialiseClientSecretPost_Success() throws Exception {
+        partyConfig.setTokenEndpointAuthMethod("client_secret_post");
+        handler.initialize();
+        handler.invoke(prc.getOutboundMessageContext());
+        
+        final var context = prc.getOutboundMessageContext().getSubcontext(OIDCPeerEntityContext.class)
+                .getSubcontext(OAuth2ClientAuthenticationContext.class);
+        assertNotNull(context);
+        assertNotNull(context.getClientAuthentication());
+        assertTrue(context.getClientAuthentication() instanceof ClientSecretPost);
+        final var basicClientAuth = (ClientSecretPost) context.getClientAuthentication();
+        assertEquals(basicClientAuth.getClientSecret().getValue(),CLIENT_SECRET);
+    }
+    
+    @Test
+    public void testInitialiseClientSecretJWT_Success() throws Exception {
+        partyConfig.setTokenEndpointAuthMethod("client_secret_jwt");
+        
+        final SecurityParametersContext secContext = new SecurityParametersContext();
+        final SignatureSigningParameters secParams = new SignatureSigningParameters();
+        secParams.setSignatureAlgorithm("HS256");
+        secContext.setSignatureSigningParameters(secParams);
+        secParams.setSigningCredential(new DefaultClientSecretCredential(CLIENT_SECRET).toSigningCredential());
+        
+        prc.getOutboundMessageContext().addSubcontext(secContext);
+        
+        handler.initialize();
+        handler.invoke(prc.getOutboundMessageContext());
+        
+        final var context = prc.getOutboundMessageContext().getSubcontext(OIDCPeerEntityContext.class)
+                .getSubcontext(OAuth2ClientAuthenticationContext.class);
+        assertNotNull(context);
+        assertNotNull(context.getClientAuthentication());
+        assertTrue(context.getClientAuthentication() instanceof ClientSecretJWT);
+        final var clientSecretJwt = (ClientSecretJWT) context.getClientAuthentication();
+        assertNotNull(clientSecretJwt);
+        assertNotNull(clientSecretJwt.getJWTAuthenticationClaimsSet());
+        assertNotNull(clientSecretJwt.getClientAssertion());
+        assertEquals(clientSecretJwt.getJWTAuthenticationClaimsSet().getClientID().toString(), CLIENT_ID);
+        assertEquals(clientSecretJwt.getJWTAuthenticationClaimsSet().getSubject().toString(), CLIENT_ID);
+        assertEquals(clientSecretJwt.getJWTAuthenticationClaimsSet().getIssuer().toString(), CLIENT_ID);        
+        assertEquals(clientSecretJwt.getJWTAuthenticationClaimsSet().getAudience().size(),1);
+        assertEquals(clientSecretJwt.getJWTAuthenticationClaimsSet().getAudience().get(0).toString(),
+                "https://www.certification.openid.net/test/a/test_rp_proxy/token");
+        assertNotNull(clientSecretJwt.getJWTAuthenticationClaimsSet().getJWTID());
+        assertNotNull(clientSecretJwt.getJWTAuthenticationClaimsSet().getExpirationTime());
+    }
+    
+    
+    @Test(expectedExceptions = MessageHandlerException.class)
+    public void testInitialiseClientSecretJWT_NoSecurityParams() throws Exception {
+        partyConfig.setTokenEndpointAuthMethod("client_secret_jwt");
+        handler.initialize();
+        handler.invoke(prc.getOutboundMessageContext());
+        
+        final var context = prc.getOutboundMessageContext().getSubcontext(OIDCPeerEntityContext.class)
+                .getSubcontext(OAuth2ClientAuthenticationContext.class);
+    }
+    
+    @Test
+    public void testInitialisePrivateKeyJWT_Success() throws Exception {
+        partyConfig.setTokenEndpointAuthMethod("private_key_jwt");
+        
+        final SecurityParametersContext secContext = new SecurityParametersContext();
+        final SignatureSigningParameters secParams = new SignatureSigningParameters();
+        secParams.setSignatureAlgorithm("RS256");
+        secContext.setSignatureSigningParameters(secParams);
+        final RSAKey key = new RSAKeyGenerator(2048)
+                .keyID("1")
+                .keyUse(KeyUse.ENCRYPTION)
+                .generate();
+        secParams.setSigningCredential(new BasicCredential(key.toPublicKey(), key.toPrivateKey()));
+        
+        prc.getOutboundMessageContext().addSubcontext(secContext);
+        
+        handler.initialize();
+        handler.invoke(prc.getOutboundMessageContext());
+        
+        final var context = prc.getOutboundMessageContext().getSubcontext(OIDCPeerEntityContext.class)
+                .getSubcontext(OAuth2ClientAuthenticationContext.class);
+        assertNotNull(context);
+        assertNotNull(context.getClientAuthentication());
+        assertTrue(context.getClientAuthentication() instanceof PrivateKeyJWT);
+        final var privateKeyJwt = (PrivateKeyJWT) context.getClientAuthentication();
+        assertNotNull(privateKeyJwt);
+        assertNotNull(privateKeyJwt.getJWTAuthenticationClaimsSet());
+        assertNotNull(privateKeyJwt.getClientAssertion());
+        assertEquals(privateKeyJwt.getJWTAuthenticationClaimsSet().getClientID().toString(), CLIENT_ID);
+        assertEquals(privateKeyJwt.getJWTAuthenticationClaimsSet().getSubject().toString(), CLIENT_ID);
+        assertEquals(privateKeyJwt.getJWTAuthenticationClaimsSet().getIssuer().toString(), CLIENT_ID);        
+        assertEquals(privateKeyJwt.getJWTAuthenticationClaimsSet().getAudience().size(),1);
+        assertEquals(privateKeyJwt.getJWTAuthenticationClaimsSet().getAudience().get(0).toString(),
+                "https://www.certification.openid.net/test/a/test_rp_proxy/token");
+        assertNotNull(privateKeyJwt.getJWTAuthenticationClaimsSet().getJWTID());
+        assertNotNull(privateKeyJwt.getJWTAuthenticationClaimsSet().getExpirationTime());
+    }
+    
+    @Test(expectedExceptions = MessageHandlerException.class)
+    public void testInitialisePrivateKeyJWT_WrongAlgorithm() throws Exception {
+        partyConfig.setTokenEndpointAuthMethod("private_key_jwt");
+        
+        final SecurityParametersContext secContext = new SecurityParametersContext();
+        final SignatureSigningParameters secParams = new SignatureSigningParameters();
+        secParams.setSignatureAlgorithm("HS256");
+        secContext.setSignatureSigningParameters(secParams);
+        final RSAKey key = new RSAKeyGenerator(2048)
+                .keyID("1")
+                .keyUse(KeyUse.ENCRYPTION)
+                .generate();
+        secParams.setSigningCredential(new BasicCredential(key.toPublicKey(), key.toPrivateKey()));
+        
+        prc.getOutboundMessageContext().addSubcontext(secContext);
+        
+        handler.initialize();
+        handler.invoke(prc.getOutboundMessageContext());        
+    }
+    
+    @Test(expectedExceptions = MessageHandlerException.class)
+    public void testInitialiseClientSecretJWT_WrongAlgorithm() throws Exception {
+        partyConfig.setTokenEndpointAuthMethod("client_secret_jwt");
+        
+        final SecurityParametersContext secContext = new SecurityParametersContext();
+        final SignatureSigningParameters secParams = new SignatureSigningParameters();
+        secParams.setSignatureAlgorithm("RS256");
+        secContext.setSignatureSigningParameters(secParams);
+        secParams.setSigningCredential(new DefaultClientSecretCredential(CLIENT_SECRET).toSigningCredential());
+        
+        prc.getOutboundMessageContext().addSubcontext(secContext);
+        
+        handler.initialize();
+        handler.invoke(prc.getOutboundMessageContext());
+       
+    }
+    
+    @Test(expectedExceptions = MessageHandlerException.class)
+    public void testInitialisePrivateKeyJWT_NoCredential() throws Exception {
+        partyConfig.setTokenEndpointAuthMethod("private_key_jwt");
+        
+        final SecurityParametersContext secContext = new SecurityParametersContext();
+        final SignatureSigningParameters secParams = new SignatureSigningParameters();
+        secParams.setSignatureAlgorithm("RS256");
+        secContext.setSignatureSigningParameters(secParams);
+        
+        prc.getOutboundMessageContext().addSubcontext(secContext);
+        
+        handler.initialize();
+        handler.invoke(prc.getOutboundMessageContext());        
+    }
+    
+    @Test(expectedExceptions = MessageHandlerException.class)
+    public void testInitialiseClientSecretJWT_NoCredential() throws Exception {
+        partyConfig.setTokenEndpointAuthMethod("client_secret_jwt");
+        
+        final SecurityParametersContext secContext = new SecurityParametersContext();
+        final SignatureSigningParameters secParams = new SignatureSigningParameters();
+        secParams.setSignatureAlgorithm("HS256");
+        secContext.setSignatureSigningParameters(secParams);
+        
+        prc.getOutboundMessageContext().addSubcontext(secContext);
+        
+        handler.initialize();
+        handler.invoke(prc.getOutboundMessageContext());        
+    }
+
+}
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowFromAuthenticationResponseTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowFromAuthenticationResponseTest.java
index b853ab3..c1fdfd1 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowFromAuthenticationResponseTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowFromAuthenticationResponseTest.java
@@ -58,6 +58,7 @@ import net.shibboleth.oidc.security.credential.impl.ClientSecretCriterionCredent
 import net.shibboleth.oidc.security.impl.ExplicitKeySignedJWTTrustEngine;
 import net.shibboleth.oidc.security.impl.support.TestCredentialHelper;
 import net.shibboleth.oidc.security.jose.impl.BasicDecryptionConfiguration;
+import net.shibboleth.oidc.security.jose.impl.BasicSignatureSigningConfiguration;
 import net.shibboleth.oidc.security.jose.impl.BasicSignatureValidationConfiguration;
 import okhttp3.mockwebserver.MockResponse;
 import okhttp3.mockwebserver.MockWebServer;
@@ -154,6 +155,133 @@ public class OIDCRPFlowFromAuthenticationResponseTest extends OIDCRPFlowTest {
         assertEndUserClaimsVerified(nestedPrc);         
     }
     
+    /** 
+     * Test the flow from the external authorization request to the end of the flow.
+     * Using client_secret_jwt to client authentication.
+     * 
+     * @throws Exception on error.
+     */
+    @Test 
+    public void test_IDTokenHS256_PlainUserInfo_ClientSecretJWTClientAuth() throws Exception {
+        
+        basicSetup();
+        
+        final MockWebServer mockOPServer = createSimpleServer();
+        final var accessTokenResp = TestTokenHelper.createAccessTokenResponseJSON(
+                Map.of("iss", OP_ISSUER_ID, "azp", CLIENT_ID, "aud", List.of(CLIENT_ID)),
+                JWSAlgorithm.HS256, null, null, new DefaultClientSecretCredential(CLIENT_SECRET).toSigningCredential(), 
+                null);
+        final var userInfoResp = 
+                TestTokenHelper.createPlainUserInfoResponseString(Map.of("iss", OP_ISSUER_ID, "aud", List.of(CLIENT_ID)));
+        // First is token exchange
+        queueMockServerResponse(mockOPServer, 200, accessTokenResp, "application/json");
+        // Second is plain userInfo
+        queueMockServerResponse(mockOPServer, 200, userInfoResp, "application/json");
+        mockOPServer.start(9918);        
+        
+        final FlowExecutionImpl flowExecution = (FlowExecutionImpl)getFlowExecutionFactory()
+                .createFlowExecution(getFlowDefinition());
+        
+        final ProfileRequestContext prc = populateBasicContextTreeFromAuthnResponse(flowExecution);
+        
+        final DefaultOIDCAuthorizationConfiguration partyConfig = getRelyingPartyProfileConfig(prc);
+        partyConfig.setTokenEndpointAuthMethod("client_secret_jwt");        
+        
+        final JSONSecurityConfiguration secConfig = (JSONSecurityConfiguration) 
+                partyConfig.getSecurityConfiguration(prc);
+        final BasicSignatureSigningConfiguration signingConfig = new BasicSignatureSigningConfiguration();
+        // Add more than one type of algorithm and credential, and make sure the resolver and signer picks the correctly
+        // NOTE, the client_secret credential will be added by the resolver, so you do not need to add it here.
+        // Add unused key here
+        final RSAKey encKey = new RSAKeyGenerator(2048)
+                .keyID("1")
+                .keyUse(KeyUse.ENCRYPTION)
+                .generate();
+        signingConfig.setSigningCredentials(List.of(new BasicCredential(encKey.toPublicKey(), encKey.toPrivateKey())));
+        signingConfig.setSignatureAlgorithms(List.of("RS256","HS256"));
+        secConfig.setJwtSignatureSigningConfiguration(signingConfig);
+        
+        updateFlowExecution(flowExecution);
+        
+        //set start view and ending event to transition on.
+        externalContext.setEventId("proceed");
+        setCurrentState("AuthnRequest");       
+        resumeFlow(externalContext);       
+
+        mockOPServer.shutdown();        
+        
+        // Assert test conditions
+        
+        final var nestedPrc = assertStandardEndFlowSuccessConditions(prc); 
+        
+        assertIdTokenSignatureAndClaimsVerified(nestedPrc);
+        assertPlainJSONObjectUserInfoToken(nestedPrc);
+        assertEndUserClaimsVerified(nestedPrc);         
+    }
+    
+    /** 
+     * Test the flow from the external authorization request to the end of the flow.
+     * Using private_key_jwt to client authentication.
+     * 
+     * @throws Exception on error.
+     */
+    @Test 
+    public void test_IDTokenHS256_PlainUserInfo_PrivateKeyJWTClientAuth() throws Exception {
+        
+        basicSetup();
+        
+        final MockWebServer mockOPServer = createSimpleServer();
+        final var accessTokenResp = TestTokenHelper.createAccessTokenResponseJSON(
+                Map.of("iss", OP_ISSUER_ID, "azp", CLIENT_ID, "aud", List.of(CLIENT_ID)),
+                JWSAlgorithm.HS256, null, null, new DefaultClientSecretCredential(CLIENT_SECRET).toSigningCredential(), 
+                null);
+        final var userInfoResp = 
+                TestTokenHelper.createPlainUserInfoResponseString(Map.of("iss", OP_ISSUER_ID, "aud", List.of(CLIENT_ID)));
+        // First is token exchange
+        queueMockServerResponse(mockOPServer, 200, accessTokenResp, "application/json");
+        // Second is plain userInfo
+        queueMockServerResponse(mockOPServer, 200, userInfoResp, "application/json");
+        mockOPServer.start(9918);        
+        
+        final FlowExecutionImpl flowExecution = (FlowExecutionImpl)getFlowExecutionFactory()
+                .createFlowExecution(getFlowDefinition());
+        
+        final ProfileRequestContext prc = populateBasicContextTreeFromAuthnResponse(flowExecution);
+        
+        final DefaultOIDCAuthorizationConfiguration partyConfig = getRelyingPartyProfileConfig(prc);
+        partyConfig.setTokenEndpointAuthMethod("private_key_jwt");      
+        
+        // Create signature signing config for client_authentication JWT method
+        final RSAKey encKey = new RSAKeyGenerator(2048)
+                .keyID("1")
+                .keyUse(KeyUse.ENCRYPTION)
+                .generate();
+        final JSONSecurityConfiguration secConfig = (JSONSecurityConfiguration) 
+                partyConfig.getSecurityConfiguration(prc);
+        final BasicSignatureSigningConfiguration signingConfig = new BasicSignatureSigningConfiguration();
+        signingConfig.setSigningCredentials(List.of(new BasicCredential(encKey.toPublicKey(), encKey.toPrivateKey())));
+        // Ensure it chooses the RS algorithm here.
+        signingConfig.setSignatureAlgorithms(List.of("RS256","HS256"));
+        secConfig.setJwtSignatureSigningConfiguration(signingConfig);
+
+        updateFlowExecution(flowExecution);
+        
+        //set start view and ending event to transition on.
+        externalContext.setEventId("proceed");
+        setCurrentState("AuthnRequest");       
+        resumeFlow(externalContext);       
+
+        mockOPServer.shutdown();        
+        
+        // Assert test conditions
+        
+        final var nestedPrc = assertStandardEndFlowSuccessConditions(prc); 
+        
+        assertIdTokenSignatureAndClaimsVerified(nestedPrc);
+        assertPlainJSONObjectUserInfoToken(nestedPrc);
+        assertEndUserClaimsVerified(nestedPrc);         
+    }
+    
     
     @Test 
     public void test_IDTokenHS256_UserInfoHS256() throws Exception {
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java
index dbde6c2..4041162 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java
@@ -155,7 +155,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
      * mock server that is started. This OP supports the use of the request object.
      */
     protected static final ClassPathResource GOOD_PROVIDER_CONFIGURATION_INFO_USE_REQUEST_OBJECT_ENCRYPT = 
-            new ClassPathResource("/metadata/test-provider-requestobject-encrypt.json");;
+            new ClassPathResource("/metadata/test-provider-requestobject-encrypt.json");
     
     /**
      * Example of good provider metadata. Only supports RS256 signature alg for request object.
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/security/impl/ClientAuthenticationConfigurationLookupFunctionTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/security/impl/ClientAuthenticationConfigurationLookupFunctionTest.java
new file mode 100644
index 0000000..49ffc22
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/security/impl/ClientAuthenticationConfigurationLookupFunctionTest.java
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.security.impl;
+
+import static org.junit.Assert.assertNotNull;
+import static org.testng.Assert.assertEquals;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.opensaml.security.credential.BasicCredential;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jose.jwk.KeyUse;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.impl.AbstractOIDCTest;
+import net.shibboleth.oidc.profile.config.JSONSecurityConfiguration;
+import net.shibboleth.oidc.profile.config.impl.DefaultOIDCAuthorizationConfiguration;
+import net.shibboleth.oidc.security.credential.DefaultClientSecretCredential;
+import net.shibboleth.oidc.security.jose.impl.BasicSignatureSigningConfiguration;
+import net.shibboleth.utilities.java.support.logic.FunctionSupport;
+
+/** Tests for {@link ClientAuthenticationConfigurationLookupFunction}.*/
+public class ClientAuthenticationConfigurationLookupFunctionTest extends AbstractOIDCTest {
+    
+    private ClientAuthenticationConfigurationLookupFunction function;
+    
+    private DefaultOIDCAuthorizationConfiguration partyConfig;
+    
+    private BasicSignatureSigningConfiguration signingConfig;
+    
+    @Override
+    @BeforeMethod
+    public void setup() throws Exception {
+        super.setup();
+        function = new ClientAuthenticationConfigurationLookupFunction();
+        partyConfig = getRelyingPartyProfileConfig(prc);              
+        
+        // Create signature signing config for client_authentication JWT method
+        final RSAKey encKey = new RSAKeyGenerator(2048)
+                .keyID("1")
+                .keyUse(KeyUse.ENCRYPTION)
+                .generate();
+        final JSONSecurityConfiguration secConfig = new JSONSecurityConfiguration();
+        signingConfig = new BasicSignatureSigningConfiguration();
+        signingConfig.setSigningCredentials(List.of(new BasicCredential(encKey.toPublicKey(), encKey.toPrivateKey()),
+                new DefaultClientSecretCredential(CLIENT_SECRET).toSigningCredential()));
+        secConfig.setJwtSignatureSigningConfiguration(signingConfig);
+        partyConfig.setSecurityConfiguration(secConfig);
+    }
+    
+    @Test
+    public void testRS256_Success() {
+        // Ensure it chooses the RS algorithm here.
+        partyConfig.setTokenEndpointAuthMethod("private_key_jwt");
+        signingConfig.setSignatureAlgorithms(List.of("RS256","HS256"));
+        final var sigConfigs = function.apply(prc.getOutboundMessageContext());
+        assertNotNull(sigConfigs);
+        assertEquals(sigConfigs.size(), 1);
+        assertNotNull(sigConfigs.get(0).getSignatureAlgorithms());
+        assertNotNull(sigConfigs.get(0).getSigningCredentials());
+        assertEquals(sigConfigs.get(0).getSignatureAlgorithms().size(), 1);
+        assertEquals(sigConfigs.get(0).getSigningCredentials().size(), 2);
+        assertEquals(sigConfigs.get(0).getSignatureAlgorithms().get(0),"RS256");
+    }
+    
+    @Test
+    public void testRS256_NotSpecified() {
+        // Ensure it chooses the RS algorithm here.
+        partyConfig.setTokenEndpointAuthMethod("private_key_jwt");
+        signingConfig.setSignatureAlgorithms(List.of("HS256"));
+        final var sigConfigs = function.apply(prc.getOutboundMessageContext());
+        assertNotNull(sigConfigs);
+        assertEquals(sigConfigs.size(), 1);
+        assertNotNull(sigConfigs.get(0).getSignatureAlgorithms());
+        assertEquals(sigConfigs.get(0).getSignatureAlgorithms().size(), 0);
+        assertNotNull(sigConfigs.get(0).getSignatureAlgorithms());
+        assertEquals(sigConfigs.get(0).getSignatureAlgorithms().size(), 0);
+    }
+    
+    @Test
+    public void testHS256_Success() {
+        // Ensure it chooses the RS algorithm here.
+        partyConfig.setTokenEndpointAuthMethod("client_secret_jwt");
+        signingConfig.setSignatureAlgorithms(List.of("RS256","HS256"));
+        final var sigConfigs = function.apply(prc.getOutboundMessageContext());
+        assertNotNull(sigConfigs);
+        assertEquals(sigConfigs.size(), 1);
+        assertNotNull(sigConfigs.get(0).getSigningCredentials());
+        assertEquals(sigConfigs.get(0).getSignatureAlgorithms().size(), 1);
+        assertEquals(sigConfigs.get(0).getSigningCredentials().size(), 2);
+        assertEquals(sigConfigs.get(0).getSignatureAlgorithms().get(0),"HS256");
+    }
+    
+    @Test
+    public void testHS256_NotSpecified() {
+        // Ensure it chooses the RS algorithm here.
+        partyConfig.setTokenEndpointAuthMethod("client_secret_jwt");
+        signingConfig.setSignatureAlgorithms(List.of("RS256"));
+        final var sigConfigs = function.apply(prc.getOutboundMessageContext());
+        assertNotNull(sigConfigs);
+        assertEquals(sigConfigs.size(), 1);
+        assertNotNull(sigConfigs.get(0).getSigningCredentials());
+        assertEquals(sigConfigs.get(0).getSigningCredentials().size(), 2);
+        assertNotNull(sigConfigs.get(0).getSignatureAlgorithms());
+        assertEquals(sigConfigs.get(0).getSignatureAlgorithms().size(), 0);
+    }
+    
+    @Test
+    public void testNoTokenAuthenticationMethodSpecified() {
+        // Ensure it chooses the RS algorithm here.
+        partyConfig.setTokenEndpointAuthMethodLookupStrategy(FunctionSupport.constant(null));
+        signingConfig.setSignatureAlgorithms(List.of("RS256"));
+        final var sigConfigs = function.apply(prc.getOutboundMessageContext());
+        assertNotNull(sigConfigs);
+        assertEquals(sigConfigs.size(), 0);
+    }
+    
+    @Test
+    public void testNoAlgorithmsSpecified() {
+        // Ensure it chooses the RS algorithm here.
+        partyConfig.setTokenEndpointAuthMethod("client_secret_jwt");
+        signingConfig.setSignatureAlgorithms(Collections.emptyList());
+        final var sigConfigs = function.apply(prc.getOutboundMessageContext());
+        assertNotNull(sigConfigs);
+        assertEquals(sigConfigs.size(), 1);
+        assertNotNull(sigConfigs.get(0).getSignatureAlgorithms());
+        assertEquals(sigConfigs.get(0).getSignatureAlgorithms().size(), 0);
+        assertNotNull(sigConfigs.get(0).getSigningCredentials());
+        assertEquals(sigConfigs.get(0).getSigningCredentials().size(), 2);
+    }
+    
+    @Test
+    public void testNullAlgorithmsSpecified() {
+        // Ensure it chooses the RS algorithm here.
+        partyConfig.setTokenEndpointAuthMethod("client_secret_jwt");
+        signingConfig.setSignatureAlgorithms(null);
+        final var sigConfigs = function.apply(prc.getOutboundMessageContext());
+        assertNotNull(sigConfigs);
+        assertEquals(sigConfigs.size(), 1);
+        assertNotNull(sigConfigs.get(0).getSignatureAlgorithms());
+        assertEquals(sigConfigs.get(0).getSignatureAlgorithms().size(), 0);
+        assertNotNull(sigConfigs.get(0).getSigningCredentials());
+        assertEquals(sigConfigs.get(0).getSigningCredentials().size(), 2);
+    }
+
+}
diff --git a/idp-oidc-rp-impl/src/test/resources/metadata/test-provider-standard.json b/idp-oidc-rp-impl/src/test/resources/metadata/test-provider-standard.json
index 41404ac..ff71502 100644
--- a/idp-oidc-rp-impl/src/test/resources/metadata/test-provider-standard.json
+++ b/idp-oidc-rp-impl/src/test/resources/metadata/test-provider-standard.json
@@ -29,7 +29,9 @@
    ],
    "token_endpoint_auth_methods_supported":[
       "client_secret_post",
-      "client_secret_basic"
+      "client_secret_basic",
+      "private_key_jwt",
+      "client_secret_jwt"
    ],
    "claims_supported":[
       "aud",
@@ -54,5 +56,9 @@
       "refresh_token",
       "urn:ietf:params:oauth:grant-type:device_code",
       "urn:ietf:params:oauth:grant-type:jwt-bearer"
+   ],
+   "token_endpoint_auth_signing_alg_values_supported":[
+        "HS256",
+        "RS256"
    ]
 }
\ No newline at end of file

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


More information about the commits mailing list