[java-idp-plugin-oidc-rp] branch main updated: Improve test construction and coverage

Phil Smart philip.smart at jisc.ac.uk
Tue Feb 14 17:32:00 UTC 2023


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

philsmart pushed a commit to branch main
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=4ecebdef09358d8a1a32ac06ff9717c2ec8b7308

The following commit(s) were added to refs/heads/main by this push:
     new 4ecebde  Improve test construction and coverage
4ecebde is described below

commit 4ecebdef09358d8a1a32ac06ff9717c2ec8b7308
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Tue Feb 14 17:31:55 2023 +0000

    Improve test construction and coverage
---
 .../OIDCRPFlowFromAuthenticationResponseTest.java  | 138 ++++++---
 .../oidc/rp/impl/OIDCRPFlowPreRedirectTest.java    |  10 +-
 .../plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java  |  37 ---
 .../plugin/authn/oidc/rp/impl/TestTokenHelper.java | 343 +++++++++------------
 4 files changed, 230 insertions(+), 298 deletions(-)

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 c8a38b6..7bf0135 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
@@ -8,13 +8,20 @@ import java.util.Map;
 import org.junit.Test;
 import org.opensaml.messaging.context.MessageContext;
 import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.security.credential.BasicCredential;
 import org.opensaml.security.credential.Credential;
 import org.springframework.webflow.engine.impl.FlowExecutionImpl;
 
+import com.nimbusds.jose.EncryptionMethod;
 import com.nimbusds.jose.JOSEException;
 import com.nimbusds.jose.JWEAlgorithm;
+import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.jose.JWSObject.State;
+import com.nimbusds.jose.jwk.Curve;
+import com.nimbusds.jose.jwk.KeyUse;
 import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
+import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
 import com.nimbusds.jwt.SignedJWT;
 import com.nimbusds.oauth2.sdk.ResponseType;
 import com.nimbusds.oauth2.sdk.id.ClientID;
@@ -36,6 +43,7 @@ import net.shibboleth.oidc.profile.config.JSONSecurityConfiguration;
 import net.shibboleth.oidc.profile.config.OIDCAuthorizationConfiguration;
 import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
 import net.shibboleth.oidc.security.credential.BasicJWKCredential;
+import net.shibboleth.oidc.security.credential.DefaultClientSecretCredential;
 import net.shibboleth.oidc.security.credential.JOSEObjectCredentialResolver;
 import net.shibboleth.oidc.security.credential.impl.BasicJOSEObjectCredentialResolver;
 import net.shibboleth.oidc.security.credential.impl.ChainingJOSEObjectCredentialResolver;
@@ -43,7 +51,6 @@ import net.shibboleth.oidc.security.credential.impl.ClientSecretCriterionCredent
 import net.shibboleth.oidc.security.impl.ExplicitKeySignedJWTTrustEngine;
 import net.shibboleth.oidc.security.jose.impl.BasicDecryptionConfiguration;
 import net.shibboleth.oidc.security.jose.impl.BasicSignatureValidationConfiguration;
-import net.shibboleth.utilities.java.support.collection.Pair;
 import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
 import net.shibboleth.utilities.java.support.resolver.ResolverException;
 import okhttp3.mockwebserver.MockResponse;
@@ -62,7 +69,7 @@ public class OIDCRPFlowFromAuthenticationResponseTest extends OIDCRPFlowTest {
      * @throws Exception on error.
      */
     @Test 
-    public void testAuthnFlowFromAuthorizationCallback_Using_MACSignedIDToken_PlainUserInfo() throws Exception {
+    public void test_IDTokenHS256_PlainUserInfo() throws Exception {
         
         setFlowPath(FLOW);
         setFlowModelResources(flowResources);
@@ -78,12 +85,13 @@ public class OIDCRPFlowFromAuthenticationResponseTest extends OIDCRPFlowTest {
         // First is token exchange
         mockOPServer.enqueue(new MockResponse().setResponseCode(200)
                 .setHeader("content-type", "application/json")
-                .setBody(TestTokenHelper.createAccessTokenResponseJSONWithHMACIDToken(
-                        OP_ISSUER_ID, CLIENT_ID, List.of(CLIENT_ID), CLIENT_SECRET)));
+                .setBody(TestTokenHelper.createAccessTokenResponseJSON(
+                        OP_ISSUER_ID, CLIENT_ID, List.of(CLIENT_ID), JWSAlgorithm.HS256, null, null, 
+                        new DefaultClientSecretCredential(CLIENT_SECRET).toSigningCredential(), null)));
         // Second is plain userInfo
         mockOPServer.enqueue(new MockResponse().setResponseCode(200)
                 .setHeader("content-type", "application/json")
-                .setBody(USERINFO_RESPONSE));
+                .setBody(TestTokenHelper.createPlainUserInfoResponseString(OP_ISSUER_ID, List.of(CLIENT_ID),"jdoe")));
         mockOPServer.start(9918);
         
 
@@ -130,7 +138,7 @@ public class OIDCRPFlowFromAuthenticationResponseTest extends OIDCRPFlowTest {
      * @throws Exception on error.
      */
     @Test 
-    public void testAuthnFlowFromAuthorizationCallback_Using_MACSignedIDToken_InvalidPlainUserInfo() throws Exception {
+    public void test_IDTokenHS256_InvalidPlainUserInfo() throws Exception {
         
         setFlowPath(FLOW);
         setFlowModelResources(flowResources);
@@ -146,12 +154,13 @@ public class OIDCRPFlowFromAuthenticationResponseTest extends OIDCRPFlowTest {
         // First is token exchange
         mockOPServer.enqueue(new MockResponse().setResponseCode(200)
                 .setHeader("content-type", "application/json")
-                .setBody(TestTokenHelper.createAccessTokenResponseJSONWithHMACIDToken(
-                        OP_ISSUER_ID, CLIENT_ID, List.of(CLIENT_ID), CLIENT_SECRET)));
+                .setBody(TestTokenHelper.createAccessTokenResponseJSON(
+                        OP_ISSUER_ID, CLIENT_ID, List.of(CLIENT_ID), JWSAlgorithm.HS256, null, null, 
+                        new DefaultClientSecretCredential(CLIENT_SECRET).toSigningCredential(), null)));
         // Second is plain userInfo
         mockOPServer.enqueue(new MockResponse().setResponseCode(200)
                 .setHeader("content-type", "application/json")
-                .setBody(USERINFO_RESPONSE_NO_SUB));
+                .setBody(TestTokenHelper.createPlainUserInfoResponseString(OP_ISSUER_ID, List.of(CLIENT_ID), null)));
         mockOPServer.start(9918);
         
 
@@ -183,12 +192,10 @@ public class OIDCRPFlowFromAuthenticationResponseTest extends OIDCRPFlowTest {
         
         // Should have failed to produce a result
         assertFlowEndedInErrorConditions(prc, "InvalidUserInfoClaims");  
-
     }
-
     
     @Test 
-    public void testAuthnFlowFromAuthorizationCallback_Using_HMAC_UserInfo_And_IDToken_Response() throws Exception {
+    public void test_IDTokenHS256_UserInfoHS256() throws Exception {
         
         setFlowPath(FLOW);
         setFlowModelResources(flowResources);
@@ -204,11 +211,14 @@ public class OIDCRPFlowFromAuthenticationResponseTest extends OIDCRPFlowTest {
         // First is token exchange
         mockOPServer.enqueue(new MockResponse().setResponseCode(200)
                 .setHeader("content-type", "application/json")
-                .setBody(TestTokenHelper.createAccessTokenResponseJSONWithHMACIDToken(
-                        OP_ISSUER_ID, CLIENT_ID, List.of(CLIENT_ID), CLIENT_SECRET)));
-        // Second is userInfo
-        final var userInfoToken = TestTokenHelper.createHMACSignedUserInfoJWTResponseJSON(
-                OP_ISSUER_ID,CLIENT_ID, List.of(CLIENT_ID), CLIENT_SECRET);
+                .setBody(TestTokenHelper.createAccessTokenResponseJSON(
+                        OP_ISSUER_ID, CLIENT_ID, List.of(CLIENT_ID), JWSAlgorithm.HS256, null, null, 
+                        new DefaultClientSecretCredential(CLIENT_SECRET).toSigningCredential(), null)));
+        // Second is userInfo        
+        final var userInfoToken = TestTokenHelper.createJWTUserInfoResponse(
+                OP_ISSUER_ID, List.of(CLIENT_ID), "jdoe", JWSAlgorithm.HS256, null, null, 
+                new DefaultClientSecretCredential(CLIENT_SECRET).toSigningCredential(), null);
+        
         mockOPServer.enqueue(new MockResponse().setResponseCode(200)
                 .setHeader("content-type", "application/jwt")
                 .setBody(userInfoToken.serialize()));
@@ -255,7 +265,7 @@ public class OIDCRPFlowFromAuthenticationResponseTest extends OIDCRPFlowTest {
     }
     
     @Test 
-    public void testAuthnFlowFromAuthorizationCallback_Using_SymetricSignedIDToken_And_AsymetricSignedAndEncryptedUserInfoResponse() 
+    public void test_IDTokenHS256_DirA128CBC_HS256_UserInfoES256_RSA_OAEP_256A256GCM() 
             throws Exception {
         
         setFlowPath(FLOW);
@@ -271,17 +281,32 @@ public class OIDCRPFlowFromAuthenticationResponseTest extends OIDCRPFlowTest {
         
         final MockWebServer mockOPServer = createSimpleServer();
         // First is token exchange
+        
+        final var accessTokenResponseJson = TestTokenHelper.createAccessTokenResponseJSON(
+                OP_ISSUER_ID, CLIENT_ID, List.of(CLIENT_ID), JWSAlgorithm.HS256, JWEAlgorithm.DIR, 
+                EncryptionMethod.A128CBC_HS256, 
+                new DefaultClientSecretCredential(CLIENT_SECRET).toSigningCredential(), 
+                new DefaultClientSecretCredential(CLIENT_SECRET).toEncryptionCredential(
+                        JWEAlgorithm.DIR, EncryptionMethod.A128CBC_HS256));
+
         mockOPServer.enqueue(new MockResponse().setResponseCode(200)
                 .setHeader("content-type", "application/json")
-                .setBody(TestTokenHelper.createAccessTokenResponseJSONIDTokenSignedAndDirEncrypted(
-                        OP_ISSUER_ID, CLIENT_ID, List.of(CLIENT_ID), CLIENT_SECRET)));
-        // Second is userInfo
-        final var userInfoTokenAndKey =
-                TestTokenHelper.createAsymetricSignedAndAsymetricEncryptedUserInfoJWTResponse(
-                        OP_ISSUER_ID,List.of(CLIENT_ID), CLIENT_ID);
+                .setBody(accessTokenResponseJson));
+        // Second is userInfo      
+        final var sigKey = new ECKeyGenerator(Curve.P_256).keyID("123").generate();
+        final RSAKey encKey = new RSAKeyGenerator(2048)
+                .keyID("1")
+                .keyUse(KeyUse.ENCRYPTION)
+                .generate();
+        final var userInfoToken = TestTokenHelper.createJWTUserInfoResponse(
+                OP_ISSUER_ID, List.of(CLIENT_ID), "jdoe", JWSAlgorithm.ES256, JWEAlgorithm.RSA_OAEP_256, 
+                EncryptionMethod.A256GCM, 
+                new BasicCredential(sigKey.toPublicKey(), sigKey.toPrivateKey()), 
+                new BasicCredential(encKey.toPublicKey(), encKey.toPrivateKey()));
+        
         mockOPServer.enqueue(new MockResponse().setResponseCode(200)
                 .setHeader("content-type", "application/jwt")
-                .setBody(userInfoTokenAndKey.getSecond().serialize()));
+                .setBody(userInfoToken.serialize()));
         mockOPServer.start(9918);
         
 
@@ -315,10 +340,10 @@ public class OIDCRPFlowFromAuthenticationResponseTest extends OIDCRPFlowTest {
             public Credential resolveSingle(final CriteriaSet criteria) throws ResolverException {
                 final BasicJWKCredential jwkCredential = new BasicJWKCredential();
                 jwkCredential.setAlgorithm(JWEAlgorithm.RSA_OAEP_256);
-                jwkCredential.setKid(userInfoTokenAndKey.getFirst().getSecond().getKeyID());                
+                jwkCredential.setKid(encKey.getKeyID());                
                 try {
-                    jwkCredential.setPrivateKey(userInfoTokenAndKey.getFirst().getSecond().toPrivateKey());
-                    jwkCredential.setPublicKey(userInfoTokenAndKey.getFirst().getSecond().toPublicKey());
+                    jwkCredential.setPrivateKey(encKey.toPrivateKey());
+                    jwkCredential.setPublicKey(encKey.toPublicKey());
                 } catch (final JOSEException e) {
                     fail();
                 }                
@@ -344,10 +369,10 @@ public class OIDCRPFlowFromAuthenticationResponseTest extends OIDCRPFlowTest {
                     public Credential resolveSingle(final CriteriaSet criteria) throws ResolverException {
                         final BasicJWKCredential jwkCredential = new BasicJWKCredential();
                         jwkCredential.setAlgorithm(JWEAlgorithm.RSA_OAEP_256);
-                        jwkCredential.setKid(userInfoTokenAndKey.getFirst().getFirst().getKeyID());                
+                        jwkCredential.setKid(sigKey.getKeyID());                
                         try {
-                            jwkCredential.setPrivateKey(userInfoTokenAndKey.getFirst().getFirst().toPrivateKey());
-                            jwkCredential.setPublicKey(userInfoTokenAndKey.getFirst().getFirst().toPublicKey());
+                            jwkCredential.setPrivateKey(sigKey.toPrivateKey());
+                            jwkCredential.setPublicKey(sigKey.toPublicKey());
                         } catch (final JOSEException e) {
                             fail();
                         }                
@@ -403,7 +428,7 @@ public class OIDCRPFlowFromAuthenticationResponseTest extends OIDCRPFlowTest {
      * @throws Exception on error.
      */
     @Test 
-    public void testAuthnFlowFromAuthorizationCallback_Using_SymetricSigned_And_AsymetricEncryptedIDToken() 
+    public void test_IDTokenHS256_RSA_OAEP_256_A256GCM_PlainUserInfo() 
             throws Exception {
         
         setFlowPath(FLOW);
@@ -417,18 +442,26 @@ public class OIDCRPFlowFromAuthenticationResponseTest extends OIDCRPFlowTest {
         
         setMockProperties(mockProperties);
         
-        final Pair<String, RSAKey> accessTokenAndKey = 
-                TestTokenHelper.createAccessTokenResponseJSONWithSignedAndAsymmetricEncryptedIDToken(
-                OP_ISSUER_ID, CLIENT_ID, List.of(CLIENT_ID), CLIENT_SECRET);
+        final RSAKey encKey = new RSAKeyGenerator(2048)
+                .keyID("1")
+                .keyUse(KeyUse.ENCRYPTION)
+                .generate();        
+        final var accessTokenResponseJson = TestTokenHelper.createAccessTokenResponseJSON(
+                OP_ISSUER_ID, CLIENT_ID, List.of(CLIENT_ID), JWSAlgorithm.HS256, JWEAlgorithm.RSA_OAEP_256, 
+                EncryptionMethod.A256GCM, 
+                new DefaultClientSecretCredential(CLIENT_SECRET).toSigningCredential(), 
+                new BasicCredential(encKey.toPublicKey(), encKey.toPrivateKey()));
+        
+        
         final MockWebServer mockOPServer = createSimpleServer();
         // First is token exchange
         mockOPServer.enqueue(new MockResponse().setResponseCode(200)
                 .setHeader("content-type", "application/json")
-                .setBody(accessTokenAndKey.getFirst()));
+                .setBody(accessTokenResponseJson));
         // Second is userInfo
         mockOPServer.enqueue(new MockResponse().setResponseCode(200)
                 .setHeader("content-type", "application/json")
-                .setBody(USERINFO_RESPONSE));
+                .setBody(TestTokenHelper.createPlainUserInfoResponseString(OP_ISSUER_ID, List.of(CLIENT_ID), "jdoe")));
         mockOPServer.start(9918);
         
 
@@ -460,10 +493,10 @@ public class OIDCRPFlowFromAuthenticationResponseTest extends OIDCRPFlowTest {
             public Credential resolveSingle(final CriteriaSet criteria) throws ResolverException {
                 final BasicJWKCredential jwkCredential = new BasicJWKCredential();
                 jwkCredential.setAlgorithm(JWEAlgorithm.RSA_OAEP_256);
-                jwkCredential.setKid(accessTokenAndKey.getSecond().getKeyID());                
+                jwkCredential.setKid(encKey.getKeyID());                
                 try {
-                    jwkCredential.setPrivateKey(accessTokenAndKey.getSecond().toPrivateKey());
-                    jwkCredential.setPublicKey(accessTokenAndKey.getSecond().toPublicKey());
+                    jwkCredential.setPrivateKey(encKey.toPrivateKey());
+                    jwkCredential.setPublicKey(encKey.toPublicKey());
                 } catch (final JOSEException e) {
                     fail();
                 }                
@@ -517,7 +550,7 @@ public class OIDCRPFlowFromAuthenticationResponseTest extends OIDCRPFlowTest {
      * @throws Exception on error.
      */
     @Test 
-    public void testAuthnFlowFromAuthorizationCallback_UsingPlainJSONObjectUserInfoResponse() 
+    public void test_IDTokenHS256_PlainJWTUserInfoResponse() 
             throws Exception {
         
         setFlowPath(FLOW);
@@ -535,12 +568,13 @@ public class OIDCRPFlowFromAuthenticationResponseTest extends OIDCRPFlowTest {
         // First is token exchange
         mockOPServer.enqueue(new MockResponse().setResponseCode(200)
                 .setHeader("content-type", "application/json")
-                .setBody(TestTokenHelper.createAccessTokenResponseJSONWithHMACIDToken(
-                        OP_ISSUER_ID, CLIENT_ID, List.of(CLIENT_ID), CLIENT_SECRET)));
+                .setBody(TestTokenHelper.createAccessTokenResponseJSON(
+                        OP_ISSUER_ID, CLIENT_ID, List.of(CLIENT_ID), JWSAlgorithm.HS256, null, null, 
+                        new DefaultClientSecretCredential(CLIENT_SECRET).toSigningCredential(), null)));
         // Second is userInfo
         mockOPServer.enqueue(new MockResponse().setResponseCode(200)
                 .setHeader("content-type", "application/jwt")
-                .setBody(TestTokenHelper.createPlainUserInfoJWTResponseJSON(OP_ISSUER_ID,CLIENT_ID)
+                .setBody(TestTokenHelper.createPlainUserInfoResponseJSON(OP_ISSUER_ID,CLIENT_ID)
                         .serialize()));
         mockOPServer.start(9918);
         
@@ -563,7 +597,7 @@ public class OIDCRPFlowFromAuthenticationResponseTest extends OIDCRPFlowTest {
     }
     
     @Test 
-    public void testAuthnFlowFromAuthorizationCallback_Using_HMAC_UserInfo_And_IDToken_Response_WithACRAMRTranslation() 
+    public void test_IDTokenHS256_UserInfoHS256_WithACRAMRTranslation() 
             throws Exception {
         
         setFlowPath(FLOW);
@@ -580,11 +614,13 @@ public class OIDCRPFlowFromAuthenticationResponseTest extends OIDCRPFlowTest {
         // First is token exchange
         mockOPServer.enqueue(new MockResponse().setResponseCode(200)
                 .setHeader("content-type", "application/json")
-                .setBody(TestTokenHelper.createAccessTokenResponseJSONWithHMACIDToken(
-                        OP_ISSUER_ID, CLIENT_ID, List.of(CLIENT_ID), CLIENT_SECRET)));
+                .setBody(TestTokenHelper.createAccessTokenResponseJSON(
+                        OP_ISSUER_ID, CLIENT_ID, List.of(CLIENT_ID), JWSAlgorithm.HS256, null, null, 
+                        new DefaultClientSecretCredential(CLIENT_SECRET).toSigningCredential(), null)));
         // Second is userInfo
-        final var userInfoToken = TestTokenHelper.createHMACSignedUserInfoJWTResponseJSON(
-                OP_ISSUER_ID,CLIENT_ID, List.of(CLIENT_ID), CLIENT_SECRET);
+        final var userInfoToken = TestTokenHelper.createJWTUserInfoResponse(
+                OP_ISSUER_ID, List.of(CLIENT_ID), "jdoe", JWSAlgorithm.HS256, null, null, 
+                new DefaultClientSecretCredential(CLIENT_SECRET).toSigningCredential(), null);
         mockOPServer.enqueue(new MockResponse().setResponseCode(200)
                 .setHeader("content-type", "application/jwt")
                 .setBody(userInfoToken.serialize()));
@@ -623,7 +659,7 @@ public class OIDCRPFlowFromAuthenticationResponseTest extends OIDCRPFlowTest {
      * @throws Exception on error.
      */
     @Test 
-    public void testAuthnFlowFromAuthorizationCallback_UnsupportedOIDCFlow() 
+    public void testUnsupportedOIDCFlow() 
             throws Exception {
         
         setFlowPath(FLOW);
@@ -670,7 +706,7 @@ public class OIDCRPFlowFromAuthenticationResponseTest extends OIDCRPFlowTest {
      * @throws Exception on error.
      */
     @Test 
-    public void testAuthnFlowFromAuthorizationCallback_ErrorAuthenticationResponse() 
+    public void testErrorAuthenticationResponse() 
             throws Exception {
         
         setFlowPath(FLOW);
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowPreRedirectTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowPreRedirectTest.java
index c3f6932..763b084 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowPreRedirectTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowPreRedirectTest.java
@@ -93,7 +93,7 @@ public class OIDCRPFlowPreRedirectTest extends OIDCRPFlowTest {
      * @throws Exception on error.
      */
     @Test
-    public void testFlowToAuthorizationRedirect_UsingRequestObject() throws Exception {
+    public void test_UsingRequestObject() throws Exception {
         setFlowPath(FLOW);
         setFlowModelResources(flowResources);
         setSubflows(subflows);
@@ -145,7 +145,7 @@ public class OIDCRPFlowPreRedirectTest extends OIDCRPFlowTest {
      * @throws Exception on error.
      */
     @Test
-    public void testFlowToAuthorizationRedirect_UsingRequestObject_WithEncryption() throws Exception {
+    public void test_UsingRequestObject_WithEncryption() throws Exception {
         setFlowPath(FLOW);
         setFlowModelResources(flowResources);
         setSubflows(subflows);
@@ -219,7 +219,7 @@ public class OIDCRPFlowPreRedirectTest extends OIDCRPFlowTest {
      * @throws Exception on error.
      */
     @Test
-    public void testFlowToAuthorizationRedirect_UsingRequestObject_RSA256Signature() throws Exception {
+    public void test_UsingRequestObject_RSA256Signature() throws Exception {
         setFlowPath(FLOW);
         setFlowModelResources(flowResources);
         setSubflows(subflows);
@@ -281,7 +281,7 @@ public class OIDCRPFlowPreRedirectTest extends OIDCRPFlowTest {
      * @throws Exception on error.
      */
     @Test
-    public void testFlowToAuthorizationRedirect_Fail_UsingRequestObject_UnsupportedSignatureAlgorithm()
+    public void test_Fail_UsingRequestObject_UnsupportedSignatureAlgorithm()
             throws Exception {
         setFlowPath(FLOW);
         setFlowModelResources(flowResources);
@@ -343,7 +343,7 @@ public class OIDCRPFlowPreRedirectTest extends OIDCRPFlowTest {
      * @throws Exception on error.
      */
     @Test
-    public void testFlowToAuthorizationRedirect_WithACRs() throws Exception {
+    public void testWithACRs() throws Exception {
         setFlowPath(FLOW);
         setFlowModelResources(flowResources);
         setSubflows(subflows);
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 f02bf00..8a71389 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
@@ -88,7 +88,6 @@ import net.shibboleth.oidc.security.impl.ExplicitKeySignedJWTTrustEngine;
 import net.shibboleth.oidc.security.jose.impl.BasicDecryptionConfiguration;
 import net.shibboleth.oidc.security.jose.impl.BasicSignatureValidationConfiguration;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
-import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
 import okhttp3.mockwebserver.MockWebServer;
 import okhttp3.tls.HandshakeCertificates;
@@ -167,42 +166,6 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
       protected static final ClassPathResource GOOD_PROVIDER_CONFIGURATION_INFO_USE_REQUEST_OBJECT_HS512_SIG = 
              new ClassPathResource("/metadata/test-provider-requestobject-HS512-only-sig.json");
    
-
-    /** Mock JSON Object response from the UserInfo endpoint.*/
-    @Nonnull @NotEmpty
-    protected static final String USERINFO_RESPONSE ="{\n"
-            + "  \"sub\": \"jdoe\",\n"
-            + "  \"website\": \"https://openid.net/\",\n"
-            + "  \"zoneinfo\": \"America/Los_Angeles\",\n"
-            + "  \"birthdate\": \"2000-02-03\",\n"
-            + "  \"gender\": \"female\",\n"
-            + "  \"preferred_username\": \"d.tu\",\n"
-            + "  \"given_name\": \"Demo\",\n"
-            + "  \"middle_name\": \"Theresa\",\n"
-            + "  \"locale\": \"en-US\",\n"
-            + "  \"updated_at\": 1580000000,\n"
-            + "  \"name\": \"Demo T. User\",\n"
-            + "  \"nickname\": \"Dee\",\n"
-            + "  \"family_name\": \"User\"\n"
-            + "}";
-    
-    /** Mock JSON Object response from the UserInfo endpoint.*/
-    @Nonnull @NotEmpty
-    protected static final String USERINFO_RESPONSE_NO_SUB ="{\n"
-            + "  \"website\": \"https://openid.net/\",\n"
-            + "  \"zoneinfo\": \"America/Los_Angeles\",\n"
-            + "  \"birthdate\": \"2000-02-03\",\n"
-            + "  \"gender\": \"female\",\n"
-            + "  \"preferred_username\": \"d.tu\",\n"
-            + "  \"given_name\": \"Demo\",\n"
-            + "  \"middle_name\": \"Theresa\",\n"
-            + "  \"locale\": \"en-US\",\n"
-            + "  \"updated_at\": 1580000000,\n"
-            + "  \"name\": \"Demo T. User\",\n"
-            + "  \"nickname\": \"Dee\",\n"
-            + "  \"family_name\": \"User\"\n"
-            + "}";
-
     
     /** Path to the flow to be tested.*/
     @Nonnull protected static final String FLOW = 
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TestTokenHelper.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TestTokenHelper.java
index 21ac7ba..7271d6c 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TestTokenHelper.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TestTokenHelper.java
@@ -18,10 +18,21 @@
 
 package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
 
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.text.ParseException;
 import java.time.Instant;
 import java.util.Date;
 import java.util.List;
 
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.security.credential.Credential;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.nimbusds.jose.EncryptionMethod;
 import com.nimbusds.jose.JOSEException;
 import com.nimbusds.jose.JOSEObjectType;
@@ -30,27 +41,20 @@ import com.nimbusds.jose.JWEHeader;
 import com.nimbusds.jose.JWEObject;
 import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.jose.JWSHeader;
-import com.nimbusds.jose.KeyLengthException;
 import com.nimbusds.jose.Payload;
+import com.nimbusds.jose.crypto.AESEncrypter;
 import com.nimbusds.jose.crypto.DirectEncrypter;
+import com.nimbusds.jose.crypto.ECDHEncrypter;
 import com.nimbusds.jose.crypto.ECDSASigner;
 import com.nimbusds.jose.crypto.MACSigner;
 import com.nimbusds.jose.crypto.RSAEncrypter;
-import com.nimbusds.jose.jwk.Curve;
-import com.nimbusds.jose.jwk.ECKey;
-import com.nimbusds.jose.jwk.KeyUse;
-import com.nimbusds.jose.jwk.RSAKey;
-import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
-import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
+import com.nimbusds.jose.crypto.RSASSASigner;
 import com.nimbusds.jwt.EncryptedJWT;
+import com.nimbusds.jwt.JWT;
 import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.jwt.PlainJWT;
 import com.nimbusds.jwt.SignedJWT;
 
-import net.shibboleth.oidc.security.credential.JWKCredential;
-import net.shibboleth.oidc.security.credential.JWKCredentialSupport;
-import net.shibboleth.utilities.java.support.collection.Pair;
-
 /** A helper class for generating tokens for testing.*/
 public final class TestTokenHelper {
     
@@ -62,9 +66,6 @@ public final class TestTokenHelper {
     /**
      * Build a basic {@link JWTClaimsSet} from the supplied parameters.
      * 
-     * @param subject the subject
-     * @param nonce the nonce
-     * @param name the name
      * @param opIssuerId the OP's issuer ID
      * @param clientId the client_id
      * @param audience the list of audiences
@@ -95,231 +96,170 @@ public final class TestTokenHelper {
     }
     
     /**
-     * Create an OAuth access token with a runtime constructed id_token. This allows the
-     * expiry to be current. The token is signed with the client_secret using HS256.
-     * 
-     * @param opIssuerId the OP's issuer ID
-     * @param clientId the client_id
-     * @param audience the list of audiences for the id_token token
-     * @param clientSecret the client_secret used to sign the id_token
-     * 
+     * Build a basic {@link JWTClaimsSet} from the supplied parameters to mock a UserInfo response.
      * 
-     * @return a serialized access token response.
+     * @param subject the subject
+     * @param opIssuerId the OP's issuer ID     * 
+     * @param audience the list of audiences
+     * @param sub the subject
      * 
-     * @throws Exception on error.
+     * @return the JWT claims set.
      */
-    public static String createAccessTokenResponseJSONWithHMACIDToken(
-            final String opIssuerId, final String clientId, 
-            final List<String> audience, final String clientSecret) throws Exception {
+    public static JWTClaimsSet createBasicUserInfoClaims(
+            final String opIssuerId, final List<String> audience, final String sub) {
         
-        final var header = new JWSHeader.Builder(JWSAlgorithm.HS256)
-                .type(JOSEObjectType.JWT)
+        return new JWTClaimsSet.Builder()
+                .issuer(opIssuerId)
+                .audience(audience)
+                .subject(sub)
+                .claim("preferred_username", "d.tu")
+                .claim("given_name", "Demo")
+                .claim("family_name", "User")
+                .claim("nonce", "abadnonce")
+                .claim("nickname", "Dee")
+                .claim("name","Demo T. User")
                 .build();
-        
-        final var payload = createBasicClaims(opIssuerId, clientId, audience);
-       
-        final var signedJWT = new SignedJWT(header,payload);
-        signedJWT.sign(new MACSigner(clientSecret));
-        
-        return buildTemplateAccessTokenJSONResponse(signedJWT.serialize());
     }
     
     /**
-     * Create an OAuth access token with a runtime constructed id_token. This allows the
-     * expiry to be current. The token is signed using HS256 and encrypted using the 'Direct Encryption' (dir)
-     * management mode i.e. no key wrapping. Both use the shared client_secret.
+     * Create a JWT from the given payload. The JWT can either be plain, or signed and encrypted. If encrypted, it must
+     * be signed. 
      * 
-     * @param opIssuerId the OP's issuer ID
-     * @param clientId the client_id
-     * @param audience the list of audiences for the id_token token
-     * @param clientSecret the client_secret used to sign the id_token
+     * @param payload the payload to create the JWT from
+     * @param sigAlg the signature alg to use. {@code Null} if the JWT is not going to be signed and/or encrypted.
+     * @param jweAlg the encryption alg to use. {@code Null} if the JWT is not going to be encrypted. Note, if encrypted
+     *                  the signature algorithm must also be supplied, otherwise a plain JWT will be returned.
+     * @param enc the content encryption algorithm to use.
+     * @param sigKey the key to use for signing
+     * @param encKey the key to use for encryption
      * 
-     * @return a serialized access token response.
+     * @return and plain JWT, JWS, or JWE.
      * 
-     * @throws Exception on error.
+     * @throws JOSEException on error.
+     * @throws ParseException on error.
      */
-    public static String createAccessTokenResponseJSONIDTokenSignedAndDirEncrypted(final String opIssuerId, 
-            final String clientId, final List<String> audience, final String clientSecret) throws Exception {
-        
-        final var header = new JWSHeader.Builder(JWSAlgorithm.HS256)
-                .type(JOSEObjectType.JWT)
-                .build();
-        
-        //client_secret needs to be derived
-        final JWKCredential derviedCred = JWKCredentialSupport.deriveSymmetricKeyForAlgAndEnc(
-                TestCredentialHelper.createClientSecretCredential(clientSecret).toSigningCredential(), 
-                JWEAlgorithm.DIR, EncryptionMethod.A256GCM);
-        
-        final var payload = createBasicClaims(opIssuerId, clientId, audience);
-        final var signedJWT = new SignedJWT(header,payload);
-        // sign off the client_secret octets directly
-        signedJWT.sign(new MACSigner(clientSecret));
-        
-        final JWEObject jweObject = 
-                new JWEObject(new JWEHeader.Builder(JWEAlgorithm.DIR, EncryptionMethod.A256GCM)
-                .contentType("JWT")
-                .build(),
-                new Payload(signedJWT));
-        jweObject.encrypt(new DirectEncrypter(derviedCred.getSecretKey()));
-        final EncryptedJWT jwe = EncryptedJWT.parse(jweObject.serialize());
+    public static JWT createJWT(@Nonnull final JWTClaimsSet payload, @Nullable final JWSAlgorithm sigAlg, 
+            @Nullable final JWEAlgorithm jweAlg, @Nullable final EncryptionMethod enc, 
+            @Nullable final Credential sigKey, @Nullable final Credential encKey) throws JOSEException, ParseException {
         
-        return buildTemplateAccessTokenJSONResponse(jwe.serialize());
+        // Sign first.
+        if (sigAlg != null) {
+            final var header = new JWSHeader.Builder(sigAlg)
+                    .type(JOSEObjectType.JWT)
+                    .build();
+            final var signedJWT = new SignedJWT(header,payload);
+            
+            if (JWSAlgorithm.Family.HMAC_SHA.contains(sigAlg)){
+                signedJWT.sign(new MACSigner(sigKey.getSecretKey()));
+            } else if (JWSAlgorithm.Family.RSA.contains(sigAlg)) {
+                signedJWT.sign(new RSASSASigner(sigKey.getPrivateKey()));
+            } else if (JWSAlgorithm.Family.EC.contains(sigAlg)) {
+                signedJWT.sign(new ECDSASigner((ECPrivateKey)sigKey.getPrivateKey()));
+            }
+            
+            if (jweAlg != null) {
+                final JWEObject jweObject = 
+                        new JWEObject(new JWEHeader.Builder(jweAlg, enc)
+                        .contentType("JWT")
+                        .build(),
+                        new Payload(signedJWT));
+                
+                if (JWEAlgorithm.Family.RSA.contains(jweAlg)) {                    
+                    jweObject.encrypt(new RSAEncrypter((RSAPublicKey)encKey.getPublicKey()));                    
+                    return EncryptedJWT.parse(jweObject.serialize());
+                    
+                } else if (JWEAlgorithm.Family.AES_KW.contains(jweAlg) 
+                        || JWEAlgorithm.Family.AES_GCM_KW.contains(jweAlg)) {
+                    
+                    jweObject.encrypt(new AESEncrypter(encKey.getSecretKey()));
+                    return EncryptedJWT.parse(jweObject.serialize());
+                    
+                } else if (JWEAlgorithm.Family.ECDH_ES.contains(jweAlg)) {
+                    jweObject.encrypt(new ECDHEncrypter((ECPublicKey) encKey.getPublicKey()));
+                    return EncryptedJWT.parse(jweObject.serialize());
+                } else if (JWEAlgorithm.DIR == jweAlg) {
+                    jweObject.encrypt(new DirectEncrypter(encKey.getSecretKey()));
+                    return EncryptedJWT.parse(jweObject.serialize());
+                }
+            } else {
+                return signedJWT;
+            }
+        } 
+        return new PlainJWT(payload);        
     }
     
     /**
-     * Create an OAuth access token with a runtime constructed id_token. This allows the
-     * expiry to be current. The token is signed using HS256 (MAC) and encrypted using a key encryption
-     * management mode.
+     * Create a JWT UserInfo response. Can be a JWS or JWE depending on the input values.
      * 
-     * @param opIssuerId the OP's issuer ID
-     * @param clientId the client_id
-     * @param audience the list of audiences for the id_token token
-     * @param clientSecret the client_secret used to sign the id_token
-     * 
-     * @return a serialized access token response and the RSAKey used to encrypt the CEK.
-     * 
-     * @throws Exception on error.
-     */
-    public static Pair<String, RSAKey> createAccessTokenResponseJSONWithSignedAndAsymmetricEncryptedIDToken(
-            final String opIssuerId, 
-            final String clientId, final List<String> audience, final String clientSecret) throws Exception {
-        
-        final var header = new JWSHeader.Builder(JWSAlgorithm.HS256)
-                .type(JOSEObjectType.JWT)
-                .build();
-        
-        final var payload = createBasicClaims(opIssuerId, clientId, audience);
-        final var signedJWT = new SignedJWT(header,payload);
-        signedJWT.sign(new MACSigner(clientSecret));
-        
-        
-        final RSAKey keyRecipient = new RSAKeyGenerator(2048)
-                .keyID("1")
-                .keyUse(KeyUse.ENCRYPTION)
-                .generate();
-        
-        final JWEObject jweObject = 
-                new JWEObject(new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256GCM)
-                .contentType("JWT")
-                .build(),
-                new Payload(signedJWT));
-        jweObject.encrypt(new RSAEncrypter(keyRecipient.toPublicJWK()));
-        final EncryptedJWT jwe = EncryptedJWT.parse(jweObject.serialize());
-
-        return new Pair<>(buildTemplateAccessTokenJSONResponse(jwe.serialize()), keyRecipient);
-    }
-    
-    /**
-     * Create an RSA asymetrically signed and encrypted UserInfo response JWT. Return both signature and encryption
-     * keys alongside the EncryptedJWT in the response.
-     * 
-     * @param issuer the issuer
+     * @param opIssuerId the issuer
      * @param audience the audience
-     * @param clientId the client_id
-     * @return the signed JWT alongside the pair of signature and encryption keys
-     * @throws Exception 
-     * 
-     * @throws Exception on error
+     * @param sub the subject
+     * @param sigAlg the signature algorithm
+     * @param jweAlg the encryption algorithm
+     * @param enc the content encryption algorithm
+     * @param sigKey the signing key
+     * @param encKey the encryption key
+     * @return
+     * @throws JOSEException on error
+     * @throws ParseException on error
      */
-    public static Pair<Pair<ECKey, RSAKey>, EncryptedJWT> createAsymetricSignedAndAsymetricEncryptedUserInfoJWTResponse(
-            final String issuer, final List<String> audience, final String clientId) throws Exception {
-        
-        final RSAKey keyRecipient = new RSAKeyGenerator(2048)
-                .keyID("2")
-                .keyUse(KeyUse.ENCRYPTION)
-                .generate();
+    public static JWT createJWTUserInfoResponse(final String opIssuerId, final List<String> audience, 
+            final String sub, @Nullable final JWSAlgorithm sigAlg, 
+            @Nullable final JWEAlgorithm jweAlg, @Nullable final EncryptionMethod enc, 
+            @Nullable final Credential sigKey, @Nullable final Credential encKey) throws JOSEException, ParseException {
         
-        final var keySignedJWTPair = createECDSASignedUserInfoJWTResponseJSON(issuer, clientId, audience);
+        final var payload = createBasicUserInfoClaims(opIssuerId, audience, sub);
+        return createJWT(payload, sigAlg, jweAlg, enc, sigKey, encKey);
         
-        final JWEObject jweObject = 
-                new JWEObject(new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256GCM)
-                .contentType("JWT")
-                .build(),
-                new Payload(keySignedJWTPair.getSecond()));
-        jweObject.encrypt(new RSAEncrypter(keyRecipient.toPublicJWK()));
-        
-        return new Pair<>(
-                new Pair<>(keySignedJWTPair.getFirst(),keyRecipient), 
-                EncryptedJWT.parse(jweObject.serialize()));
     }
     
     /**
-     * Create an ECDSA signed UserInfo response JWT.
+     * Create an access token response JSON object and build a suitable id_token to include in the response.
      * 
      * @param opIssuerId the issuer
      * @param clientId the client_id
      * @param audience the audience
+     * @param sigAlg the signature algorithm
+     * @param jweAlg the encryption algorithm
+     * @param enc the content encryption algorithm
+     * @param sigKey the signing key
+     * @param encKey the encryption key
      * 
-     * @return the signed JWT
+     * @return an access token response JSON object
      * 
-     * @throws JOSEException on error
+     * @throws Exception on error.
      */
-    public static Pair<ECKey, SignedJWT> createECDSASignedUserInfoJWTResponseJSON(
-            final String opIssuerId, final String clientId, final List<String> audience) 
-            throws JOSEException {
+    public static String createAccessTokenResponseJSON(
+            final String opIssuerId, final String clientId, 
+            final List<String> audience, @Nullable final JWSAlgorithm sigAlg, 
+            @Nullable final JWEAlgorithm jweAlg, @Nullable final EncryptionMethod enc, 
+            @Nullable final Credential sigKey, @Nullable final Credential encKey) throws Exception {
         
-        final var key = new ECKeyGenerator(Curve.P_256).keyID("123").generate();
-        
-        final var header = new JWSHeader.Builder(JWSAlgorithm.ES256)
-                .type(JOSEObjectType.JWT)
-                .keyID(key.getKeyID())
-                .build();
         
         final var payload = createBasicClaims(opIssuerId, clientId, audience);
+        final JWT idToken = createJWT(payload, sigAlg, jweAlg, enc, sigKey, encKey);
         
-        final var signedJWT = new SignedJWT(header, payload);
-        signedJWT.sign(new ECDSASigner(key.toECPrivateKey()));
-        return new Pair<>(key,signedJWT);
+        return buildTemplateAccessTokenJSONResponse(idToken.serialize());
     }
     
     /**
-     * Create a HMAC signed UserInfo response JWT.
+     * Create a plain JSON object based UserInfo response.
      * 
      * @param opIssuerId the issuer
-     * @param clientId the client_id
      * @param audience the audience
-     * @param clientSecret the client_secret
+     * @param sub the subject.
+     * @return a plain JSON object representing the UserInfo response
      * 
-     * @return the signed JWT
-     * 
-     * @throws JOSEException on error
+     * @throws JsonProcessingException on error
      */
-    public static SignedJWT createHMACSignedUserInfoJWTResponseJSON(
-            final String opIssuerId, final String clientId, final List<String> audience, final String clientSecret) 
-            throws JOSEException {
-        
-        final var header = new JWSHeader.Builder(JWSAlgorithm.HS256)
-                .type(JOSEObjectType.JWT)
-                .build();
-
-        final var payload = createBasicClaims(opIssuerId, clientId, audience);
-        
-        final var signedJWT = new SignedJWT(header, payload);
-        signedJWT.sign(new MACSigner(clientSecret));
-        return signedJWT;
-    }
-    
-    /**
-     * Create a MAC signed JWT from the given JWT claims set, using the supplied secret.
-     * 
-     * @param payload the payload to sign
-     * @param secret the secret used to create the MAC
-     * 
-     * @return the signed JWT
-     * 
-     * @throws KeyLengthException on error
-     * @throws JOSEException on error
-     */
-    public static SignedJWT createHMACSignedJWT(final JWTClaimsSet payload, final String secret) 
-            throws KeyLengthException, JOSEException {
-        final var header = new JWSHeader.Builder(JWSAlgorithm.HS256)
-                .type(JOSEObjectType.JWT)
-                .build();
-        final var signedJWT = new SignedJWT(header, payload);
-        signedJWT.sign(new MACSigner(secret));
-        return signedJWT;
+    public static String createPlainUserInfoResponseString(final String opIssuerId, final List<String> audience, 
+            final String sub) 
+            throws JsonProcessingException {
+        final JWTClaimsSet claims = createBasicUserInfoClaims(opIssuerId, audience, sub);
+        return new ObjectMapper().writeValueAsString(claims.toJSONObject());
     }
     
+
     /**
      * Create a Plain UserInfo response JWT.
      * 
@@ -328,20 +268,13 @@ public final class TestTokenHelper {
      * @return the signed JWT
      * @throws JOSEException on error
      */
-    public static PlainJWT createPlainUserInfoJWTResponseJSON(final String issuer, final String audience) 
-            throws JOSEException {
-        
-        
-        final var payload = new JWTClaimsSet.Builder()
-                .issuer(issuer)
-                .audience(audience)
-                .subject("jdoe")
-                .claim("preferred_username", "jdoe")
-                .claim("name", "J Doe")
-                .build();
+    public static PlainJWT createPlainUserInfoResponseJSON(final String issuer, final String audience) 
+            throws JOSEException {        
         
+        final var payload = createBasicUserInfoClaims(issuer, List.of(audience), "jdoe");        
         return new PlainJWT(payload);
     }
+
     
     /**
      * Build a simple OAuth2.0/OIDC Access Token JSON response using the serialized JWT.

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


More information about the commits mailing list