[java-plugin-shibd-oidc] 05/06: WIP: JSHIBDOIDC-31 - Support new CredentialResolver service

Codeberg noreply at shibboleth.net
Wed Aug 12 16:18:29 UTC 2026


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

codeberg pushed a commit to branch dev/JSHIBDOIDC-31-cred-resolver
in repository java-plugin-shibd-oidc.

View the commit online:
https://codeberg.org/Shibboleth/java-plugin-shibd-oidc/commit/b5a21bab12a8b188335e0773a0d73d9846e19a95

commit b5a21bab12a8b188335e0773a0d73d9846e19a95
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Wed Aug 12 17:01:19 2026 +0100

    WIP: JSHIBDOIDC-31 - Support new CredentialResolver service
    
     - Inject the new credential resolver service to resolve client secrets
     - There is currently only one, default, global, client secret
     - New resolvers will be developed to lookup secrets for a given
    OP/Agent/Application
    
    https://shibboleth.atlassian.net/browse/JSHIBDOIDC-31
---
 .../net/shibboleth/sp/oidc/testing/TestHelper.java |  66 ++-
 .../META-INF/net.shibboleth.idp/postconfig.xml     |   5 +
 .../idp/flows/sp/consumer/oidc/oidc-beans.xml      |   7 +-
 .../net/shibboleth/sp/service/agent/postconfig.xml |  30 +-
 .../sp/service/credentials/postconfig.xml          |  17 +
 .../flows/AbstractOIDCTokenConsumerFlowTest.java   |   4 +
 .../sp/oidc/flows/OIDCTokenConsumerFlowTest.java   |  59 +++
 ...DCEnvironmentApplicationContextInitializer.java |   4 +-
 ...cationContextInitializerWithStorageService.java |   2 +-
 .../idp/module/conf/sp/oidc-test-agents.xml        |  12 +-
 .../net/shibboleth/sp/oidc-test-beans.xml          |   8 +-
 .../impl/ClientSecretCredentialResolver.java       | 168 ++++++++
 .../impl/StaticClientSecretCredentialResolver.java | 179 +++++++++
 ...izeOAuth2ClientAuthenticationMethodHandler.java | 113 ++++--
 .../profile/impl/BaseOIDCAuthenticationTest.java   |  12 +
 ...Auth2ClientAuthenticationMethodHandlerTest.java | 444 +++++++++++++++++++++
 16 files changed, 1073 insertions(+), 57 deletions(-)

diff --git a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/testing/TestHelper.java b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/testing/TestHelper.java
index 28290c8..24efc75 100644
--- a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/testing/TestHelper.java
+++ b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/testing/TestHelper.java
@@ -18,6 +18,7 @@ import static org.testng.Assert.fail;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.nio.charset.StandardCharsets;
+import java.security.KeyException;
 import java.security.PublicKey;
 import java.security.interfaces.ECPrivateKey;
 import java.security.interfaces.ECPublicKey;
@@ -28,11 +29,14 @@ import java.time.Instant;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import org.opensaml.security.credential.Credential;
+import org.opensaml.security.credential.UsageType;
+import org.opensaml.security.crypto.KeySupport;
 import org.testng.Assert;
 
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
@@ -73,6 +77,8 @@ import com.nimbusds.oauth2.sdk.id.State;
 import com.nimbusds.openid.connect.sdk.AuthenticationErrorResponse;
 import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
 
+import net.shibboleth.oidc.security.JWSAssemblyUtils;
+import net.shibboleth.oidc.security.credential.BasicExpiringJWKCredential;
 import net.shibboleth.oidc.security.credential.JWKCredential;
 import net.shibboleth.shared.codec.Base64Support;
 import net.shibboleth.shared.collection.CollectionSupport;
@@ -164,13 +170,15 @@ public final class TestHelper {
      * Build a basic {@link JWTClaimsSet} from the supplied parameters to mock a UserInfo response.
      * 
      * @param overrideClaims claims to override
+     * @param extraClaims, any extra claims.
      * 
      * @return the JWT claims set.
      */
     @SuppressWarnings("null")
-    @Nonnull public static JWTClaimsSet createBasicUserInfoClaims(final Map<String, Object> overrideClaims) {
+    @Nonnull public static JWTClaimsSet createBasicUserInfoClaimsWithExtra(final Map<String, Object> overrideClaims,
+    		final Map<String, Object> extraClaims) {
         
-        return new JWTClaimsSet.Builder()
+    	JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder()
                 .issuer(getClaimValue(overrideClaims, "iss", "test-issuer", String.class))
                 .audience(getClaimValue(overrideClaims, "aud", CollectionSupport.listOf("test-client"), List.class))
                 .subject(getClaimValue(overrideClaims, "sub", "jdoe", String.class))
@@ -178,8 +186,36 @@ public final class TestHelper {
                 .claim("given_name", getClaimValue(overrideClaims, "given_name", "Demo", String.class))
                 .claim("family_name", getClaimValue(overrideClaims, "family_name", "User", String.class))
                 .claim("nickname", getClaimValue(overrideClaims, "nickname", "Dee", String.class))
-                .claim("name",getClaimValue(overrideClaims, "name", "Demo T. User", String.class))
-                .build();
+                .claim("name",getClaimValue(overrideClaims, "name", "Demo T. User", String.class));
+    	
+    	if (extraClaims != null && !extraClaims.isEmpty()) {
+    		if (extraClaims.get("exp") instanceof Instant expI) {
+    			builder.expirationTime(Date.from(expI));
+    		}
+    		if (extraClaims.get("iat") instanceof Instant iatI) {
+    			builder.issueTime(Date.from(iatI));
+    		}
+    		extraClaims.forEach((k,v) -> {
+    			if (!Set.of("iss", "iat", "exp").contains(k)) {
+                    builder.claim(k, v);
+                }
+    		});
+    	}
+    	
+    	return builder.build();
+    }
+    
+    /**
+     * Build a basic {@link JWTClaimsSet} from the supplied parameters to mock a UserInfo response.
+     * 
+     * @param overrideClaims claims to override
+     * 
+     * @return the JWT claims set.
+     */
+    @SuppressWarnings("null")
+    @Nonnull public static JWTClaimsSet createBasicUserInfoClaims(final Map<String, Object> overrideClaims) {
+        
+        return createBasicUserInfoClaimsWithExtra(overrideClaims, null);
     }
     
     /** 
@@ -253,6 +289,28 @@ public final class TestHelper {
         return new SignedJWT(new JWSHeader(JWSAlgorithm.HS256),claims);       
     }
     
+    /**
+     * Create a direct encryption {@link JWKCredential} from the given shared secret.
+     * 
+     * @param secret the secret to convert to a {@link JWKCredential}.
+     * 
+     * @return the credential
+     * @throws KeyException on error creating the key
+     */
+    public static JWKCredential createDirectEncryptionCredentialFromSharedSecret(final String secret) 
+            throws KeyException {
+        final BasicExpiringJWKCredential jwkCredential = new BasicExpiringJWKCredential();
+        jwkCredential.setSecretKey(KeySupport.decodeSecretKey(
+                    JWSAssemblyUtils.getSecretBytes(secret), "AES"));
+        jwkCredential.setCredentialExpiresAt(Duration.ZERO);
+        jwkCredential.setUsageType(UsageType.UNSPECIFIED);
+        
+        jwkCredential.setKid("mockKey");
+        jwkCredential.getKeyNames().add("mockKey");
+        jwkCredential.setAlgorithm(JWEAlgorithm.DIR);
+        return jwkCredential;
+    }
+    
     /**
      * Create a JWT from the given payload. The JWT can either be plain, or signed and encrypted. If encrypted, it must
      * be signed. 
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index cd705eb..12da393 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -164,5 +164,10 @@
         </constructor-arg>
     </bean>   
     
+    <!-- Global abstract bean for client secret resolvers which are specified in a number of different bean files -->
+    <bean id="shibboleth.sp.oidc.ClientSecretCredentialResolver" class="net.shibboleth.sp.jose.config.impl.ClientSecretCredentialResolver"
+       c:resolver-ref="shibboleth.sp.CredentialResolverService" scope="prototype" abstract="true"
+       c:requestSupplier-ref="shibboleth.HttpServletRequestSupplier"/>
+    
 
 </beans>
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-beans.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-beans.xml
index 81409fe..7a16862 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-beans.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-beans.xml
@@ -87,7 +87,12 @@
                          <bean id="InitializeOAuth2ClientAuthenticationMethodHandler" scope="prototype"
                             class="net.shibboleth.sp.oidc.profile.impl.InitializeOAuth2ClientAuthenticationMethodHandler"
                             p:securityParametersContextLookupStrategy-ref="shibboleth.ChildLookupOrCreate.SecurityParametersFromOAuth2ClientAuthenticationContext"
-                            p:jwtBearerExpiryOffset="%{sp.oidc.authenticationMethod.jwt.expiryOffset:PT30S}"/> 
+                            p:jwtBearerExpiryOffset="%{sp.oidc.authenticationMethod.jwt.expiryOffset:PT30S}"
+                            p:issuerLookupStrategy-ref="shibboleth.ClientIdLookup.Simple">
+                            <property name="clientSecretResolver">
+                                <bean id="ClientSecretCredentialResolver" parent="shibboleth.sp.oidc.ClientSecretCredentialResolver"/>
+                            </property>
+                          </bean> 
                     </list>
                 </property>            
             </bean>
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/agent/postconfig.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/agent/postconfig.xml
index d938d4f..b122fa4 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/agent/postconfig.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/agent/postconfig.xml
@@ -58,14 +58,12 @@
     <bean id="AbstractOIDCProfile" abstract="true"
         p:securityConfiguration-ref="%{sp.oidc.security.config:shibboleth.oidc.DefaultSecurityConfiguration}" />
 
-    <bean id="AbstractOIDCSSOProfile" parent="AbstractOIDCProfile" abstract="true" 
-        p:issuer="#{getObject('shibboleth.oidc.issuer')}"        
+    <bean id="AbstractOIDCSSOProfile" parent="AbstractOIDCProfile" abstract="true"       
         p:claimsValidator="#{getObject('DefaultJWTClaimsValidator')}"
         p:forcePKCE="%{sp.oidc.forcePKCE:false}"
         p:allowPKCEPlain="%{sp.oidc.allowPKCEPlain:false}"      
         p:tokenEndpointAuthMethod="%{sp.oidc.authenticationMethod:client_secret_basic}"
         p:useTargetedEndpointAsJWTAudience="%{sp.oauth2.jwtAuth.targetedEndpointAsJWTAudience:true}"
-        p:clientCredential="#{%{sp.oidc.discoveryRequired:false} == true ? {null} : getObject('shibboleth.oidc.DefaultCredential')}"
         p:extractStandardAttributes="%{sp.oidc.extractStandardAttributes:false}"/>
 
    <bean id="OIDC.SSO" parent="AbstractOIDCSSOProfile" lazy-init="true"
@@ -134,13 +132,15 @@
         
    
     <!-- 
-        Bridging beans to link the basic security configuration to the CredentialResolver service to aquire credentials 
+        Bridging beans to link the basic security configuration to the CredentialResolver service to acquire credentials 
     -->
    
     <bean id="shibboleth.sp.oidc.BasicSignatureSigningConfiguration" parent="shibboleth.oidc.BasicSignatureSigningConfiguration"
         class="net.shibboleth.sp.jose.config.impl.BasicSignatureSigningConfiguration"
         c:resolver-ref="shibboleth.sp.CredentialResolverBridge" />
         
+
+        
    <!-- 
         Security configuration beans.
     -->
@@ -265,8 +265,7 @@
                     class="net.shibboleth.oidc.security.credential.impl.ProviderMetadataCredentialResolver"
                     c:remoteJwkSetCache-ref="shibboleth.oidc.RemoteJwkSetCache"
                     c:keyFetchInterval="%{sp.oidc.keyfetch.interval:PT30M}" />
-                <bean id="ClientSecretCriterionCredentialResolver"
-                    class="net.shibboleth.oidc.security.credential.impl.ClientSecretCriterionCredentialResolver"/>
+                <bean id="ClientSecretCredentialResolver" parent="shibboleth.sp.oidc.ClientSecretCredentialResolver"/>
             </list>
         </constructor-arg>
     </bean>
@@ -275,8 +274,7 @@
         class="net.shibboleth.oidc.security.credential.impl.ChainingJOSEObjectCredentialResolver">
         <constructor-arg>
             <list>
-                <bean id="ClientSecretCriterionCredentialResolver"
-                    class="net.shibboleth.oidc.security.credential.impl.ClientSecretCriterionCredentialResolver"/>
+                <bean id="ClientSecretCredentialResolver" parent="shibboleth.sp.oidc.ClientSecretCredentialResolver"/>
             </list>
         </constructor-arg>
     </bean>   
@@ -289,9 +287,7 @@
     
     <!-- 
         A resolver to public/private key encryption keys global to the RP 
-    -->
-    
-    
+    -->   
     
     <bean id="defaultOIDCKeyDecryptionCredentialResolver"
         class="net.shibboleth.oidc.security.credential.impl.ChainingJOSEObjectCredentialResolver">
@@ -325,8 +321,7 @@
 						    </bean>
                     </constructor-arg>
                 </bean>
-                <bean id="CriterionCredentialResolver"
-                    class="net.shibboleth.oidc.security.credential.impl.ClientSecretCriterionCredentialResolver"/>
+                <bean id="ClientSecretCredentialResolver" parent="shibboleth.sp.oidc.ClientSecretCredentialResolver"/>
             </list>
         </constructor-arg>
     </bean>
@@ -335,9 +330,7 @@
         class="net.shibboleth.oidc.security.credential.impl.ChainingJOSEObjectCredentialResolver">
         <constructor-arg>
             <list>
-                <!--  Used by the RP -->
-                <bean id="CriterionCredentialResolver"
-                    class="net.shibboleth.oidc.security.credential.impl.ClientSecretCriterionCredentialResolver"/>
+               <bean id="ClientSecretCredentialResolver" parent="shibboleth.sp.oidc.ClientSecretCredentialResolver"/>
             </list>
         </constructor-arg>
     </bean>    
@@ -357,18 +350,15 @@
         class="net.shibboleth.oidc.security.credential.impl.BasicJOSEObjectCredentialResolver" />
     
      <!-- A resolver for resolving trusted credentials to match against those resolved from the JWT -->
-     <!-- TODO the client_secret credential should come from the new credential resolver -->
     <bean id="defaultSignedJWTTrustedCredentialResolver"
         class="net.shibboleth.oidc.security.credential.impl.ChainingJOSEObjectCredentialResolver">
         <constructor-arg>
             <list>
-                <!--  Used by the RP -->
                 <bean id="OIDCProviderMetadataCredentialResolver"
                     class="net.shibboleth.oidc.security.credential.impl.ProviderMetadataCredentialResolver"
                     c:remoteJwkSetCache-ref="shibboleth.oidc.RemoteJwkSetCache" 
                     c:keyFetchInterval="%{sp.oidc.provider.keyfetch.interval:PT30M}"/>
-                <bean id="CriterionCredentialResolver"
-                    class="net.shibboleth.oidc.security.credential.impl.ClientSecretCriterionCredentialResolver"/>
+                <bean id="ClientSecretCredentialResolver" parent="shibboleth.sp.oidc.ClientSecretCredentialResolver"/>
             </list>
         </constructor-arg>
     </bean>
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/credentials/postconfig.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/credentials/postconfig.xml
index 67eab5f..56cc768 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/credentials/postconfig.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/credentials/postconfig.xml
@@ -21,6 +21,23 @@
         </property>
     </bean>
     
+    <bean id="shibboleth.sp.oidc.DefaultClientSecretCredentialResolver" class="net.shibboleth.sp.jose.config.impl.StaticClientSecretCredentialResolver"
+            c:_0="#{getObject('shibboleth.sp.oidc.DefaultClientSecretCredentials') ?: getObject('DefaultClientSecretCredentials')}">
+        <property name="protocols">
+            <util:constant static-field="net.shibboleth.oidc.saml.xmlobject.Constants.OIDC_PROTOCOL_URI" />
+        </property>
+    </bean>
+    
+    <bean id="DefaultClientSecretCredentials" class="org.springframework.beans.factory.config.ListFactoryBean" lazy-init="true">
+        <property name="sourceList">
+            <list>
+                <!-- TODO: This is a temporary definition, eventually these need to come from an OP and Agent (issuer) lookup resolver -->
+                <bean id="shibboleth.oidc.DefaultClientSecretCredential" parent="shibboleth.oidc.ClientSecretCredential"
+                         p:secret="%{sp.oidc.defaultClientSecret:#{null}}"/>
+            </list>
+        </property>
+    </bean>
+    
     <bean id="DefaultCredentials" class="org.springframework.beans.factory.config.ListFactoryBean" lazy-init="true">
         <property name="sourceList">
             <list>
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/AbstractOIDCTokenConsumerFlowTest.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/AbstractOIDCTokenConsumerFlowTest.java
index fd136a5..ae43a09 100644
--- a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/AbstractOIDCTokenConsumerFlowTest.java
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/AbstractOIDCTokenConsumerFlowTest.java
@@ -69,6 +69,7 @@ import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
 import com.nimbusds.openid.connect.sdk.token.OIDCTokens;
 
 import net.minidev.json.JSONObject;
+import net.shibboleth.oidc.security.credential.ClientSecretCredential;
 import net.shibboleth.oidc.security.credential.JWKCredential;
 import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.collection.Pair;
@@ -95,6 +96,9 @@ public abstract class AbstractOIDCTokenConsumerFlowTest extends AbstractSPFlowTe
     /** Dummy encryption key of the RP/SP. */
     @Autowired @Qualifier("dummy.sp.encryption.Credential") protected JWKCredential rpEncryptionCredential;
     
+    /** Dummy client_secret credential of the RP/SP. */
+    @Autowired @Qualifier("dummy.sp.DefaultClientSecretCredential") protected ClientSecretCredential clientSecretCredential;
+    
     /** The mocked HttpClient to use when responding to Token and UserInfo requests.*/
     private HttpClient httpClient;
     
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowTest.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowTest.java
index c45892a..f030ac5 100644
--- a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowTest.java
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowTest.java
@@ -18,6 +18,7 @@ import java.io.IOException;
 import java.time.Duration;
 import java.time.Instant;
 import java.util.Date;
+import java.util.List;
 import java.util.Map;
 
 import org.opensaml.profile.action.EventIds;
@@ -28,8 +29,17 @@ import org.springframework.webflow.executor.FlowExecutionResult;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import com.nimbusds.jose.EncryptionMethod;
+import com.nimbusds.jose.JWEAlgorithm;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.oauth2.sdk.ResponseMode;
+import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
+import com.nimbusds.oauth2.sdk.token.RefreshToken;
 import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+import com.nimbusds.openid.connect.sdk.token.OIDCTokens;
 
 import net.shibboleth.idp.spring.IdPPropertiesApplicationContextInitializer;
 import net.shibboleth.idp.test.PreferFileSystemApplicationContextInitializer;
@@ -321,6 +331,55 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
                 TestConstants.RESOURCE_URL);
     }
     
+    /**
+     * Test successful flow with a signed and encrypted id_token and a plain user info response. Encryption is direct
+     * encryption using the client_secret as a shared key, 'dir'. 
+     * 
+     * @throws IOException on error
+     */
+    @Test
+    public void testSuccess_SignedDirectEncryptedIDToken_WithDecryptionKID_PlainUserInfo() throws Exception {       
+    	final JWTClaimsSet claimsSet = TestHelper.createBasicUserInfoClaimsWithExtra(
+    			Map.of("sub","jdoe","iss", TestConstants.ISSUER, 
+    					"name", "John Doe", "aud", List.of(TestConstants.CLIENT_ID)), 
+    			Map.of("iat", Instant.now(), "exp", Instant.now().plusSeconds(3600)));
+        
+        final JWT encryptedIdToken = 
+                TestHelper.createJWT(claimsSet, JWSAlgorithm.RS256, JWEAlgorithm.DIR, 
+                		EncryptionMethod.A256GCM, opSigningCredential, 
+                		clientSecretCredential.toEncryptionCredential(JWEAlgorithm.DIR, EncryptionMethod.A256GCM), 
+                		"client_secret_credential");
+        
+        final OIDCTokenResponse tokenResponse = 
+        		new OIDCTokenResponse((new OIDCTokens(encryptedIdToken, 
+        				new BearerAccessToken("fake-access-token-value", 3600, null), 
+        				new RefreshToken("fake-refresh-token-value"))));
+        
+        mockOIDCEndpoints(tokenResponse, constructJSONUserInfoResponse());
+
+        final AuthenticationSuccessResponse response = 
+                TestHelper.buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY, 
+                        TestConstants.STATE_TOKEN);         
+        final DDF input = buildRemotedQueryStringResponse(response);
+        
+        // Add cookies
+        input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
+                TestConstants.STATE_TOKEN, 
+                TestConstants.APPLICATION_ID_REQUEST_OBJECT,
+                TestHelper.buildAuthenticationState(null, false, null), true));
+        
+        setApplicationRequest(TestConstants.APPLICATION_ID_REQUEST_OBJECT, input);
+
+        final FlowExecutionResult result = flowExecutor.launchExecution(TestConstants.FLOW_ID, null, externalContext);
+        assertFlowExecutionResult(result, TestConstants.FLOW_ID);
+        assertFlowExecutionOutcome(result.getOutcome());
+        final DDF output = assertOutputMessageSuccess(result);
+        assert output != null;
+        System.out.println("test output: " + output.toString());
+        validateOutputMessage(result, CollectionSupport.setOf("sub","mail","displayName","eduPersonScopedAffiliation"),
+                TestConstants.RESOURCE_URL);
+    }
+    
     /**
      * Test failure flow with a signed and encrypted id_token and a plain user info response. The kid of the key 
      * used to encrypt the IDToken is not one that matches to a local key defined.
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/TestSPOIDCEnvironmentApplicationContextInitializer.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/TestSPOIDCEnvironmentApplicationContextInitializer.java
index 0faa3dc..132526f 100644
--- a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/TestSPOIDCEnvironmentApplicationContextInitializer.java
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/TestSPOIDCEnvironmentApplicationContextInitializer.java
@@ -40,6 +40,8 @@ public class TestSPOIDCEnvironmentApplicationContextInitializer extends TestSPEn
         mock.setProperty("idp.webflows", "classpath*:/flows");
         mock.setProperty("idp.service.logging.resource", "/logback-flow-test.xml");
         mock.setProperty("sp.service.agents.resources", "test.sp.oidc.AgentResolverResources");
+        // Default authority identifier
+        mock.setProperty("sp.defaultAuthority", "https://op.example.org");
         // Use cookie based state management
         mock.setProperty("sp.stateToken.Manager","shibboleth.sp.CookieStateManager");
         // Use a mocked HTTP client
@@ -53,7 +55,7 @@ public class TestSPOIDCEnvironmentApplicationContextInitializer extends TestSPEn
         // Turn off the sealing state data for testing
         mock.setProperty("sp.stateToken.sealed", "false");
         // Create a basic default client secret
-        mock.setProperty("sp.oidc.defaultClientSecret", "secret");
+        mock.setProperty("sp.oidc.defaultClientSecret", "zIq2qS8w8k7qzShC0Z6j8l0mKmdEFdt02KDv2Qz4xXQ");
         // Turn on the replay cache for testing
         mock.setProperty("sp.stateToken.checkReplay", "true");
         mock.setProperty("idp.additionalProperties",
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/TestSPOIDCEnvironmentApplicationContextInitializerWithStorageService.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/TestSPOIDCEnvironmentApplicationContextInitializerWithStorageService.java
index 11f7250..64ca5ee 100644
--- a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/TestSPOIDCEnvironmentApplicationContextInitializerWithStorageService.java
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/TestSPOIDCEnvironmentApplicationContextInitializerWithStorageService.java
@@ -57,7 +57,7 @@ public class TestSPOIDCEnvironmentApplicationContextInitializerWithStorageServic
         // Turn off the sealing state data for testing
         mock.setProperty("sp.stateToken.sealed", "false");
         // Create a basic default client secret
-        mock.setProperty("sp.oidc.defaultClientSecret", "secret");
+        mock.setProperty("sp.oidc.defaultClientSecret", "zIq2qS8w8k7qzShC0Z6j8l0mKmdEFdt02KDv2Qz4xXQ");
         // Turn on the replay cache for testing
         mock.setProperty("sp.stateToken.checkReplay", "true");
         mock.setProperty("idp.additionalProperties",
diff --git a/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/oidc-test-agents.xml b/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/oidc-test-agents.xml
index 579df9f..2880e10 100644
--- a/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/oidc-test-agents.xml
+++ b/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/oidc-test-agents.xml
@@ -60,7 +60,7 @@
     <util:list id="test.ProfileConfigurations">
         <bean parent="OIDC.SSO" p:securityConfiguration-ref="testSecConfig" p:useRequestObject="false">
             <property name="clientCredential">
-                  <bean parent="shibboleth.oidc.ClientSecretCredential" p:secret="secret"/>
+                  <bean parent="shibboleth.oidc.ClientSecretCredential" p:secret="zIq2qS8w8k7qzShC0Z6j8l0mKmdEFdt02KDv2Qz4xXQ"/>
             </property>
         </bean>
     </util:list>
@@ -72,7 +72,7 @@
     <util:list id="test.WithPKCE">
         <bean parent="OIDC.SSO" p:securityConfiguration-ref="testSecConfig" p:forcePKCE="true">
             <property name="clientCredential">
-                  <bean parent="shibboleth.oidc.ClientSecretCredential" p:secret="secret"/>
+                  <bean parent="shibboleth.oidc.ClientSecretCredential" p:secret="zIq2qS8w8k7qzShC0Z6j8l0mKmdEFdt02KDv2Qz4xXQ"/>
             </property>
         </bean>
     </util:list>
@@ -80,7 +80,7 @@
     <util:list id="test.WithPostResponseMode">
         <bean parent="OIDC.SSO" p:securityConfiguration-ref="testSecConfig" p:responseMode="form_post">
             <property name="clientCredential">
-                  <bean parent="shibboleth.oidc.ClientSecretCredential" p:secret="secret"/>
+                  <bean parent="shibboleth.oidc.ClientSecretCredential" p:secret="zIq2qS8w8k7qzShC0Z6j8l0mKmdEFdt02KDv2Qz4xXQ"/>
             </property>
         </bean>
     </util:list>
@@ -88,7 +88,7 @@
     <util:list id="test.RequestObjectProfileConfigurations">
         <bean parent="OIDC.SSO" p:securityConfiguration-ref="testSecConfig" p:useRequestObject="true" p:signRequestObject="true" p:encryptRequestObject="false">
             <property name="clientCredential">
-                  <bean parent="shibboleth.oidc.ClientSecretCredential" p:secret="secret"/>
+                  <bean parent="shibboleth.oidc.ClientSecretCredential" p:secret="zIq2qS8w8k7qzShC0Z6j8l0mKmdEFdt02KDv2Qz4xXQ"/>
             </property>
         </bean>
     </util:list>
@@ -97,7 +97,7 @@
         <bean parent="OIDC.SSO" p:securityConfiguration-ref="testSecConfig" p:useRequestObject="true" p:signRequestObject="true" p:encryptRequestObject="false"
             p:tokenEndpointAuthMethod="private_key_jwt">
             <property name="clientCredential">
-                  <bean parent="shibboleth.oidc.ClientSecretCredential" p:secret="secret"/>
+                  <bean parent="shibboleth.oidc.ClientSecretCredential" p:secret="zIq2qS8w8k7qzShC0Z6j8l0mKmdEFdt02KDv2Qz4xXQ"/>
             </property>
         </bean>
     </util:list>
@@ -108,7 +108,7 @@
                 <bean id="basicRequestedClaims" class="net.shibboleth.sp.oidc.functions.RequestedClaimsExampleFunction"/>
             </property> 
             <property name="clientCredential">
-                  <bean parent="shibboleth.oidc.ClientSecretCredential" p:secret="secret"/>
+                  <bean parent="shibboleth.oidc.ClientSecretCredential" p:secret="zIq2qS8w8k7qzShC0Z6j8l0mKmdEFdt02KDv2Qz4xXQ"/>
             </property>       
         </bean>
     </util:list>
diff --git a/sp-oidc-conf-impl/src/test/resources/net/shibboleth/sp/oidc-test-beans.xml b/sp-oidc-conf-impl/src/test/resources/net/shibboleth/sp/oidc-test-beans.xml
index 084a430..4c5b857 100644
--- a/sp-oidc-conf-impl/src/test/resources/net/shibboleth/sp/oidc-test-beans.xml
+++ b/sp-oidc-conf-impl/src/test/resources/net/shibboleth/sp/oidc-test-beans.xml
@@ -27,13 +27,19 @@
     
     <bean id="shibboleth.oidc.JWKCredential" abstract="true"
         class="net.shibboleth.oidc.security.credential.BasicJWKCredentialFactoryBean" />
+        
+    <bean id="shibboleth.oidc.ClientSecretCredential" abstract="true"
+        class="net.shibboleth.oidc.security.credential.BasicClientSecretCredentialFactoryBean" />   
 
     <!-- These need to be the same as those resolvable by the security configuration used -->
     <bean id="dummy.op.signing.Credential" parent="shibboleth.oidc.JWKCredential"
         p:resource="%{idp.home}/credentials/op/op-signing-rsa.jwk" p:throwIfNull="false" />
         
-     <bean id="dummy.sp.encryption.Credential" parent="shibboleth.oidc.JWKCredential"
+    <bean id="dummy.sp.encryption.Credential" parent="shibboleth.oidc.JWKCredential"
         p:resource="%{idp.home}/credentials/sp/sp-encryption-rsa.jwk" p:throwIfNull="false" />
+        
+    <bean id="dummy.sp.DefaultClientSecretCredential" parent="shibboleth.oidc.ClientSecretCredential"
+        p:secret="%{sp.oidc.defaultClientSecret:#{null}}"/>
 
     <!-- Mockito mock for HttpClient -->
     <bean id="Mock.HttpClient"
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/jose/config/impl/ClientSecretCredentialResolver.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/jose/config/impl/ClientSecretCredentialResolver.java
new file mode 100644
index 0000000..116cb44
--- /dev/null
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/jose/config/impl/ClientSecretCredentialResolver.java
@@ -0,0 +1,168 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.sp.jose.config.impl;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.criterion.ProfileRequestContextCriterion;
+import org.opensaml.security.credential.Credential;
+import org.opensaml.security.credential.CredentialResolver;
+import org.opensaml.security.credential.criteria.impl.EvaluableEntityIDCredentialCriterion;
+import org.opensaml.security.credential.impl.AbstractCriteriaFilteringCredentialResolver;
+import org.slf4j.Logger;
+
+import jakarta.servlet.ServletRequest;
+import net.shibboleth.oidc.security.credential.JOSEObjectCredentialResolver;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.NonnullSupplier;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.ResolverException;
+import net.shibboleth.shared.service.ReloadableService;
+import net.shibboleth.shared.service.ServiceableComponent;
+import net.shibboleth.sp.Agent;
+import net.shibboleth.sp.AgentIDCriterion;
+import net.shibboleth.sp.Application;
+import net.shibboleth.sp.ApplicationIDCriterion;
+import net.shibboleth.sp.context.AgentRequestContext;
+
+/**
+ * A {@link JOSEObjectCredentialResolver} that extracts a client_secret credential from the {@link CredentialResolver} 
+ * service. 
+ * 
+ * TODO: The client secret MUST be appropriate for the relying party (OpenID Provider) and Agent in question. As a 
+ * result, an {@link EvaluableEntityIDCredentialCriterion} has to be built to filter out any credentials not registered 
+ * with a given OpenID Provider — this is essential so secrets are not sent to the wrong Provider.
+ */
+//TODO: Should be in common once finished
+public class ClientSecretCredentialResolver extends AbstractCriteriaFilteringCredentialResolver 
+																		implements JOSEObjectCredentialResolver {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(ClientSecretCredentialResolver.class);
+    
+    /** Credential resolver service. */
+    @Nonnull private final ReloadableService<CredentialResolver> credentialResolver;
+    
+    /** Access to servlet request. */
+    @Nonnull private final NonnullSupplier<ServletRequest> servletRequestSupplier;   
+    
+    
+    /**
+     * Constructor.
+     * 
+     * @param resolver credential resolver service 
+     */
+    public ClientSecretCredentialResolver(
+    		@Nonnull @ParameterName(name="resolver") final ReloadableService<CredentialResolver> resolver,
+    		@Nonnull @ParameterName(name="requestSupplier") final NonnullSupplier<ServletRequest> requestSupplier) {
+    	credentialResolver = Constraint.isNotNull(resolver, "CredentialResolver cannot be null");
+    	servletRequestSupplier = Constraint.isNotNull(requestSupplier, "ServletRequest supplier cannot be null");
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull protected Iterable<Credential> resolveFromSource(@Nullable final CriteriaSet criteriaSet) 
+            throws ResolverException {
+    	
+    	final CriteriaSet finalCriteria = buildCriteriaSet(criteriaSet);
+    	
+    	try (final ServiceableComponent<CredentialResolver> component = credentialResolver.getServiceableComponent()) {
+            return component.getComponent().resolve(finalCriteria);
+    	}
+
+    }
+    
+    /**
+     * Add or construct final {@link CriteriaSet} to supply to resolver. 
+     * 
+     * TODO: Mandate an {@link EvaluableEntityIDCredentialCriterion} is created, as credentials resolved must be 
+     * appropriate for the OpenID Provider. Or just pass through an EntityID type, so the resolver can use it as a key
+     * for lookup.
+     * 
+     * TODO: The AgentID is already present, so credentials should be resolved for the correct client.
+     * 
+     * @param criteria existing criteria to add to, if any
+     * 
+     * @return the constructed criteria set
+     * 
+     * @throws ResolverException if there is a failure to find the identifier of the OpenID Provider
+     */
+    @Nullable private CriteriaSet buildCriteriaSet(@Nullable final CriteriaSet criteria) throws ResolverException {
+        
+        final ProfileRequestContext prc = getProfileRequestContext();
+        if (prc == null) {
+        	throw new ResolverException(
+        		    "Client secret credential resolution requires a ProfileRequestContext");
+        }
+        
+        final CriteriaSet finalCriteria = criteria != null ? criteria : new CriteriaSet();
+        finalCriteria.add(new ProfileRequestContextCriterion(prc));
+        
+        // Add Agent/Application info.
+        final AgentRequestContext arc = prc.getSubcontext(AgentRequestContext.class);
+        //TODO we should probably mandate this exists, so we can ensure the agentId criterion is used
+        if (arc != null) {
+            final Agent agent = arc.getAgent();
+            if (agent != null) {
+                final String id = agent.getId();
+                if (id != null) {
+                    finalCriteria.add(new AgentIDCriterion(id));
+                }
+            }
+            final Application app = arc.getApplication();
+            if (app != null) {
+                finalCriteria.add(new ApplicationIDCriterion(app.getApplicationId()));
+            }
+        }
+        
+        final RelyingPartyContext rpc = prc.getSubcontext(RelyingPartyContext.class);
+        if (rpc != null) {
+            final String rpid = rpc.getRelyingPartyId();
+            if (rpid != null) {
+            	// Create an evaluable criterion, as we require this to be evaluated
+            	// TODO we need to evaluate credentials on the RPID and Issuer/Agent eventually
+                //finalCriteria.add(new EvaluableEntityIDCredentialCriterion(rpid));
+            } else {
+            	throw new ResolverException("Client secret credential resolver requires relying party ID "
+            			+ "to scope credentials for");
+            }
+        } else {
+        	throw new ResolverException("Client secret credential resolver requires a relying party context");
+        }
+        
+        //TODO: do we need to specify a 'secret' type here. e.g. via another criterion
+
+        return finalCriteria;
+    }
+    
+    /**
+     * Get the current {@link ProfileRequestContext}.
+     * 
+     * @return current profile request context or null
+     */
+    @Nullable private ProfileRequestContext getProfileRequestContext() {
+        if (servletRequestSupplier.get().getAttribute(ProfileRequestContext.BINDING_KEY)
+                instanceof final ProfileRequestContext prc) {
+            return prc;
+        }
+        return null;
+    }
+
+}
\ No newline at end of file
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/jose/config/impl/StaticClientSecretCredentialResolver.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/jose/config/impl/StaticClientSecretCredentialResolver.java
new file mode 100644
index 0000000..a31ade8
--- /dev/null
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/jose/config/impl/StaticClientSecretCredentialResolver.java
@@ -0,0 +1,179 @@
+
+package net.shibboleth.sp.jose.config.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.security.credential.Credential;
+import org.opensaml.security.credential.CredentialResolver;
+import org.opensaml.security.credential.UsageType;
+import org.opensaml.security.criteria.UsageCriterion;
+import org.slf4j.Logger;
+
+import com.nimbusds.jose.EncryptionMethod;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWEAlgorithm;
+
+import net.shibboleth.oidc.security.credential.ClientSecretCredential;
+import net.shibboleth.oidc.security.credential.impl.DataEncryptionAlgorithmCriterion;
+import net.shibboleth.oidc.security.credential.impl.KeyManagmentAlgorithmCriterion;
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Unmodifiable;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.ResolverException;
+import net.shibboleth.sp.credential.AbstractOrderedCredentialResolver;
+
+/**
+ * A {@link CredentialResolver} that holds {@link ClientSecretCredential}s when instantiated, and derives the 
+ * appropriate {@link Credential} at runtime based on the supplied criteria. 
+ *  
+ * <p>A different key credential is derived for different usage types. MAC ('signing') keys are generated directly off 
+ * the UTF-8 octets of the client_secret. Encryption credentials are derived using the key management and content 
+ * encryption algorithms supplied in the criteria set. Requests for encryption credentials therefore require both 
+ * algorithm criteria to be present.</p>
+ */
+public class StaticClientSecretCredentialResolver extends AbstractOrderedCredentialResolver {
+	
+	 /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(StaticClientSecretCredentialResolver.class);
+
+    /** List of credentials held by this resolver. */
+    @Nonnull private final List<ClientSecretCredential> creds;
+    
+    /**
+     * Constructor.
+     *
+     * @param credentials static credentials
+     */
+    public StaticClientSecretCredentialResolver(
+    		@Nonnull @ParameterName(name="credentials") final List<ClientSecretCredential> credentials) {
+        Constraint.isNotNull(credentials, "Input credentials list cannot be null");
+        
+        creds = CollectionSupport.copyToList(credentials);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull @NotLive @Unmodifiable public Iterable<Credential> doResolve(@Nullable final CriteriaSet criteria) 
+    		throws ResolverException {
+    	
+    	if (!creds.isEmpty()) {
+    		
+    		verifyCriteria(criteria);
+    		
+    		final List<Credential> credentials = new ArrayList<>(creds.size());
+    		for (final ClientSecretCredential cred : creds) {
+    			try {
+    				final Credential derivedCredential = deriveClientSecretCredential(cred, criteria);
+		    		credentials.add(derivedCredential);
+    			} catch (final ResolverException e) {
+    				log.trace("Unable to create suitable credential from client_secret: ",e);
+    				// Ignore and try next
+    			}
+    		}
+    		return credentials;
+    	}
+    	return CollectionSupport.emptyList();
+    }
+    
+    /**
+     * Verify the criteria set has the required criterion to correctly process the client secret credential.
+     * 
+     * @param criteriaSet the criteria set to check
+     * 
+     * @throws ResolverException if the required criterion are not present
+     */
+    private void verifyCriteria(@Nullable final CriteriaSet criteriaSet) throws ResolverException {
+    	
+    	if (criteriaSet == null) {
+    		throw new ResolverException("No criteria set found, can not process client secrets");
+    	}
+    	
+		final UsageCriterion usageTypeCriterion = criteriaSet.get(UsageCriterion.class);
+        if (usageTypeCriterion == null) {
+            throw new ResolverException("No usage type criterion supplied, unable to derive client_secret credential");
+        }
+        
+        if (usageTypeCriterion.getUsage() == UsageType.ENCRYPTION) {
+        
+	        final KeyManagmentAlgorithmCriterion alg = criteriaSet.get(KeyManagmentAlgorithmCriterion.class);
+	        if (alg == null) {
+	            throw new ResolverException(
+	                    "Credential criteria set did not contain an instance of KeyManagmentAlgorithmCriterion");
+	        }
+	        // Technically the encryption method is only relevant to key derivation for the Direct Encryption mode
+	        final DataEncryptionAlgorithmCriterion enc = criteriaSet.get(DataEncryptionAlgorithmCriterion.class);
+	        if (enc == null) {
+	            throw new ResolverException(
+	                    "Credential criteria set did not contain an instance of DataEncryptionAlgorithmCriterion");
+	        }
+        }
+    }
+    
+    /**
+     * Use the usage type and algorithm information in the criteria to build a suitable signing or encryption 
+     * credential.
+     * 
+     * <p>Only supports symmetric key encryption algorithms. Request for asymmetric key encryption algorithms are 
+     * ignored.</p>
+     * 
+     * @param secretCred the raw client_secret credential
+     * @param criteriaSet the criteria set used to find algorithm details for encryption keys
+     * 
+     * @return a suitable credential
+     * 
+     * @throws ResolverException if there is an error deriving the key
+     */
+    @Nonnull protected Credential deriveClientSecretCredential(@Nonnull final ClientSecretCredential secretCred, 
+            @Nonnull final CriteriaSet criteriaSet) throws ResolverException {
+        
+    	// Already verified that the criterion exist
+        final UsageCriterion usageTypeCriterion = criteriaSet.get(UsageCriterion.class);
+        final UsageType usageType = usageTypeCriterion.getUsage();
+        
+        if (usageType == UsageType.SIGNING) {
+            // Create a signing credential
+            final Credential signingCred = secretCred.toSigningCredential();
+            log.debug("Derived signing credential '{}'", signingCred.getKeyNames());            
+            return signingCred;
+            
+        } else if (usageType == UsageType.ENCRYPTION) {
+            // Create an encryption credential suitable for the algorithms specified
+            final KeyManagmentAlgorithmCriterion alg = criteriaSet.get(KeyManagmentAlgorithmCriterion.class);
+
+            // Technically the encryption method is only relevant to key derivation for the Direct Encryption mode
+            final DataEncryptionAlgorithmCriterion enc = criteriaSet.get(DataEncryptionAlgorithmCriterion.class);
+            
+            // Can only derive symmetric key credentials, ignore if not
+            if (JWEAlgorithm.Family.SYMMETRIC.contains(JWEAlgorithm.parse(alg.getAlgorithm()))) {                     
+                try {
+                    final JWEAlgorithm jweAlgorithm = JWEAlgorithm.parse(alg.getAlgorithm());
+                    final EncryptionMethod encryptionMethod = EncryptionMethod.parse(enc.getEncAlgorithm());
+                    final Credential derivedCred = secretCred.toEncryptionCredential(
+                            jweAlgorithm, encryptionMethod);
+                    
+                    log.debug("Derived encryption credential '{}' from 'alg={}' and 'enc={}'", derivedCred.getKeyNames()
+                            ,alg.getAlgorithm(), enc.getEncAlgorithm());
+                    return derivedCred;
+                    
+                } catch (final JOSEException e) {
+                    log.trace("Unable to derive symmetric encryption key from client_secret using 'alg={}' and 'enc={}'",
+                            alg.getAlgorithm(), enc.getEncAlgorithm());      
+                    throw new ResolverException("Unable to create encryption key from client_secret", e);
+                }                
+            } else {    
+                throw new ResolverException("Asymmetric key requested, client_secret not appropriate");
+            }
+        } else { 
+            throw new ResolverException("Unable to create key from client_secret, incompatible usage type");
+        }
+        
+    }
+}
\ No newline at end of file
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeOAuth2ClientAuthenticationMethodHandler.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeOAuth2ClientAuthenticationMethodHandler.java
index 82c318f..ade55f2 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeOAuth2ClientAuthenticationMethodHandler.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeOAuth2ClientAuthenticationMethodHandler.java
@@ -14,6 +14,7 @@
 
 package net.shibboleth.sp.oidc.profile.impl;
 
+import java.nio.charset.StandardCharsets;
 import java.time.Duration;
 import java.time.Instant;
 import java.util.Date;
@@ -29,6 +30,10 @@ 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.opensaml.security.credential.CredentialResolver;
+import org.opensaml.security.credential.UsageType;
+import org.opensaml.security.criteria.UsageCriterion;
 import org.slf4j.Logger;
 
 import com.nimbusds.jose.Algorithm;
@@ -51,17 +56,21 @@ 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.credential.ClientSecretCredential;
+import net.shibboleth.oidc.security.credential.JOSEObjectCredentialResolver;
 import net.shibboleth.oidc.security.impl.JWSTokenSigner;
 import net.shibboleth.oidc.security.jose.SignatureException;
 import net.shibboleth.oidc.security.jose.SignatureSigningParameters;
 import net.shibboleth.oidc.security.jose.context.SecurityParametersContext;
 import net.shibboleth.profile.context.RelyingPartyContext;
 import net.shibboleth.profile.context.navigate.IssuerLookupFunction;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
 import net.shibboleth.shared.primitive.StringSupport;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.ResolverException;
 import net.shibboleth.sp.oidc.profile.OIDCSupport;
 
 /**
@@ -130,9 +139,6 @@ public class InitializeOAuth2ClientAuthenticationMethodHandler extends AbstractM
     /** The stashed provider metadata.*/
     @NonnullBeforeExec 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;
     
@@ -141,6 +147,9 @@ public class InitializeOAuth2ClientAuthenticationMethodHandler extends AbstractM
     
     /** The stashed peer entity context.*/
     @Nullable private OIDCPeerEntityContext peerEntityContext;
+    
+    /** The resolver used to resolve client secrets for client_secret based authentication.*/
+    @NonnullAfterInit private JOSEObjectCredentialResolver clientSecretResolver;
         
     /** Constructor.*/
     public InitializeOAuth2ClientAuthenticationMethodHandler() {
@@ -245,6 +254,25 @@ public class InitializeOAuth2ClientAuthenticationMethodHandler extends AbstractM
         issuerLookupStrategy = Constraint.isNotNull(strategy, "IssuerLookupStrategy lookup strategy cannot be null");
     }
     
+    /**
+     * Set the {@link CredentialResolver} used to determine the client_secret to use for client_secret based
+     * authentication methods. 
+     * 
+     * @param resolver the resolver used to find the client_secret
+     */
+    public void setClientSecretResolver(@Nonnull final JOSEObjectCredentialResolver resolver) {
+    	checkSetterPreconditions();
+		clientSecretResolver = Constraint.isNotNull(resolver, "Client secret resolver can not be null");
+	}
+    
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {    	
+    	super.doInitialize();
+    	if (clientSecretResolver == null) {
+    		throw new ComponentInitializationException("Client secret resolver can not be null");
+    	}
+    }
+    
     
     /** {@inheritDoc} */
     @Override
@@ -269,7 +297,7 @@ public class InitializeOAuth2ClientAuthenticationMethodHandler extends AbstractM
         }
         if (profileConfiguration == null) {
             log.error("{} Profile configuration not found", getLogPrefix());   
-            throw new MessageHandlerException("No OAuth2 client authentication context found or created");             
+            throw new MessageHandlerException("No profile configuration found");             
         }
         
         // Can be null
@@ -300,8 +328,6 @@ public class InitializeOAuth2ClientAuthenticationMethodHandler extends AbstractM
             throw new MessageHandlerException("No client_id found from issuer lookup strategy");
         }
         
-        clientCredential = profileConfiguration.getClientCredential(PRC_LOOKUP.apply(messageContext));
-        
         return true;
          
     }
@@ -315,12 +341,7 @@ public class InitializeOAuth2ClientAuthenticationMethodHandler extends AbstractM
         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");
-            }            
-            assert clientCredential != null;
-            final Secret secret = new Secret(clientCredential.getSecret());            
+            final Secret secret = resolveClientSecret();            
             // 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);
@@ -336,15 +357,12 @@ public class InitializeOAuth2ClientAuthenticationMethodHandler extends AbstractM
             verifySuitableClientSecretJWTSecurityContext();
             clientAuthentication = new ClientSecretJWT(buildClientAuthenticationJwt(messageContext));
         } else if (method.equals(ClientAuthenticationMethod.PRIVATE_KEY_JWT)) {
-            verifySuitablePrivateKetJWTSecurityContext();
+        	verifySuitablePrivateKeyJWTSecurityContext();
             clientAuthentication = new PrivateKeyJWT(buildClientAuthenticationJwt(messageContext));
         } else {
             log.warn("{}: Client authentication method '{}' not supported for client '{}'", getLogPrefix(), 
                     method, clientId);
-        }
-        
-        if (clientAuthentication == null) {
-            throw new MessageHandlerException("Client authentication could not be constructed");
+            throw new MessageHandlerException("Unsupported client authentication method: " + method);            
         }
 
         oauth2ClientAuthenticationContext.setClientAuthentication(clientAuthentication);   
@@ -354,6 +372,48 @@ public class InitializeOAuth2ClientAuthenticationMethodHandler extends AbstractM
         
     }
     
+    /**
+     * Resolve a client_secret credential using the provided resolver.
+     *
+     * <p>The secret is defined to be a SIGNING type in the absence of another suitable type. This should give back
+     * the raw secret, and not derive it into a different sequence.</p>
+     * 
+     * <p>The credential is checked to ensure it is appropriate for the target OpenID Provider, and it is a 
+     * secret and not a public key. These checks may also happen in the resolver, so this serves to guarantee that 
+     * behaviour.</p>
+     * 
+     * TODO: When we have a way to signal a 'PASSWORD' usage type, the resolvers will know not to return assymmetric keys.
+     * 
+     * @return the secret if found
+     * 
+     * @throws MessageHandlerException if a suitable secret can not be resolved
+     */
+    @Nonnull private Secret resolveClientSecret() throws MessageHandlerException {
+    	
+    	try {
+    		final CriteriaSet criteria = new CriteriaSet();
+    		// Use the SIGNING type, to ensure direct key usage. 
+    		// TODO: if another type is specified, we can just say RAW etc. This is a hack. It is really a 'PASSWORD'.
+    		criteria.add(new UsageCriterion(UsageType.SIGNING));
+    		// Return the first that fits
+			for (final Credential secret : clientSecretResolver.resolve(criteria)) {
+				// Guard we have a secret for the correct provider (even if filtered by the resolver), and is a secret
+				// TODO: Maybe we guard on entityId, but for now that is not possible for the client_secret
+//				if (providerMetadata.getIssuer().getValue().equals(secret.getEntityId()) &&
+//						secret.getSecretKey() != null){
+				if (secret.getSecretKey() != null){
+					final byte[] encodedKey = secret.getSecretKey().getEncoded();
+					if (encodedKey != null) {
+						return new Secret(new String(encodedKey, StandardCharsets.UTF_8));
+					}
+				}
+			}
+			throw new MessageHandlerException("Unable to find a suitable client_secret");
+		} catch (final ResolverException e) {
+			throw new MessageHandlerException("Unable to resolve client secret", e);
+		}
+    }
+    
     /**
      * Check the populated security context is using the correct algorithm family for client_secret_jwt client 
      * authentication.
@@ -389,7 +449,7 @@ public class InitializeOAuth2ClientAuthenticationMethodHandler extends AbstractM
      * 
      * @throws MessageHandlerException if the wrong algorithm family is specified in the security context
      */
-    private void verifySuitablePrivateKetJWTSecurityContext() throws MessageHandlerException {
+    private void verifySuitablePrivateKeyJWTSecurityContext() throws MessageHandlerException {
         
         final SecurityParametersContext bearerSecurityParams = jwtBearerClientAuthSecurityParameters;        
         if (bearerSecurityParams == null || bearerSecurityParams.getSignatureSigningParameters() == null) {
@@ -403,6 +463,9 @@ public class InitializeOAuth2ClientAuthenticationMethodHandler extends AbstractM
         if (signatureSigningParameters.getSigningCredential() == null) {
             throw new MessageHandlerException("Missing credential needed to sign private_key_jwt");
         }
+        if (signatureSigningParameters.getSignatureAlgorithm() == null) {
+            throw new MessageHandlerException("Missing signing algorithm needed to sign private_key_jwt");
+        }
         final Algorithm jwsAlgorithm = new JWSAlgorithm(signatureSigningParameters.getSignatureAlgorithm());
         if (!JWSAlgorithm.Family.SIGNATURE.contains(jwsAlgorithm)) {
             throw new MessageHandlerException("Trying to construct private_key_jwt using the wrong algorithm: "
@@ -427,6 +490,10 @@ public class InitializeOAuth2ClientAuthenticationMethodHandler extends AbstractM
         
         final String audience;
         if (profileConfiguration.isUseTargetedEndpointAsJWTAudience(PRC_LOOKUP.apply(messageContext))) {
+        	if (providerMetadata.getTokenEndpointURI() == null) {
+        		throw new MessageHandlerException("Token endpoint URI expected as the audience, but no token endpoint"
+        				+ " exists in provider metadata");
+        	}
             audience = providerMetadata.getTokenEndpointURI().toString();
         } else {
             final var localPeerEntityCtx = peerEntityContext;
@@ -439,14 +506,14 @@ public class InitializeOAuth2ClientAuthenticationMethodHandler extends AbstractM
         if (StringSupport.trimOrNull(audience) == null) {
             throw new MessageHandlerException("JWT audience can not be null");
         }
-        
+        final Instant now = Instant.now();
         final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
                 .subject(clientId)
                 .issuer(clientId)
                 .audience(audience)
                 .jwtID(OIDCSupport.generateRandom(32))
-                .issueTime(Date.from(Instant.now()))
-                .expirationTime(Date.from(Instant.now().plus(jwtBearerExpiryOffset)))  
+                .issueTime(Date.from(now))
+                .expirationTime(Date.from(now.plus(jwtBearerExpiryOffset)))  
                 .build();
         assert claimsSet != null;
         return claimsSet;
@@ -488,7 +555,7 @@ public class InitializeOAuth2ClientAuthenticationMethodHandler extends AbstractM
             final JWSTokenSigner signer = new JWSTokenSigner(signingParams);
             final SignedJWT signed = signer.sign(claims, jwtType);    
             if (log.isDebugEnabled() && !log.isTraceEnabled()) {
-                log.debug("{} Signed JWT Bearer Token for client authentication'", getLogPrefix());
+                log.debug("{} Signed JWT Bearer Token for client authentication", getLogPrefix());
             } else if (log.isTraceEnabled()) {
                 log.trace("{} Signed JWT Bearer Token for client authentication: {}", getLogPrefix()
                         ,signed.serialize());
diff --git a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/BaseOIDCAuthenticationTest.java b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/BaseOIDCAuthenticationTest.java
index 80af108..0b308a3 100644
--- a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/BaseOIDCAuthenticationTest.java
+++ b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/BaseOIDCAuthenticationTest.java
@@ -40,6 +40,8 @@ import net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext;
 import net.shibboleth.oidc.profile.config.impl.DefaultOIDCAuthorizationConfiguration;
 import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
 import net.shibboleth.oidc.profile.messaging.context.OIDCPeerEntityContext;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.relyingparty.BasicRelyingPartyConfiguration;
 import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
 import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.sp.oidc.testing.TestConstants;
@@ -112,6 +114,16 @@ public abstract class BaseOIDCAuthenticationTest extends BaseApplicationActionTe
         peerEntityCtxInbound.addSubcontext(providerCtxInbound);   
         inMsgCtx.addSubcontext(peerEntityCtxInbound);
         prc.setInboundMessageContext(inMsgCtx);
+        
+        final RelyingPartyContext partyContext =  prc.ensureSubcontext(RelyingPartyContext.class);
+        partyConfig = new DefaultOIDCAuthorizationConfiguration();  
+        partyContext.setProfileConfig(partyConfig);
+        partyConfig.setTokenEndpointAuthMethod("client_secret_basic");
+        partyConfig.setIssuer(TestConstants.CLIENT_ID);
+        final BasicRelyingPartyConfiguration rPartyConfig = new BasicRelyingPartyConfiguration();
+        rPartyConfig.setIssuer(TestConstants.CLIENT_ID);
+        partyContext.setConfiguration(rPartyConfig);
+       
 
 
     }
diff --git a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/InitializeOAuth2ClientAuthenticationMethodHandlerTest.java b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/InitializeOAuth2ClientAuthenticationMethodHandlerTest.java
new file mode 100644
index 0000000..4c7ca67
--- /dev/null
+++ b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/InitializeOAuth2ClientAuthenticationMethodHandlerTest.java
@@ -0,0 +1,444 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.sp.oidc.profile.impl;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.opensaml.security.credential.BasicCredential;
+import org.opensaml.security.credential.Credential;
+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.credential.JOSEObjectCredentialResolver;
+import net.shibboleth.oidc.security.jose.SignatureSigningParameters;
+import net.shibboleth.oidc.security.jose.context.SecurityParametersContext;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.ResolverException;
+import net.shibboleth.sp.oidc.testing.TestConstants;
+
+/** Tests for {@link InitializeOAuth2ClientAuthenticationMethodHandler}.*/
+public class InitializeOAuth2ClientAuthenticationMethodHandlerTest extends BaseOIDCAuthenticationTest {
+	
+	/** Example client_secret for client secret type client authentication methods.*/
+	private static final String CLIENT_SECRET = "zIq2qS8w8k7qzShC0Z6j8l0mKmdEFdt02KDv2Qz4xXQ";
+    
+	/** The handler.*/
+    private InitializeOAuth2ClientAuthenticationMethodHandler handler;
+    
+    @Override
+    @BeforeMethod
+    public void beforeMethod() throws ComponentInitializationException {  
+        super.beforeMethod();        
+        handler = new InitializeOAuth2ClientAuthenticationMethodHandler();     
+        handler.setClientSecretResolver(new JOSEObjectCredentialResolver() {
+			
+        	@Override
+			@Nullable public Credential resolveSingle(@Nullable final CriteriaSet criteria) throws ResolverException {                
+        		final DefaultClientSecretCredential secret = 
+        				new DefaultClientSecretCredential(CLIENT_SECRET, "client_secret_credential");
+        		return secret.toSigningCredential();
+            }
+            
+            @Override
+			@Nonnull public Iterable<Credential> resolve(@Nullable final CriteriaSet criteria) throws ResolverException {
+                final Credential cred = resolveSingle(criteria);
+                return cred != null ? CollectionSupport.singletonList(cred) : CollectionSupport.emptyList();
+            }
+		});
+    }
+    
+    
+    @Test
+    public void testInitialiseClientSecretBasic_Success() throws Exception {
+        partyConfig.setTokenEndpointAuthMethod("client_secret_basic");
+        handler.initialize();
+        final var outboundMsgCtx = prc.getOutboundMessageContext();
+        assert outboundMsgCtx != null;
+        handler.invoke(outboundMsgCtx);
+        
+        final var peerEntityCtx = outboundMsgCtx.getSubcontext(OIDCPeerEntityContext.class);
+        assert peerEntityCtx != null;
+        final var context = peerEntityCtx.getSubcontext(OAuth2ClientAuthenticationContext.class);
+        assert context != null;
+        assertNotNull(context);
+        assertNotNull(context.getClientAuthentication());
+        assertTrue(context.getClientAuthentication() instanceof ClientSecretBasic);
+        final var basicClientAuth = (ClientSecretBasic) context.getClientAuthentication();
+        assert basicClientAuth != null;
+        assertEquals(basicClientAuth.getClientSecret().getValue(),CLIENT_SECRET);
+    }
+    
+    @Test(expectedExceptions = MessageHandlerException.class)
+    public void testInitialiseClientSecretBasic_NoCredential() throws Exception {
+    	handler.setClientSecretResolver(new JOSEObjectCredentialResolver() {
+			
+        	@Override
+			@Nullable public Credential resolveSingle(@Nullable final CriteriaSet criteria) throws ResolverException {                
+        		return null;
+            }
+            
+            @Override
+			@Nonnull public Iterable<Credential> resolve(@Nullable final CriteriaSet criteria) throws ResolverException {
+                return CollectionSupport.emptyList();
+            }
+		});
+
+        partyConfig.setTokenEndpointAuthMethod("client_secret_basic");
+        partyConfig.setClientCredential(null);
+        
+        handler.initialize();
+        final var outboundMsgCtx = getOutboundMessageContextFailIfNull(prc);
+        handler.invoke(outboundMsgCtx);        
+        
+    }
+    
+    @Test(expectedExceptions = MessageHandlerException.class)
+    public void testInitialiseUnsportedClientAuthenticationMethod() throws Exception {
+        partyConfig.setTokenEndpointAuthMethod("unsupported");
+        partyConfig.setClientCredential(null);
+        
+        handler.initialize();
+        final var outboundMsgCtx = getOutboundMessageContextFailIfNull(prc);
+        handler.invoke(outboundMsgCtx);        
+        
+    }
+    
+    @Test
+    public void testInitialiseClientSecretPost_Success() throws Exception {
+        partyConfig.setTokenEndpointAuthMethod("client_secret_post");
+        handler.initialize();
+        final var outboundMsgCtx = getOutboundMessageContextFailIfNull(prc);
+        handler.invoke(outboundMsgCtx);
+        
+        final var peerEntityCtx = outboundMsgCtx.getSubcontext(OIDCPeerEntityContext.class);
+        assert peerEntityCtx != null;
+        final var context = peerEntityCtx.getSubcontext(OAuth2ClientAuthenticationContext.class);
+        assert context != null;
+        assertNotNull(context);
+        assertNotNull(context.getClientAuthentication());
+        assertTrue(context.getClientAuthentication() instanceof ClientSecretPost);
+        final var basicClientAuth = (ClientSecretPost) context.getClientAuthentication();
+        assert basicClientAuth != null;
+        assertEquals(basicClientAuth.getClientSecret().getValue(),CLIENT_SECRET);
+    }
+    
+    @Test
+    public void testInitialiseClientSecretJWT_Success() throws Exception {
+        partyConfig.setTokenEndpointAuthMethod("client_secret_jwt");
+        partyConfig.setUseTargetedEndpointAsJWTAudience(true);
+        
+        final SecurityParametersContext secContext = new SecurityParametersContext();
+        final SignatureSigningParameters secParams = new SignatureSigningParameters();
+        secParams.setSignatureAlgorithm("HS256");
+        secContext.setSignatureSigningParameters(secParams);
+        secParams.setSigningCredential(new DefaultClientSecretCredential(CLIENT_SECRET).toSigningCredential());
+        
+        final var outboundMsgCtx = getOutboundMessageContextFailIfNull(prc);
+        
+        final var peerEntityCtx = outboundMsgCtx.getSubcontext(OIDCPeerEntityContext.class);        
+        assert peerEntityCtx != null;
+        peerEntityCtx.setIdentifier("https://op.example.com");
+        
+        outboundMsgCtx.addSubcontext(secContext);
+        
+        handler.initialize();
+        handler.invoke(outboundMsgCtx);
+        
+        final var context = peerEntityCtx.getSubcontext(OAuth2ClientAuthenticationContext.class);
+        assert context != null;
+        assertNotNull(context);
+        assertNotNull(context.getClientAuthentication());
+        assertTrue(context.getClientAuthentication() instanceof ClientSecretJWT);
+        final var clientSecretJwt = (ClientSecretJWT) context.getClientAuthentication();
+        assert clientSecretJwt != null;
+        assertNotNull(clientSecretJwt);
+        assertNotNull(clientSecretJwt.getJWTAuthenticationClaimsSet());
+        assertNotNull(clientSecretJwt.getClientAssertion());
+        assertEquals(clientSecretJwt.getJWTAuthenticationClaimsSet().getClientID().toString(), TestConstants.CLIENT_ID);
+        assertEquals(clientSecretJwt.getJWTAuthenticationClaimsSet().getSubject().toString(), TestConstants.CLIENT_ID);
+        assertEquals(clientSecretJwt.getJWTAuthenticationClaimsSet().getIssuer().toString(), TestConstants.CLIENT_ID);        
+        assertEquals(clientSecretJwt.getJWTAuthenticationClaimsSet().getAudience().size(),1);
+        assertEquals(clientSecretJwt.getJWTAuthenticationClaimsSet().getAudience().get(0).toString(),
+                "https://oauth2.op.example.org/token");
+        assertNotNull(clientSecretJwt.getJWTAuthenticationClaimsSet().getJWTID());
+        assertNotNull(clientSecretJwt.getJWTAuthenticationClaimsSet().getExpirationTime());
+    }
+    
+    @Test
+    public void testInitialiseClientSecretJWT_TokenEndpointURL_Success() throws Exception {
+        partyConfig.setTokenEndpointAuthMethod("client_secret_jwt");
+        partyConfig.setUseTargetedEndpointAsJWTAudience(true);
+        
+        final SecurityParametersContext secContext = new SecurityParametersContext();
+        final SignatureSigningParameters secParams = new SignatureSigningParameters();
+        secParams.setSignatureAlgorithm("HS256");
+        secContext.setSignatureSigningParameters(secParams);
+        secParams.setSigningCredential(new DefaultClientSecretCredential(CLIENT_SECRET).toSigningCredential());
+        
+        final var outboundMsgCtx = getOutboundMessageContextFailIfNull(prc);
+        
+        final var peerEntityCtx = outboundMsgCtx.getSubcontext(OIDCPeerEntityContext.class);        
+        assert peerEntityCtx != null;
+        peerEntityCtx.setIdentifier("https://op.example.com");
+        partyConfig.setUseTargetedEndpointAsJWTAudience(true);
+        
+        outboundMsgCtx.addSubcontext(secContext);
+        
+        handler.initialize();
+        handler.invoke(outboundMsgCtx);
+        
+        final var context = peerEntityCtx.getSubcontext(OAuth2ClientAuthenticationContext.class);
+        assert context != null;
+        assertNotNull(context);
+        assertNotNull(context.getClientAuthentication());
+        assertTrue(context.getClientAuthentication() instanceof ClientSecretJWT);
+        final var clientSecretJwt = (ClientSecretJWT) context.getClientAuthentication();
+        assert clientSecretJwt != null;
+        assertNotNull(clientSecretJwt);
+        assertNotNull(clientSecretJwt.getJWTAuthenticationClaimsSet());
+        assertNotNull(clientSecretJwt.getClientAssertion());
+        assertEquals(clientSecretJwt.getJWTAuthenticationClaimsSet().getClientID().toString(), TestConstants.CLIENT_ID);
+        assertEquals(clientSecretJwt.getJWTAuthenticationClaimsSet().getSubject().toString(), TestConstants.CLIENT_ID);
+        assertEquals(clientSecretJwt.getJWTAuthenticationClaimsSet().getIssuer().toString(), TestConstants.CLIENT_ID);        
+        assertEquals(clientSecretJwt.getJWTAuthenticationClaimsSet().getAudience().size(),1);
+        assertEquals(clientSecretJwt.getJWTAuthenticationClaimsSet().getAudience().get(0).toString(),
+                "https://oauth2.op.example.org/token");
+        assertNotNull(clientSecretJwt.getJWTAuthenticationClaimsSet().getJWTID());
+        assertNotNull(clientSecretJwt.getJWTAuthenticationClaimsSet().getExpirationTime());
+    }
+    
+    @Test(expectedExceptions = MessageHandlerException.class)
+    public void testInitialiseClientSecretJWT_NoAudience_Fail() throws Exception {
+        partyConfig.setTokenEndpointAuthMethod("client_secret_jwt");
+        partyConfig.setUseTargetedEndpointAsJWTAudience(false);
+        
+        final SecurityParametersContext secContext = new SecurityParametersContext();
+        final SignatureSigningParameters secParams = new SignatureSigningParameters();
+        secParams.setSignatureAlgorithm("HS256");
+        secContext.setSignatureSigningParameters(secParams);
+        secParams.setSigningCredential(new DefaultClientSecretCredential(CLIENT_SECRET).toSigningCredential());
+        
+        final var outboundMsgCtx = getOutboundMessageContextFailIfNull(prc);
+        
+        final var peerEntityCtx = outboundMsgCtx.getSubcontext(OIDCPeerEntityContext.class);        
+        assert peerEntityCtx != null;
+        // Should not be empty
+        peerEntityCtx.setIdentifier("");
+        
+        outboundMsgCtx.addSubcontext(secContext);
+        
+        handler.initialize();
+        handler.invoke(outboundMsgCtx);
+
+    }
+    
+    
+    @Test(expectedExceptions = MessageHandlerException.class)
+    public void testInitialiseClientSecretJWT_NoSecurityParams() throws Exception {
+        partyConfig.setTokenEndpointAuthMethod("client_secret_jwt");
+        handler.initialize();
+        final var outboundMsgCtx = getOutboundMessageContextFailIfNull(prc);
+        handler.invoke(outboundMsgCtx);
+    }
+    
+    @Test
+    public void testInitialisePrivateKeyJWT_Success() throws Exception {
+        partyConfig.setTokenEndpointAuthMethod("private_key_jwt");
+        partyConfig.setUseTargetedEndpointAsJWTAudience(true);
+        
+        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();
+        final var publicKey = key.toPublicKey();
+        assert publicKey != null;
+        secParams.setSigningCredential(new BasicCredential(publicKey, key.toPrivateKey()));
+        
+        final var outboundMsgCtx = getOutboundMessageContextFailIfNull(prc);
+        outboundMsgCtx.addSubcontext(secContext);
+        
+        final var peerEntityCtx = outboundMsgCtx.getSubcontext(OIDCPeerEntityContext.class);
+        assert peerEntityCtx != null;
+        peerEntityCtx.setIdentifier("https://op.example.com");
+        
+        handler.initialize();
+        handler.invoke(outboundMsgCtx);        
+        
+        final var context = peerEntityCtx.getSubcontext(OAuth2ClientAuthenticationContext.class);
+        assert context != null;
+        assertNotNull(context);
+        assertNotNull(context.getClientAuthentication());
+        assertTrue(context.getClientAuthentication() instanceof PrivateKeyJWT);
+        final var privateKeyJwt = (PrivateKeyJWT) context.getClientAuthentication();
+        assert privateKeyJwt != null;
+        assertNotNull(privateKeyJwt);
+        assertNotNull(privateKeyJwt.getJWTAuthenticationClaimsSet());
+        assertNotNull(privateKeyJwt.getClientAssertion());
+        assertEquals(privateKeyJwt.getJWTAuthenticationClaimsSet().getClientID().toString(), TestConstants.CLIENT_ID);
+        assertEquals(privateKeyJwt.getJWTAuthenticationClaimsSet().getSubject().toString(), TestConstants.CLIENT_ID);
+        assertEquals(privateKeyJwt.getJWTAuthenticationClaimsSet().getIssuer().toString(), TestConstants.CLIENT_ID);        
+        assertEquals(privateKeyJwt.getJWTAuthenticationClaimsSet().getAudience().size(),1);
+        assertEquals(privateKeyJwt.getJWTAuthenticationClaimsSet().getAudience().get(0).toString(),
+                "https://oauth2.op.example.org/token");
+        assertNotNull(privateKeyJwt.getJWTAuthenticationClaimsSet().getJWTID());
+        assertNotNull(privateKeyJwt.getJWTAuthenticationClaimsSet().getExpirationTime());
+    }
+    
+    @Test
+    public void testInitialisePrivateKeyJWT_TokenEndpointURL_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();
+        final var publicKey = key.toPublicKey();
+        assert publicKey != null;
+        secParams.setSigningCredential(new BasicCredential(publicKey, key.toPrivateKey()));
+        
+        final var outboundMsgCtx = getOutboundMessageContextFailIfNull(prc);
+        outboundMsgCtx.addSubcontext(secContext);
+        
+        final var peerEntityCtx = outboundMsgCtx.getSubcontext(OIDCPeerEntityContext.class);
+        assert peerEntityCtx != null;
+        peerEntityCtx.setIdentifier("https://op.example.com");
+        partyConfig.setUseTargetedEndpointAsJWTAudience(true);
+        
+        handler.initialize();
+        handler.invoke(outboundMsgCtx);
+        
+        
+        
+        final var context = peerEntityCtx.getSubcontext(OAuth2ClientAuthenticationContext.class);
+        assert context != null;
+        assertNotNull(context);
+        assertNotNull(context.getClientAuthentication());
+        assertTrue(context.getClientAuthentication() instanceof PrivateKeyJWT);
+        final var privateKeyJwt = (PrivateKeyJWT) context.getClientAuthentication();
+        assert privateKeyJwt != null;
+        assertNotNull(privateKeyJwt);
+        assertNotNull(privateKeyJwt.getJWTAuthenticationClaimsSet());
+        assertNotNull(privateKeyJwt.getClientAssertion());
+        assertEquals(privateKeyJwt.getJWTAuthenticationClaimsSet().getClientID().toString(), TestConstants.CLIENT_ID);
+        assertEquals(privateKeyJwt.getJWTAuthenticationClaimsSet().getSubject().toString(), TestConstants.CLIENT_ID);
+        assertEquals(privateKeyJwt.getJWTAuthenticationClaimsSet().getIssuer().toString(), TestConstants.CLIENT_ID);        
+        assertEquals(privateKeyJwt.getJWTAuthenticationClaimsSet().getAudience().size(),1);
+        assertEquals(privateKeyJwt.getJWTAuthenticationClaimsSet().getAudience().get(0).toString(),
+                "https://oauth2.op.example.org/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();
+        final var publicKey = key.toPublicKey();
+        assert publicKey != null;
+        secParams.setSigningCredential(new BasicCredential(publicKey, key.toPrivateKey()));
+        
+        final var outboundMsgCtx = getOutboundMessageContextFailIfNull(prc);        
+        outboundMsgCtx.addSubcontext(secContext);
+        
+        handler.initialize();
+        handler.invoke(outboundMsgCtx);        
+    }
+    
+    @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());
+        
+        final var outboundMsgCtx = getOutboundMessageContextFailIfNull(prc);
+        outboundMsgCtx.addSubcontext(secContext);
+        
+        handler.initialize();
+        handler.invoke(outboundMsgCtx);
+       
+    }
+    
+    @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);
+        
+        final var outboundMsgCtx = getOutboundMessageContextFailIfNull(prc);
+        outboundMsgCtx.addSubcontext(secContext);
+        
+        handler.initialize();
+        handler.invoke(outboundMsgCtx);        
+    }
+    
+    @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);
+        
+        final var outboundMsgCtx = getOutboundMessageContextFailIfNull(prc);
+        outboundMsgCtx.addSubcontext(secContext);
+        
+        handler.initialize();
+        handler.invoke(outboundMsgCtx);        
+    }
+
+}

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


More information about the commits mailing list