[java-idp-plugin-oidc-rp] branch main updated: Change Token and UserInfo response types

Phil Smart philip.smart at jisc.ac.uk
Mon Mar 13 16:29:25 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=8195d7df6ea5010977287c465f25d0a4333b45c8

The following commit(s) were added to refs/heads/main by this push:
     new 8195d7d  Change Token and UserInfo response types
8195d7d is described below

commit 8195d7df6ea5010977287c465f25d0a4333b45c8
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Mon Mar 13 16:29:22 2023 +0000

    Change Token and UserInfo response types
    
     - Change to the Nimbus version of these response types
     - Internal changes only. No change to RP behaviour
     - The change propagated to a large number of classes in the project
     - Remove old response types
     - Update tests
---
 idp-oidc-rp-api/pom.xml                            |   7 +-
 .../idp/plugin/authn/oidc/rp/OIDCRPException.java  |   3 -
 .../rp/context/AccessTokenResponseContext.java     |  47 +++--
 .../oidc/rp/context/UserInfoResponseContext.java   |   9 +-
 ...trategy.java => AccessTokenLookupStrategy.java} |  24 ++-
 .../navigate/DefaultIDTokenLookupStrategy.java     |   4 +-
 ....java => DefaultUserInfoJWTLookupStrategy.java} |  20 +-
 .../IDTokenInAccessTokenUpdateStrategy.java        |  13 +-
 .../navigate/IDTokenJOSEHeaderLookupStrategy.java  |   5 +-
 .../navigate/RequestObjectTokenUpdateStrategy.java |   3 +-
 .../navigate/SubFromIDTokenLookupFunction.java     |   4 +-
 .../TokenResponseIDTokenLookupStrategy.java        |  63 -------
 ...nfoInUserInfoResponseContextUpdateStrategy.java |   7 +-
 .../oidc/rp/messaging/JWTUserInfoResponse.java     | 112 ------------
 .../oidc/rp/messaging/PlainUserInfoResponse.java   |  81 ---------
 .../ResponseParsingException.java}                 |  44 ++---
 .../authn/oidc/rp/messaging/UserInfoResponse.java  |  87 ---------
 .../UserInfoEncryptedJWTResponseTypeCondition.java |  10 +-
 .../logic/UserInfoPlainResponseTypeCondition.java  |   6 +-
 .../UserInfoSignedJWTResponseTypeCondition.java    |   9 +-
 .../IDTokenInAccessTokenUpdateStrategyTest.java    |  92 ++++++++++
 .../oidc/rp/messaging/JWTUserInfoResponseTest.java | 119 ------------
 .../authn/oidc/rp/test}/TestTokenHelper.java       |   2 +-
 idp-oidc-rp-impl/pom.xml                           |   6 +
 .../impl/DefaultAccessTokenResponseDecoder.java    |  91 ++++++++++
 .../decoding/impl/DefaultMapResponseDecoder.java   |  68 -------
 .../impl/DefaultUserInfoResponseDecoder.java       | 121 ++++++-------
 .../impl/DefaultUserInfoRequestEncoder.java        |  15 +-
 .../impl/AbstractHttpOIDCAuthenticationAction.java |  37 +++-
 .../oidc/rp/impl/ExchangeCodeForAccessToken.java   |  22 ++-
 .../oidc/rp/impl/ExtractIDTokenFromResponse.java   | 156 ----------------
 .../authn/oidc/rp/impl/ProcessEndUserClaims.java   |  26 ++-
 .../authn/oidc/rp/impl/UserInfoEndpointLookup.java |  13 +-
 .../rp/impl/ValidateOAuthAccessTokenResponse.java  |  52 +++---
 .../rp/impl/ValidateUserInfoJSONObjectClaims.java  |  61 +++++--
 .../oidc-relying-party-authn-beans.xml             |  28 +--
 .../oidc-relying-party-authn-flow.xml              |   1 -
 ... => DefaultAccessTokenResponseDecoderTest.java} |  53 ++++--
 .../impl/DefaultUserInfoResponseDecoderTest.java   |  94 +++++++---
 .../authn/oidc/rp/impl/AbstractOIDCTest.java       |  48 +++++
 ...st.java => ExchangeCodeForAccessTokenTest.java} | 123 ++++++++++++-
 .../rp/impl/ExtractIDTokenFromResponseTest.java    | 103 -----------
 .../rp/impl/MergeUserInfoAndIDTokenClaimsTest.java | 126 -------------
 .../OIDCRPFlowFromAuthenticationResponseTest.java  |  36 ++++
 .../plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java  |  19 +-
 .../oidc/rp/impl/ProcessEndUserClaimsTest.java     |  84 +++++++--
 .../TokenResponseIDTokenLookupStrategyTest.java    |  75 --------
 .../oidc/rp/impl/UserInfoEndpointLookupTest.java   |  90 ++++++---
 .../impl/ValidateOAuthAccessTokenResponseTest.java | 201 ++++++++++-----------
 pom.xml                                            |   9 +-
 50 files changed, 1085 insertions(+), 1444 deletions(-)

diff --git a/idp-oidc-rp-api/pom.xml b/idp-oidc-rp-api/pom.xml
index 0535464..7b7d645 100644
--- a/idp-oidc-rp-api/pom.xml
+++ b/idp-oidc-rp-api/pom.xml
@@ -66,7 +66,12 @@
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-annotations</artifactId>
             <scope>provided</scope>
-        </dependency>   
+        </dependency>    
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <scope>provided</scope>
+        </dependency>    
 	</dependencies>
 	
 </project>
\ No newline at end of file
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCRPException.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCRPException.java
index 32a33e5..85fde94 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCRPException.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCRPException.java
@@ -65,7 +65,4 @@ public class OIDCRPException extends Exception{
         
     }
 
-
-
-
 }
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/AccessTokenResponseContext.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/AccessTokenResponseContext.java
index 3bc8f0f..15586f1 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/AccessTokenResponseContext.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/AccessTokenResponseContext.java
@@ -17,11 +17,13 @@
 
 package net.shibboleth.idp.plugin.authn.oidc.rp.context;
 
-import java.util.Map;
+import java.time.Instant;
 
 import javax.annotation.Nullable;
 
-import com.nimbusds.jwt.JWT;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+
+
 
 /** 
  * A context to hold an OIDC token request response. If authenticated, the Token was received over a TLS protected
@@ -29,46 +31,39 @@ import com.nimbusds.jwt.JWT;
  */
 public class AccessTokenResponseContext extends AbstractAuthenticatableOIDCContext {
     
-    /** The raw token response as a map.*/
-    @Nullable private Map<String, Object> rawTokenResponse;
+    /** The OAuth 2.0 and OIDC token response.*/
+    @Nullable private OIDCTokenResponse tokenResponse;
     
-    /** The id_token, should match that in the rawTokenResponse if one exists.*/
-    @Nullable private JWT idToken;
+    /** The time the token response was set onto this context.*/
+    @Nullable private Instant tokenResponseCreatedAt;
     
-    /**
-     * Set the raw token response as a map.
-     * 
-     * @param rawResponse the raw response.
-     */
-    public void setRawTokenResponse(@Nullable final Map<String, Object> rawResponse) {
-        rawTokenResponse = rawResponse;
-    }
     
     /**
-     * Get the raw token response map.
+     * Set the OAuth 2.0 and OIDC Token Response.
      * 
-     * @return the raw response map.
+     * @param token the token response
      */
-    @Nullable public Map<String, Object> getRawTokenResponse() {
-        return rawTokenResponse;
+    public void setTokenResponse(@Nullable final OIDCTokenResponse token) {
+        tokenResponse = token;
+        tokenResponseCreatedAt = Instant.now();
     }
     
     /**
-     * Set the id_token.
+     * Get the OAuth 2.0 and OIDC Token Response.
      * 
-     * @param token the id_token.
+     * @return the token response
      */
-    public void setIdToken(@Nullable final JWT token) {
-        idToken = token;
+    @Nullable public OIDCTokenResponse getTokenResponse() {
+        return tokenResponse;
     }
     
     /**
-     * Get the id_token.
+     * Get the time the token response was set onto this context.
      * 
-     * @return the id_token.
+     * @return the time the token response was set onto this context
      */
-    @Nullable public JWT getIdToken() {
-        return idToken;
+    @Nullable public Instant getTokenResponseCreatedAt() {
+        return tokenResponseCreatedAt;
     }
 
 }
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/UserInfoResponseContext.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/UserInfoResponseContext.java
index e218580..8b3ef21 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/UserInfoResponseContext.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/UserInfoResponseContext.java
@@ -19,21 +19,20 @@ package net.shibboleth.idp.plugin.authn.oidc.rp.context;
 
 import javax.annotation.Nullable;
 
-import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.UserInfoResponse;
-
+import com.nimbusds.openid.connect.sdk.UserInfoSuccessResponse;
 
 /** A context to hold the response from the UserInfo endpoint.*/
 public class UserInfoResponseContext  extends AbstractAuthenticatableOIDCContext {
     
     /** The UserInfo response.*/
-    @Nullable private UserInfoResponse userInfo;
+    @Nullable private UserInfoSuccessResponse userInfo;
     
     /**
      * Get the user info response.
      * 
      * @return the user info.
      */
-    @Nullable public UserInfoResponse getUserInfo() {
+    @Nullable public UserInfoSuccessResponse getUserInfo() {
         return userInfo;
     }
     
@@ -42,7 +41,7 @@ public class UserInfoResponseContext  extends AbstractAuthenticatableOIDCContext
      * 
      * @param info the user info response.
      */
-    public void setUserInfo(@Nullable final UserInfoResponse info) {
+    public void setUserInfo(@Nullable final UserInfoSuccessResponse info) {
         userInfo = info;
     }
 
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/RawTokenResponseLookupStrategy.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/AccessTokenLookupStrategy.java
similarity index 74%
rename from idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/RawTokenResponseLookupStrategy.java
rename to idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/AccessTokenLookupStrategy.java
index 100318d..4661edf 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/RawTokenResponseLookupStrategy.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/AccessTokenLookupStrategy.java
@@ -17,7 +17,6 @@
 
 package net.shibboleth.idp.plugin.authn.oidc.rp.context.navigate;
 
-import java.util.Map;
 import java.util.function.Function;
 
 import javax.annotation.Nonnull;
@@ -27,21 +26,29 @@ import javax.annotation.concurrent.ThreadSafe;
 import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.oauth2.sdk.token.AccessToken;
 
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
 import net.shibboleth.utilities.java.support.annotation.ParameterName;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 
-/** Function that extracts the raw token response from the {@link AccessTokenResponseContext}.*/
+/** Function that extracts the access_token from the {@link AccessTokenResponseContext}.*/
 @ThreadSafe
-public class RawTokenResponseLookupStrategy implements Function<ProfileRequestContext, Map<String, Object>> {
+public class AccessTokenLookupStrategy implements Function<ProfileRequestContext, AccessToken> {
+    
+    /** Logger. */
+    @Nonnull
+    private final Logger log = LoggerFactory.getLogger(AccessTokenLookupStrategy.class);
     
     /** Strategy used to locate the {@link AccessTokenResponseContext} to extract the id_token from.*/
     @Nonnull 
     private final Function<ProfileRequestContext, AccessTokenResponseContext> tokenResponseContextLookupStrategy;
     
     /** Constructor.*/
-    public RawTokenResponseLookupStrategy() {
+    public AccessTokenLookupStrategy() {
         tokenResponseContextLookupStrategy =
                 new ChildContextLookup<>(AccessTokenResponseContext.class, true).compose(
                         new InboundMessageContextLookup()); 
@@ -53,19 +60,20 @@ public class RawTokenResponseLookupStrategy implements Function<ProfileRequestCo
     *
     * @param strategy the AccessTokenResponseContext lookup strategy to use.
     */
-   public RawTokenResponseLookupStrategy(@ParameterName(name = "strategy")
+   public AccessTokenLookupStrategy(@ParameterName(name = "strategy")
            @Nonnull final Function<ProfileRequestContext, AccessTokenResponseContext> strategy) {
        tokenResponseContextLookupStrategy = 
                Constraint.isNotNull(strategy, "AccessTokenResponseContext lookup strategy can not be null");
    }
 
     @Override
-    @Nullable public  Map<String, Object> apply(@Nonnull final ProfileRequestContext prc) {
+    @Nullable public AccessToken apply(@Nonnull final ProfileRequestContext prc) {
         final AccessTokenResponseContext tokenContext = tokenResponseContextLookupStrategy.apply(prc);
-        if (tokenContext == null) {
+        if (tokenContext == null || tokenContext.getTokenResponse() == null ||
+                tokenContext.getTokenResponse().getTokens() == null) {
             return null;
         }
-        return tokenContext.getRawTokenResponse();
+        return  tokenContext.getTokenResponse().getTokens().getAccessToken();
     }
 
 }
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/DefaultIDTokenLookupStrategy.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/DefaultIDTokenLookupStrategy.java
index ccf56ed..07ed3ce 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/DefaultIDTokenLookupStrategy.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/DefaultIDTokenLookupStrategy.java
@@ -62,10 +62,10 @@ public class DefaultIDTokenLookupStrategy implements Function<ProfileRequestCont
     @Override
     @Nullable public JWT apply(@Nonnull final ProfileRequestContext prc) {
         final AccessTokenResponseContext tokenContext = tokenResponseContextLookupStrategy.apply(prc);
-        if (tokenContext == null) {
+        if (tokenContext == null || tokenContext.getTokenResponse() == null) {
             return null;
         }
-        return tokenContext.getIdToken();
+        return tokenContext.getTokenResponse().getOIDCTokens().getIDToken();
     }
 
 }
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/DefaultUserInfoTokenLookupStrategy.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/DefaultUserInfoJWTLookupStrategy.java
similarity index 77%
rename from idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/DefaultUserInfoTokenLookupStrategy.java
rename to idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/DefaultUserInfoJWTLookupStrategy.java
index e19587c..e5e0919 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/DefaultUserInfoTokenLookupStrategy.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/DefaultUserInfoJWTLookupStrategy.java
@@ -27,25 +27,25 @@ import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
 
+import com.nimbusds.common.contenttype.ContentType;
 import com.nimbusds.jwt.JWT;
 
-import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.UserInfoResponseContext;
-import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.JWTUserInfoResponse;
-import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.UserInfoResponse.UserInfoResponseType;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 
-/** Function that extracts the id_token from the {@link AccessTokenResponseContext}.*/
-//TODO could this be replaced by a ContextDataLookupFunction?
+/** 
+ * Function that extracts the UserInfo claims as a JWT from the {@link UserInfoResponseContext}. If not found, returns
+ * {@code null}.
+ */
 @ThreadSafe
-public class DefaultUserInfoTokenLookupStrategy implements Function<ProfileRequestContext, JWT> {
+public class DefaultUserInfoJWTLookupStrategy implements Function<ProfileRequestContext, JWT> {
     
     /** Strategy used to look up the {@link UserInfoResponseContext}. */
     @Nonnull private final Function<ProfileRequestContext, UserInfoResponseContext> 
             userInfoResponseContextLookupStrategy;
     
     /** Constructor.*/
-    public DefaultUserInfoTokenLookupStrategy() {
+    public DefaultUserInfoJWTLookupStrategy() {
         userInfoResponseContextLookupStrategy =
                 new ChildContextLookup<>(UserInfoResponseContext.class).compose(
                         new InboundMessageContextLookup());
@@ -57,7 +57,7 @@ public class DefaultUserInfoTokenLookupStrategy implements Function<ProfileReque
      *
      * @param strategy the UserInfo response context lookup strategy to use.
      */
-    public DefaultUserInfoTokenLookupStrategy(
+    public DefaultUserInfoJWTLookupStrategy(
             @Nonnull final Function<ProfileRequestContext, UserInfoResponseContext> strategy) {
         userInfoResponseContextLookupStrategy = 
                 Constraint.isNotNull(strategy, "UserInfoResponseContext lookup strategy can not be null");
@@ -70,10 +70,10 @@ public class DefaultUserInfoTokenLookupStrategy implements Function<ProfileReque
         final UserInfoResponseContext userInfoContext = userInfoResponseContextLookupStrategy.apply(prc);
         
         if (userInfoContext == null || userInfoContext.getUserInfo() == null ||
-                userInfoContext.getUserInfo().getType() != UserInfoResponseType.JWT) {
+                userInfoContext.getUserInfo().getEntityContentType() != ContentType.APPLICATION_JWT) {
             return null;
         }
-        return ((JWTUserInfoResponse)userInfoContext.getUserInfo()).getResponseJwt();
+        return userInfoContext.getUserInfo().getUserInfoJWT();
     }
 
 }
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/IDTokenInAccessTokenUpdateStrategy.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/IDTokenInAccessTokenUpdateStrategy.java
index 3703e56..ee86219 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/IDTokenInAccessTokenUpdateStrategy.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/IDTokenInAccessTokenUpdateStrategy.java
@@ -29,7 +29,10 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.nimbusds.jwt.JWT;
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
 
+import net.minidev.json.JSONObject;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
 import net.shibboleth.utilities.java.support.annotation.ParameterName;
 import net.shibboleth.utilities.java.support.logic.Constraint;
@@ -69,8 +72,14 @@ public class IDTokenInAccessTokenUpdateStrategy implements BiConsumer<ProfileReq
         
         final AccessTokenResponseContext context = 
                 tokenResponseContextLookupStrategy.apply(profileRequestContext);  
-        if (context != null) {
-            context.setIdToken(idToken);
+        if (context != null) {            
+            try {
+                final JSONObject jsonToken = context.getTokenResponse().toJSONObject();
+                jsonToken.put("id_token", idToken.serialize());
+                context.setTokenResponse(OIDCTokenResponse.parse(jsonToken));
+            } catch (final ParseException e) {
+                log.warn("Unable to set id_token back onto access token response", e);
+            }
         } else {
             log.warn("Unable to set id_token back onto access token response context");
         }
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/IDTokenJOSEHeaderLookupStrategy.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/IDTokenJOSEHeaderLookupStrategy.java
index fd5f495..e5e5041 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/IDTokenJOSEHeaderLookupStrategy.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/IDTokenJOSEHeaderLookupStrategy.java
@@ -63,10 +63,11 @@ public class IDTokenJOSEHeaderLookupStrategy implements Function<ProfileRequestC
     @Override
     @Nullable public JWSHeader apply(@Nonnull final ProfileRequestContext prc) {
         final AccessTokenResponseContext tokenContext = tokenResponseContextLookupStrategy.apply(prc);
-        if (tokenContext == null || tokenContext.getIdToken() == null) {
+        if (tokenContext == null || tokenContext.getTokenResponse() == null || 
+                tokenContext.getTokenResponse().getOIDCTokens().getIDToken() == null) {
             return null;
         }
-        final Header header = tokenContext.getIdToken().getHeader();
+        final Header header = tokenContext.getTokenResponse().getOIDCTokens().getIDToken().getHeader();
         if (header instanceof JWSHeader) {
             return (JWSHeader) header;
         }
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/RequestObjectTokenUpdateStrategy.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/RequestObjectTokenUpdateStrategy.java
index f7ce4bb..7a8642a 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/RequestObjectTokenUpdateStrategy.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/RequestObjectTokenUpdateStrategy.java
@@ -28,6 +28,7 @@ import org.slf4j.LoggerFactory;
 
 import com.nimbusds.jwt.JWT;
 
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.navigate.AccessTokenLookupStrategy;
 import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 
@@ -36,7 +37,7 @@ public class RequestObjectTokenUpdateStrategy implements BiConsumer<JWT, Message
     
     /** Class logger. */
     @Nonnull
-    private final Logger log = LoggerFactory.getLogger(RawTokenResponseLookupStrategy.class);
+    private final Logger log = LoggerFactory.getLogger(AccessTokenLookupStrategy.class);
     
     /** Strategy used to locate the {@link OIDCAuthenticationRequest} to sign.  */
     @Nonnull private Function<MessageContext, OIDCAuthenticationRequest> authenticationRequestLookupStrategy;
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/SubFromIDTokenLookupFunction.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/SubFromIDTokenLookupFunction.java
index e4a1d95..7c7105f 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/SubFromIDTokenLookupFunction.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/SubFromIDTokenLookupFunction.java
@@ -78,11 +78,11 @@ public class SubFromIDTokenLookupFunction implements BiFunction<ProfileRequestCo
     @Nullable
     public String apply(@Nonnull final ProfileRequestContext prc, @Nullable final JWTClaimsSet claimsSet) {
         final AccessTokenResponseContext tokenContext = tokenResponseContextLookupStrategy.apply(prc);
-        if (tokenContext == null || tokenContext.getIdToken() == null) {
+        if (tokenContext == null || tokenContext.getTokenResponse().getOIDCTokens().getIDToken() == null) {
             return null;
         }
         try {
-            final JWTClaimsSet claims = tokenContext.getIdToken().getJWTClaimsSet();
+            final JWTClaimsSet claims = tokenContext.getTokenResponse().getOIDCTokens().getIDToken().getJWTClaimsSet();
             if (claims.getSubject() != null) {
                 return claims.getSubject();
             }
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/TokenResponseIDTokenLookupStrategy.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/TokenResponseIDTokenLookupStrategy.java
deleted file mode 100644
index 0cfba49..0000000
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/TokenResponseIDTokenLookupStrategy.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.plugin.authn.oidc.rp.context.navigate;
-
-import java.util.function.Function;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.opensaml.messaging.context.MessageContext;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
-
-/** Return the raw base64 encoded id_token from the TokenResponseContext, or {@code null} if not found.*/
-public class TokenResponseIDTokenLookupStrategy implements Function<ProfileRequestContext, String> {
-    
-    /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(TokenResponseIDTokenLookupStrategy.class);
-
-    @Override
-    @Nullable public String apply(@Nonnull final ProfileRequestContext profileRequestContext) {
-        
-        final MessageContext inbound = profileRequestContext.getInboundMessageContext();
-        if (inbound == null) {
-            log.debug("Inbound message context was null, no id_token found");
-            return null;
-        }
-        final AccessTokenResponseContext tokenResponseContext = inbound.getSubcontext(AccessTokenResponseContext.class);
-        if (tokenResponseContext == null) {
-            log.debug("Token response context was null, no id_token found");
-            return null;
-        }
-        if (tokenResponseContext.getRawTokenResponse() != null) {
-            final Object idTokenObject =  tokenResponseContext.getRawTokenResponse().get("id_token");
-            if (idTokenObject instanceof String) {
-                return (String) idTokenObject;
-            }
-            log.debug("Token response was not a string, no id_token found");
-        }
-        log.debug("Raw token response was null, no id_token found");
-        return null;
-        
-    }
-
-}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/UserInfoInUserInfoResponseContextUpdateStrategy.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/UserInfoInUserInfoResponseContextUpdateStrategy.java
index d375bb1..7381e6f 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/UserInfoInUserInfoResponseContextUpdateStrategy.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/navigate/UserInfoInUserInfoResponseContextUpdateStrategy.java
@@ -29,11 +29,11 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.nimbusds.jwt.JWT;
+import com.nimbusds.openid.connect.sdk.UserInfoResponse;
+import com.nimbusds.openid.connect.sdk.UserInfoSuccessResponse;
 
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.UserInfoResponseContext;
-import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.JWTUserInfoResponse;
-import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.UserInfoResponse;
 import net.shibboleth.utilities.java.support.annotation.ParameterName;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 
@@ -77,7 +77,8 @@ public class UserInfoInUserInfoResponseContextUpdateStrategy implements  BiConsu
         final UserInfoResponseContext context = 
                 userInfoResponseContextLookupStrategy.apply(profileRequestContext);  
         if (context != null) {
-            context.setUserInfo(new JWTUserInfoResponse(token));
+            // Create a new UserInfo element with the new JWT response
+            context.setUserInfo(new UserInfoSuccessResponse(token));
         } else {
             log.warn("Unable to set UserInfo back onto response context");
         }
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/JWTUserInfoResponse.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/JWTUserInfoResponse.java
deleted file mode 100644
index baba023..0000000
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/JWTUserInfoResponse.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.plugin.authn.oidc.rp.messaging;
-
-import java.text.ParseException;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import com.nimbusds.jwt.EncryptedJWT;
-import com.nimbusds.jwt.JWT;
-import com.nimbusds.jwt.SignedJWT;
-import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
-
-import net.shibboleth.utilities.java.support.logic.Constraint;
-
-/** A UserInfo response returned as a JWT which maybe signed and or encrypted.*/
-public class JWTUserInfoResponse implements UserInfoResponse {
-    
-    /** The UserInfo claims inside a JWT.*/
-    @Nonnull private final JWT responseJwt;
-    
-    /** 
-     * The claims represented by the JWT, could be null if JWT is encrypted
-     * and has not yet been decrypted.
-     */
-    @Nullable private ClaimsSet claims;
-    
-    /**
-     * 
-     * Constructor.
-     *
-     * @param jwt the plain, signed, and or encrypted JWT representing UserInfo claims.
-     */
-    public JWTUserInfoResponse(@Nonnull final JWT jwt) {
-        responseJwt = Constraint.isNotNull(jwt, "JWT UserInfo response can not be null");
-        
-        try {
-            if (jwt.getJWTClaimsSet() != null) {
-                claims = new ClaimsSet();
-                claims.putAll(jwt.getJWTClaimsSet().getClaims());
-            }
-        } catch (final ParseException e) {
-            // do nothing, claims are null, likely encrypted JWT.
-            claims = null;
-        }
-        
-    }
-    
-    /**
-     * Get the UserInfo JWT response.
-     * 
-     * @return the UserInfo JWT response.
-     */
-    @Nonnull public JWT getResponseJwt() {
-        return responseJwt;
-    }
-
-    @Override
-    @Nullable public ClaimsSet getClaimsSet() {
-        return claims;
-    }
-
-    @Override
-    @Nullable public String getSub() {
-        if (claims != null) {
-            return claims.getStringClaim("sub");
-        }
-        return null;
-    }
-
-    @Override
-    public boolean isClaimsSetAvailable() {
-        return claims != null;
-    }
-
-    /**
-     * {@inheritDoc}
-     * 
-     * There is no easy way to determine if signed when nested inside a JWE, unless first decrypted.
-     */
-    @Override
-    public boolean isSigned() {
-        return responseJwt instanceof SignedJWT;
-    }
-
-    @Override
-    public boolean isEncrypted() {
-        return responseJwt instanceof EncryptedJWT;
-    }
-
-    @Override
-    public UserInfoResponseType getType() {
-        return UserInfoResponseType.JWT;
-    }
-
-}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/PlainUserInfoResponse.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/PlainUserInfoResponse.java
deleted file mode 100644
index 9e50da3..0000000
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/PlainUserInfoResponse.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.plugin.authn.oidc.rp.messaging;
-
-import javax.annotation.Nonnull;
-
-import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
-
-import net.shibboleth.utilities.java.support.logic.Constraint;
-
-/** A UserInfo response that was a plain JSON object with claims.*/
-public class PlainUserInfoResponse implements UserInfoResponse {
-    
-    /** The UserInfo claims inside a JWT.*/
-    @Nonnull private final ClaimsSet userInfoClaims;
-    
-    /**
-     * 
-     * Constructor.
-     *
-     * @param claims UserInfo claims.
-     */
-    public PlainUserInfoResponse(@Nonnull final ClaimsSet claims) {
-        userInfoClaims = Constraint.isNotNull(claims, "UserInfo claims can not be null");
-    }
-    
-    /**
-     * The consented UserInfo claims.
-     * 
-     * @return the claims.
-     */
-    @Nonnull public ClaimsSet getUserInfoClaims() {
-        return userInfoClaims;
-    }
-
-    @Override
-    public ClaimsSet getClaimsSet() {
-        return userInfoClaims;
-    }
-
-    @Override
-    public String getSub() {
-        return userInfoClaims.getStringClaim("sub");
-    }
-
-    @Override
-    public boolean isClaimsSetAvailable() {
-        return true;
-    }
-
-    @Override
-    public boolean isSigned() {        
-        return false;
-    }
-
-    @Override
-    public boolean isEncrypted() {        
-        return false;
-    }
-    
-    @Override
-    public UserInfoResponseType getType() {
-        return UserInfoResponseType.PLAIN;
-    }
-
-}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCRPException.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/ResponseParsingException.java
similarity index 59%
copy from idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCRPException.java
copy to idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/ResponseParsingException.java
index 32a33e5..12ae734 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCRPException.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/ResponseParsingException.java
@@ -15,57 +15,53 @@
  * limitations under the License.
  */
 
-package net.shibboleth.idp.plugin.authn.oidc.rp;
+package net.shibboleth.idp.plugin.authn.oidc.rp.messaging;
 
-import javax.annotation.concurrent.ThreadSafe;
-
-/** 
- * An exception to signal a general OIDC RelyingParty error.
- */
- at ThreadSafe
-public class OIDCRPException extends Exception{
+/** An exception that indiciates an error whilst parsing an OIDC/OAuth response.*/
+public class ResponseParsingException extends Exception {
 
     /** Serial UID. */
-    private static final long serialVersionUID = -2380145079984333546L;
+    private static final long serialVersionUID = -1380145079985783546L;
 
     /** Constructor. */
-    public OIDCRPException() {
+    public ResponseParsingException() {
         super();
-        
+
     }
 
     /**
      * Constructor.
      * 
-     * @param message exception message
-     * @param cause exception to be wrapped by this one
+     * @param message
+     *            exception message
+     * @param cause
+     *            exception to be wrapped by this one
      */
-    public OIDCRPException(final String message, final Throwable cause) {
+    public ResponseParsingException(final String message, final Throwable cause) {
         super(message, cause);
-        
+
     }
 
     /**
      * Constructor.
      * 
-     * @param message exception message
+     * @param message
+     *            exception message
      */
-    public OIDCRPException(final String message) {
+    public ResponseParsingException(final String message) {
         super(message);
-        
+
     }
 
     /**
      * Constructor.
      * 
-     * @param cause exception to be wrapped by this one
+     * @param cause
+     *            exception to be wrapped by this one
      */
-    public OIDCRPException(final Throwable cause) {
+    public ResponseParsingException(final Throwable cause) {
         super(cause);
-        
-    }
-
-
 
+    }
 
 }
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/UserInfoResponse.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/UserInfoResponse.java
deleted file mode 100644
index 4a39ca9..0000000
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/UserInfoResponse.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.plugin.authn.oidc.rp.messaging;
-
-import javax.annotation.Nullable;
-
-import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
-
-/** 
- * A User information response. Contains consent claims about a subject. A response
- * is either a plain JSON object or a JWT. 
- */
-public interface UserInfoResponse {
-    
-    /** Which type of response is this.*/
-    public enum UserInfoResponseType  {
-        /** A plain JSON Object type.*/
-        PLAIN,
-        
-        /** A signed and possibly encrypted JWT type.*/
-        JWT
-    }
-    
-    /**
-     * Get the claims for the authenticated end-user.
-     *  
-     * @return  the claims for the authenticated end-user. Can be {@literal null} if
-     *          the underlying claims are not available and {@link #isClaimsSetAvailable()} is false
-     *          e.g. an encrypted JWT which has not yet been decrypted. If {@link #isClaimsSetAvailable()} 
-     *          is true, this should never return {@literal null}.
-     */
-    @Nullable ClaimsSet getClaimsSet();
-    
-    /**
-     * Get the subject identifier for the authenticated end-user.
-     * 
-     * @return the subject identifier.
-     */
-    @Nullable String getSub();
-    
-    /**
-     * Is the claims set available for use? if so, {@link #getClaimsSet()} should
-     * never return {@literal null}.
-     * 
-     * @return true iff the claims set can be used.
-     */
-    boolean isClaimsSetAvailable();
-    
-    /**
-     * Is the response signed i.e. a JWS response.
-     * 
-     * @return true iff the response was signed.
-     */
-    boolean isSigned();
-    
-    /**
-     * Is the response encrypted i.e. a JWE response. Note, it could also
-     * be signed. 
-     * 
-     * @return true iff the response is encrypted.
-     */
-    boolean isEncrypted();
-    
-    /**
-     * Get the UserInfo response type. 
-     * 
-     * @return the response type, e.g. plain or JWT.
-     */
-    UserInfoResponseType getType();
-
-}
-
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/logic/UserInfoEncryptedJWTResponseTypeCondition.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/logic/UserInfoEncryptedJWTResponseTypeCondition.java
index 04cfca2..6d626ed 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/logic/UserInfoEncryptedJWTResponseTypeCondition.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/logic/UserInfoEncryptedJWTResponseTypeCondition.java
@@ -21,6 +21,9 @@ import javax.annotation.Nonnull;
 
 import org.opensaml.profile.context.ProfileRequestContext;
 
+import com.nimbusds.jose.JWEAlgorithm;
+import com.nimbusds.jwt.EncryptedJWT;
+
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.UserInfoResponseContext;
 
 /**
@@ -31,8 +34,11 @@ public class UserInfoEncryptedJWTResponseTypeCondition extends AbstractUserInfoR
     @Override
     protected boolean doTest(@Nonnull final ProfileRequestContext prc, 
             @Nonnull final UserInfoResponseContext context) {
-        if (context.getUserInfo() == null) {
+        if (context.getUserInfo() == null || context.getUserInfo().getUserInfoJWT() == null ||
+                context.getUserInfo().getUserInfoJWT().getHeader() == null) {
             return false;
         }
-        return context.getUserInfo().isEncrypted();
+        return (JWEAlgorithm.Family.SYMMETRIC.contains(context.getUserInfo().getUserInfoJWT().getHeader()
+                .getAlgorithm()) || JWEAlgorithm.Family.ASYMMETRIC.contains(context.getUserInfo().getUserInfoJWT()
+                        .getHeader().getAlgorithm())) && context.getUserInfo().getUserInfoJWT() instanceof EncryptedJWT;
     }}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/logic/UserInfoPlainResponseTypeCondition.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/logic/UserInfoPlainResponseTypeCondition.java
index 1c41d3c..bb3174f 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/logic/UserInfoPlainResponseTypeCondition.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/logic/UserInfoPlainResponseTypeCondition.java
@@ -21,8 +21,9 @@ import javax.annotation.Nonnull;
 
 import org.opensaml.profile.context.ProfileRequestContext;
 
+import com.nimbusds.common.contenttype.ContentType;
+
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.UserInfoResponseContext;
-import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.UserInfoResponse.UserInfoResponseType;
 
 /**
  * Condition that returns true if the UserInfo response was an plain JSON type i.e. not a signed and or encrypted JWT.
@@ -35,5 +36,6 @@ public class UserInfoPlainResponseTypeCondition extends AbstractUserInfoResponse
         if (context.getUserInfo() == null) {
             return false;
         }
-        return context.getUserInfo().getType() == UserInfoResponseType.PLAIN;
+        return context.getUserInfo().getEntityContentType() == ContentType.APPLICATION_JSON &&
+                context.getUserInfo().getUserInfo() != null;
     }}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/logic/UserInfoSignedJWTResponseTypeCondition.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/logic/UserInfoSignedJWTResponseTypeCondition.java
index d1322b8..72aa0e3 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/logic/UserInfoSignedJWTResponseTypeCondition.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/logic/UserInfoSignedJWTResponseTypeCondition.java
@@ -21,6 +21,9 @@ import javax.annotation.Nonnull;
 
 import org.opensaml.profile.context.ProfileRequestContext;
 
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jwt.SignedJWT;
+
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.UserInfoResponseContext;
 
 /**
@@ -31,8 +34,10 @@ public class UserInfoSignedJWTResponseTypeCondition extends AbstractUserInfoResp
     @Override
     protected boolean doTest(@Nonnull final ProfileRequestContext prc, 
             @Nonnull final UserInfoResponseContext context) {
-        if (context.getUserInfo() == null) {
+        if (context.getUserInfo() == null || context.getUserInfo().getUserInfoJWT() == null) {
             return false;
         }
-        return context.getUserInfo().isSigned();
+        
+        return JWSAlgorithm.Family.SIGNATURE.contains(context.getUserInfo().getUserInfoJWT().getHeader()
+                .getAlgorithm()) && context.getUserInfo().getUserInfoJWT() instanceof SignedJWT;
     }}
diff --git a/idp-oidc-rp-api/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/IDTokenInAccessTokenUpdateStrategyTest.java b/idp-oidc-rp-api/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/IDTokenInAccessTokenUpdateStrategyTest.java
new file mode 100644
index 0000000..6f67a2d
--- /dev/null
+++ b/idp-oidc-rp-api/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/IDTokenInAccessTokenUpdateStrategyTest.java
@@ -0,0 +1,92 @@
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate;
+
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.jose.EncryptionMethod;
+import com.nimbusds.jose.JWEAlgorithm;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jwt.EncryptedJWT;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+
+import net.minidev.json.JSONObject;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.navigate.IDTokenInAccessTokenUpdateStrategy;
+import net.shibboleth.idp.plugin.authn.oidc.rp.test.TestTokenHelper;
+import net.shibboleth.oidc.security.credential.DefaultClientSecretCredential;
+
+public class IDTokenInAccessTokenUpdateStrategyTest {
+    
+    /** The client_secret.*/
+    private static final String CLIENT_SECRET = "Xp2s5v8y/B?E(H+MbQeThWmYq3t6w9z$";
+    
+    /** The strategy to test.*/
+    private IDTokenInAccessTokenUpdateStrategy strategy;
+    
+    
+    /**
+     * Set an encrypted token into the context and update it with a signed token. Leaving all the other tokens in place.
+     * 
+     * @throws Exception on error.
+     */
+    @Test
+    public void testUpdateEncryptedToSigned() throws Exception {
+        
+        final String tokenResponse = 
+                TestTokenHelper.createAccessTokenResponseJSON(Collections.emptyMap(), JWSAlgorithm.HS256, 
+                        JWEAlgorithm.DIR, EncryptionMethod.A128CBC_HS256,
+                new DefaultClientSecretCredential(CLIENT_SECRET).toSigningCredential(),
+                new DefaultClientSecretCredential(CLIENT_SECRET)
+                .toEncryptionCredential(JWEAlgorithm.DIR, EncryptionMethod.A128CBC_HS256));
+        
+        final Map<String, Object> tokenResponseAsMap = new ObjectMapper().readValue(
+                tokenResponse, new TypeReference<Map<String, Object>>() {});
+
+        
+        final JWT jwtToUpdate = TestTokenHelper.createJWT(TestTokenHelper.createBasicClaims(Collections.emptyMap()),  
+                JWSAlgorithm.HS256, null, null,  
+                new DefaultClientSecretCredential(CLIENT_SECRET).toSigningCredential(), null);
+        
+        final AccessTokenResponseContext ctx = new AccessTokenResponseContext();
+        try {
+            ctx.setTokenResponse(OIDCTokenResponse.parse(new JSONObject(tokenResponseAsMap)));
+        } catch (final ParseException e) {
+            fail(e.getMessage());
+        }
+        strategy = new IDTokenInAccessTokenUpdateStrategy(prc -> ctx);
+        
+        // assert pre-condition the id token is encrypted and the other tokens exist
+        assertNotNull(ctx.getTokenResponse());
+        assertNotNull(ctx.getTokenResponse().getTokens());
+        assertNotNull(ctx.getTokenResponse().getTokens().getAccessToken());
+        assertNotNull(ctx.getTokenResponse().getTokens().getBearerAccessToken());
+        assertNotNull(ctx.getTokenResponse().getOIDCTokens());
+        assertNotNull(ctx.getTokenResponse().getOIDCTokens().getIDToken());
+        assertTrue(ctx.getTokenResponse().getOIDCTokens().getIDToken() instanceof EncryptedJWT);
+        
+        strategy.accept(new ProfileRequestContext(), jwtToUpdate);
+        
+        // assert post-condition the id token is now signed and the other tokens exist
+        assertNotNull(ctx.getTokenResponse());
+        assertNotNull(ctx.getTokenResponse().getTokens());
+        assertNotNull(ctx.getTokenResponse().getTokens().getAccessToken());
+        assertNotNull(ctx.getTokenResponse().getTokens().getBearerAccessToken());
+        assertNotNull(ctx.getTokenResponse().getOIDCTokens());
+        assertNotNull(ctx.getTokenResponse().getOIDCTokens().getIDToken());
+        assertTrue(ctx.getTokenResponse().getOIDCTokens().getIDToken() instanceof SignedJWT);
+    }
+
+}
diff --git a/idp-oidc-rp-api/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/JWTUserInfoResponseTest.java b/idp-oidc-rp-api/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/JWTUserInfoResponseTest.java
deleted file mode 100644
index db68812..0000000
--- a/idp-oidc-rp-api/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/JWTUserInfoResponseTest.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.plugin.authn.oidc.rp.messaging;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
-import static org.testng.Assert.assertTrue;
-
-import java.time.Instant;
-import java.util.Date;
-
-import org.testng.annotations.Test;
-
-import com.nimbusds.jose.EncryptionMethod;
-import com.nimbusds.jose.JOSEObjectType;
-import com.nimbusds.jose.JWEAlgorithm;
-import com.nimbusds.jose.JWEHeader;
-import com.nimbusds.jose.JWEObject;
-import com.nimbusds.jose.JWSAlgorithm;
-import com.nimbusds.jose.JWSHeader;
-import com.nimbusds.jose.Payload;
-import com.nimbusds.jose.crypto.ECDSASigner;
-import com.nimbusds.jose.crypto.RSAEncrypter;
-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.EncryptedJWT;
-import com.nimbusds.jwt.JWT;
-import com.nimbusds.jwt.JWTClaimsSet;
-import com.nimbusds.jwt.SignedJWT;
-
-/** Tests for JWTUserInfoResponse.*/
-public class JWTUserInfoResponseTest {
-    
-    @Test
-    public void testSignedJwt() 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 = new JWTClaimsSet.Builder()
-                .issuer("issuer")
-                .audience("rp-proxy")
-                .subject("jdoe")
-                .expirationTime(Date.from(Instant.now().plusSeconds(120)))
-                .build();
-        
-        final var signedJWT = new SignedJWT(header, payload);
-        signedJWT.sign(new ECDSASigner(key.toECPrivateKey()));
-        
-        final JWTUserInfoResponse response = new JWTUserInfoResponse(signedJWT);
-        assertTrue(response.isClaimsSetAvailable());
-        assertNotNull(response.getClaimsSet());
-        assertEquals(response.getClaimsSet().getStringClaim("sub"), "jdoe");
-        
-    }
-    
-    @Test
-    public void testEncryptedJwt() throws Exception {
-        
-        final var keySender = new ECKeyGenerator(Curve.P_256).keyID("1").generate();
-        final RSAKey keyRecipient = new RSAKeyGenerator(2048)
-                .keyID("2")
-                .keyUse(KeyUse.ENCRYPTION)
-                .generate();
-        
-        final var header = new JWSHeader.Builder(JWSAlgorithm.ES256)
-                .type(JOSEObjectType.JWT)
-                .keyID(keySender.getKeyID())
-                .build();
-        final var payload = new JWTClaimsSet.Builder()
-                .issuer("issuer")
-                .audience("rp-proxy")
-                .subject("jdoe")
-                .expirationTime(Date.from(Instant.now().plusSeconds(120)))
-                .build();
-        
-        final var signedJWT = new SignedJWT(header, payload);
-        signedJWT.sign(new ECDSASigner(keySender.toECPrivateKey()));
-        
-        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 JWT jwt = EncryptedJWT.parse(jweObject.serialize());
-        
-        final JWTUserInfoResponse response = new JWTUserInfoResponse(jwt);
-        assertFalse(response.isClaimsSetAvailable());
-        assertNull(response.getClaimsSet());
-        assertTrue(response.getResponseJwt().getHeader().getAlgorithm().equals(JWEAlgorithm.RSA_OAEP_256));
-       
-        
-    }
-
-}
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TestTokenHelper.java b/idp-oidc-rp-api/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/test/TestTokenHelper.java
similarity index 99%
rename from idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TestTokenHelper.java
rename to idp-oidc-rp-api/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/test/TestTokenHelper.java
index 8bc4547..823a082 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TestTokenHelper.java
+++ b/idp-oidc-rp-api/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/test/TestTokenHelper.java
@@ -16,7 +16,7 @@
  */
 
 
-package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+package net.shibboleth.idp.plugin.authn.oidc.rp.test;
 
 import java.security.interfaces.ECPrivateKey;
 import java.security.interfaces.ECPublicKey;
diff --git a/idp-oidc-rp-impl/pom.xml b/idp-oidc-rp-impl/pom.xml
index fc26154..4ecbdcd 100644
--- a/idp-oidc-rp-impl/pom.xml
+++ b/idp-oidc-rp-impl/pom.xml
@@ -169,6 +169,12 @@
             <scope>test</scope>
             <type>test-jar</type>
         </dependency>
+        <dependency>
+            <groupId>net.shibboleth.idp.plugin.authn</groupId>
+            <artifactId>idp-plugin-oidc-rp-api</artifactId>
+            <scope>test</scope>
+            <type>test-jar</type>
+        </dependency>
         <!-- Spring webflow tests require Junit4, runs in TestNG bridge -->
         <dependency>
             <groupId>junit</groupId>
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultAccessTokenResponseDecoder.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultAccessTokenResponseDecoder.java
new file mode 100644
index 0000000..f744175
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultAccessTokenResponseDecoder.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.decoding.impl;
+
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.entity.ContentType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.MediaType;
+import org.springframework.util.MimeType;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.nimbusds.oauth2.sdk.TokenErrorResponse;
+import com.nimbusds.oauth2.sdk.TokenResponse;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+
+import net.minidev.json.JSONObject;
+
+
+/** 
+ * Default access token response decoder, which converts a successful HTTP response into an 
+ * {@link OIDCTokenResponse}.
+ */
+public class DefaultAccessTokenResponseDecoder extends AbstractJSONResponseDecoderFunction<TokenResponse> {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultAccessTokenResponseDecoder.class);
+
+    @Override
+    @Nullable public TokenResponse apply(@Nonnull final HttpResponse httpResponse) {
+        
+        try {            
+            
+            if (httpResponse.getStatusLine() == null) {
+                log.warn("HTTP Response did not contain a status line");
+                return null;
+            }
+            
+            final ContentType contentType = ContentType.get(httpResponse.getEntity());
+            if (contentType == null || contentType.getMimeType() == null) {
+                log.warn("HTTP response did not contain a content-type, must contain a content-type");
+                return null;
+            }
+            
+            if (MediaType.APPLICATION_JSON.compareTo(MimeType.valueOf(contentType.getMimeType())) != 0) {
+               log.warn("Wrong content type header, expected 'application/json' found '{}'", contentType.getMimeType());
+               return null;
+            }
+            
+            final Map<String, Object> tokenResponseAsMap = getObjectMapper().readValue(
+                    httpResponse.getEntity().getContent(), new TypeReference<Map<String, Object>>() {});
+            
+            final int httpStatusCode = httpResponse.getStatusLine().getStatusCode();
+            
+            if (httpStatusCode != HttpStatus.SC_OK) {
+                return TokenErrorResponse.parse(new JSONObject(tokenResponseAsMap));                
+            } else if (httpResponse.getEntity() == null || httpResponse.getEntity().getContent() == null) {                
+                log.warn("HTTP response does not contain a message entity, nothing to decode, status '{}'", 
+                        httpStatusCode);
+                return null;
+            }            
+            return OIDCTokenResponse.parse(new JSONObject(tokenResponseAsMap));
+          
+        } catch (final Exception e) {
+            log.warn("Unable to decode response", e);
+        }
+        return null;
+    }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultMapResponseDecoder.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultMapResponseDecoder.java
deleted file mode 100644
index b1963fb..0000000
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultMapResponseDecoder.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.plugin.authn.oidc.rp.decoding.impl;
-
-import java.util.Map;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpStatus;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.nimbusds.jose.util.IOUtils;
-
-/** Default token response decoder, which converts a successful HTTP response into an Map.*/
-public class DefaultMapResponseDecoder extends AbstractJSONResponseDecoderFunction<Map<String, Object>> {
-    
-    /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultMapResponseDecoder.class);
-
-
-    //TODO we should handle the error response better? just return it and make the decision later?
-    @Override
-    @Nullable public Map<String, Object> apply(@Nonnull final HttpResponse httpResponse) {
-        
-        try {
-            final int httpStatusCode = httpResponse.getStatusLine().getStatusCode();
-            if (httpStatusCode != HttpStatus.SC_OK) {
-                log.warn("Non-ok status code ({}) returned from HTTP endpoint", httpStatusCode);
-                //dump the body for logging - if one exists
-                if (httpResponse.getEntity() != null && httpResponse.getEntity().getContent() != null) {
-                    final String errorContent = IOUtils.readInputStreamToString(httpResponse.getEntity().getContent());
-                    log.error("HTTP endpoint returned a Non-ok message of '{}'",errorContent);
-                }               
-                return null;
-            } else if (httpResponse.getEntity() == null || httpResponse.getEntity().getContent() == null) {
-                log.warn("HTTP response does not contain a message entity, nothing to decode");
-                return null;
-            }
-
-            return getObjectMapper().readValue(httpResponse.getEntity().getContent(), 
-                    new TypeReference<Map<String, Object>>() {});
-          
-        } catch (final Exception e) {
-            log.warn("Unable to decode response", e);
-        }
-        return null;
-    }
-
-}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultUserInfoResponseDecoder.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultUserInfoResponseDecoder.java
index ddeb3ad..22e80ca 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultUserInfoResponseDecoder.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultUserInfoResponseDecoder.java
@@ -24,23 +24,20 @@ import javax.annotation.Nonnull;
 import org.apache.http.Header;
 import org.apache.http.HttpResponse;
 import org.apache.http.HttpStatus;
-import org.apache.http.entity.ContentType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.http.MediaType;
-import org.springframework.util.MimeType;
 
 import com.fasterxml.jackson.core.type.TypeReference;
+import com.nimbusds.common.contenttype.ContentType;
 import com.nimbusds.jose.util.IOUtils;
-import com.nimbusds.jwt.EncryptedJWT;
 import com.nimbusds.jwt.JWT;
 import com.nimbusds.jwt.JWTParser;
-import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.openid.connect.sdk.UserInfoErrorResponse;
+import com.nimbusds.openid.connect.sdk.UserInfoResponse;
+import com.nimbusds.openid.connect.sdk.UserInfoSuccessResponse;
 import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
+import com.nimbusds.openid.connect.sdk.claims.UserInfo;
 
-import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.JWTUserInfoResponse;
-import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.PlainUserInfoResponse;
-import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.UserInfoResponse;
 
 /** 
  * Response decoder for UserInfo responses. Supports both plain JSON Object and JWT responses. Importantly,
@@ -51,9 +48,6 @@ import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.UserInfoResponse;
  */
 public class DefaultUserInfoResponseDecoder extends AbstractJSONResponseDecoderFunction<UserInfoResponse> {
     
-    /** The application/jwt media type.*/
-    @Nonnull public static final MediaType APPLICATION_JWT = new MediaType("application", "jwt"); 
-    
     /** The UserInfo response header that carries error information.*/
     @Nonnull public static final String USERINFO_ERROR_RESPONSE_HEADER = "WWW-Authenticate";
     
@@ -64,76 +58,75 @@ public class DefaultUserInfoResponseDecoder extends AbstractJSONResponseDecoderF
     @Override
     public UserInfoResponse apply(@Nonnull final HttpResponse httpResponse) {
         
+        if (httpResponse.getStatusLine() == null) {
+            log.warn("HTTP Response did not contain a status line");
+            return null;
+        }       
+        
+        final int httpStatusCode = httpResponse.getStatusLine().getStatusCode();
         try {
-            
-            final int httpStatusCode = httpResponse.getStatusLine().getStatusCode();
-            if (httpStatusCode != HttpStatus.SC_OK) {               
-            
-                //dump the body for logging - if one exists
-                if (httpResponse.getEntity() != null && httpResponse.getEntity().getContent() != null) {
-                    
-                    final String errorContent = IOUtils.readInputStreamToString(httpResponse.getEntity().getContent());
-                    log.error("Non-ok status code ({}) returned from UserInfo HTTP endpoint, error is: '{}' ",
-                            httpStatusCode, errorContent);
-                    
-                } else if (httpResponse.getHeaders(USERINFO_ERROR_RESPONSE_HEADER) != null &&
+            if (httpStatusCode != HttpStatus.SC_OK) {  
+                if (httpResponse.getHeaders(USERINFO_ERROR_RESPONSE_HEADER) != null &&
                         httpResponse.getHeaders(USERINFO_ERROR_RESPONSE_HEADER).length == 1) {
                     
                     final Header errorHeader = httpResponse.getHeaders(USERINFO_ERROR_RESPONSE_HEADER)[0];
-                    log.warn("Non-ok status code ({}) returned from UserInfo HTTP endpoint, error is: '{}'", 
-                            httpStatusCode, errorHeader);
                     
+                    if (errorHeader == null) {
+                        log.warn("HTTP status code implies error response, but no error given");
+                        return null;
+                    } else {                    
+                        return UserInfoErrorResponse.parse(errorHeader.getValue());                    
+                    }
+
                 } else {
-                    log.warn("Non-ok status code ({}) returned from UserInfo HTTP endpoint", httpStatusCode);
+                    log.warn("HTTP status code implies error response, but no error given");
+                    return null;
                 }
-                return null;
-            } else if (httpResponse.getEntity() == null || httpResponse.getEntity().getContent() == null) {
-                log.warn("HTTP response does not contain a message entity, nothing to decode");
-                return null;
-            }
-            
-            final ContentType contentType = ContentType.get(httpResponse.getEntity());
-            if (contentType == null || contentType.getMimeType() == null) {
-                log.warn("HTTP response did not contain a content-type, must contain a content-type");
-                return null;
-            }
-            
-            final String content = IOUtils.readInputStreamToString(httpResponse.getEntity().getContent());
-            
-            // Is a JWT type or plain JSON object
-            if (APPLICATION_JWT.compareTo(MimeType.valueOf(contentType.getMimeType())) == 0) {
                 
-                // This should fail to parse if plain JSON object type. Although this is less of
-                // a concern as parsing a JWT type as a plain object.
-                final JWT parsedJwt = JWTParser.parse(content);
-                
-                if (log.isDebugEnabled()) {
-                    log.debug("UserInfo response decoder parsed an {} JWT type", 
-                            parsedJwt instanceof SignedJWT ? "Signed" : 
-                                (parsedJwt instanceof EncryptedJWT ? "Encrypted" : "plain"));
+            } else {
+                // Response indicates success
+                if (httpResponse.getEntity() == null || httpResponse.getEntity().getContent() == null) {
+                    log.warn("HTTP response did not contain a response entity, nothing to decode");
+                    return null;
                 }
                 
-                return new JWTUserInfoResponse(parsedJwt);
+                if (httpResponse.getEntity().getContentType() == null) {
+                    log.warn("HTTP response did not contain a content-type, must contain a content-type");
+                    return null;
+                } 
                 
-            } else if (MediaType.APPLICATION_JSON.compareTo(MimeType.valueOf(contentType.getMimeType())) == 0){
+                final ContentType contentType = ContentType.parse(httpResponse.getEntity().getContentType().getValue());
+                if (contentType == null) {
+                    log.warn("HTTP response did not contain a valid content-type");
+                    return null;
+                }            
                 
-                // This should fail to parse if the input was a JWT type, but the header was a plain JSON Object type
-                final Map<String, Object> claims = getObjectMapper().readValue(
-                        content, new TypeReference<Map<String, Object>>() {});
-                final ClaimsSet claimsSet = new ClaimsSet();
-                claimsSet.putAll(claims);
-    
-                if (log.isTraceEnabled()) {
-                    log.trace("UserInfo response decoder parsed a plain JSON Object for subject '{}'", 
-                            claimsSet.getStringClaim("sub"));
-                }
-                return new PlainUserInfoResponse(claimsSet);
-            }            
+                // Is a JWT type or plain JSON object
+                if (ContentType.APPLICATION_JWT.matches(contentType)) {
+                    
+                    final String content = IOUtils.readInputStreamToString(httpResponse.getEntity().getContent());
+                    final JWT parsedJwt = JWTParser.parse(content);
+                    return new UserInfoSuccessResponse(parsedJwt);
+                    
+                } else if (ContentType.APPLICATION_JSON.matches(contentType)){
+                    
+                    final String content = IOUtils.readInputStreamToString(httpResponse.getEntity().getContent());
+                    final Map<String, Object> claims = getObjectMapper().readValue(
+                            content, new TypeReference<Map<String, Object>>() {});
+                    final ClaimsSet claimsSet = new ClaimsSet();
+                    claimsSet.putAll(claims);                
+                    return new UserInfoSuccessResponse(new UserInfo(claimsSet.toJSONObject()));
+                } 
+            }
           
+        } catch (final IllegalArgumentException e) {
+            log.warn("Error creating UserInfo claims set", e);
+            return null;
         } catch (final Exception e) {
             log.warn("Unable to decode UserInfo response", e);
             return null;
         }
+        log.warn("Unknown UserInfo response type");
         return null;
         
     }
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultUserInfoRequestEncoder.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultUserInfoRequestEncoder.java
index a38e8a0..af8889f 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultUserInfoRequestEncoder.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultUserInfoRequestEncoder.java
@@ -33,6 +33,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.nimbusds.jose.util.StandardCharset;
+import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
 
 import net.shibboleth.idp.plugin.authn.oidc.rp.OIDCRPException;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
@@ -118,14 +119,14 @@ public class DefaultUserInfoRequestEncoder extends AbstractRequestEncoderFunctio
     private void addBearerToken(@Nonnull final RequestBuilder rb, 
             @Nonnull final AccessTokenResponseContext responseCtx) throws OIDCRPException {
         
-        // Double check is bearer scheme
-        if ("Bearer".equals(responseCtx.getRawTokenResponse().get("token_type"))
-                && responseCtx.getRawTokenResponse().get("access_token") instanceof String) {
-            rb.addHeader("Authorization", "Bearer "+(String)responseCtx.getRawTokenResponse().get("access_token"));
-        } else {
-            throw new OIDCRPException("Access_token not found, or not Bearer type");
+        if (responseCtx.getTokenResponse() == null) {
+            throw new OIDCRPException("No access token response found");
         }
-        
+        final BearerAccessToken bearer = responseCtx.getTokenResponse().getTokens().getBearerAccessToken();
+        if (bearer == null) {
+            throw new OIDCRPException("Access token was not Bearer type");
+        }
+        rb.addHeader("Authorization", bearer.toAuthorizationHeader());        
     }
 
 
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractHttpOIDCAuthenticationAction.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractHttpOIDCAuthenticationAction.java
index 65fd057..14b34bb 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractHttpOIDCAuthenticationAction.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractHttpOIDCAuthenticationAction.java
@@ -33,6 +33,10 @@ import org.opensaml.security.httpclient.HttpClientSecuritySupport;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.nimbusds.oauth2.sdk.ErrorObject;
+import com.nimbusds.oauth2.sdk.ErrorResponse;
+import com.nimbusds.oauth2.sdk.Response;
+
 import net.shibboleth.idp.plugin.authn.oidc.rp.OIDCRPException;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.AbstractAuthenticatableOIDCContext;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
@@ -42,12 +46,13 @@ import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 
 /**
- * An abstract class for OIDC actions that make synchronous HTTP requests.
+ * An abstract class for OIDC actions that make synchronous HTTP requests and return {@link Response responses}.
  * 
  * @param <T> the response type of the object returned as a result of the request.
  */
 @ThreadSafeAfterInit
-public abstract class AbstractHttpOIDCAuthenticationAction<T> extends AbstractOIDCAuthenticationResponseAction {
+public abstract class AbstractHttpOIDCAuthenticationAction<T extends Response> 
+                                                extends AbstractOIDCAuthenticationResponseAction {
     
     /** Class logger.*/
     @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractHttpOIDCAuthenticationAction.class);
@@ -149,15 +154,16 @@ public abstract class AbstractHttpOIDCAuthenticationAction<T> extends AbstractOI
     
     /**
      * Encode the request using the supplied request encoder strategy. Execute a synchronous HTTP request
-     * and decode the response using the supplied decoder strategy. 
+     * and decode the response using the supplied decoder strategy. If an error response is returned an exception
+     * is thrown —the error object is not propagated back to the caller.
      * 
      * @param profileRequestContext the context to pull information out of for encoding the request.
      * @param authenticatableContext an authenticatable context to set the authenticated flag. Can be {@literal null} if
      *                                  no flag is supplied.
      * 
-     * @return the decoded response.
+     * @return a successful decoded response.
      * 
-     * @throws OIDCRPException on error making the request.
+     * @throws OIDCRPException on error making the request, or if an error response is returned.
      */
     @Nonnull protected T handleRequest(@Nonnull final ProfileRequestContext profileRequestContext, 
                 @Nullable final AbstractAuthenticatableOIDCContext authenticatableContext) throws OIDCRPException {
@@ -169,8 +175,9 @@ public abstract class AbstractHttpOIDCAuthenticationAction<T> extends AbstractOI
             final HttpResponse response = executeHttpRequest(request, authenticatableContext);
             final T responseObject = getHttpResponseDecoderStrategy().apply(response);
             if (responseObject == null) {
-                throw new OIDCRPException(
-                        "Unable to process HTTP response, likely error response");                
+                throw new OIDCRPException("Unable to process HTTP response");                
+            } else if (!responseObject.indicatesSuccess()) {
+                throw new OIDCRPException(formatErrorResponse(((ErrorResponse)responseObject).getErrorObject()));
             }
             return responseObject;
         } catch (final IOException e) {
@@ -178,6 +185,22 @@ public abstract class AbstractHttpOIDCAuthenticationAction<T> extends AbstractOI
             throw new OIDCRPException(e);
         }  
     }
+    
+    /**
+     * Format an error message string from the {@link ErrorObject} provided.
+     * 
+     * @param error the error object
+     * 
+     * @return a human readable error string
+     */
+    private String formatErrorResponse(@Nonnull final ErrorObject error) {
+        final StringBuilder errorMsg = new StringBuilder();
+        errorMsg.append("Error response, HTTP status code '")
+            .append(error.getHTTPStatusCode())
+            .append("', error code '").append(error.getCode())
+            .append("', description: ").append(error.getDescription());
+        return  errorMsg.toString();
+    }
 
 
     /**
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExchangeCodeForAccessToken.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExchangeCodeForAccessToken.java
index 32bbb7a..a18dd80 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExchangeCodeForAccessToken.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExchangeCodeForAccessToken.java
@@ -17,7 +17,6 @@
 
 package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
 
-import java.util.Map;
 import java.util.function.Function;
 
 import javax.annotation.Nonnull;
@@ -30,6 +29,9 @@ import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.nimbusds.oauth2.sdk.TokenResponse;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+
 import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.OIDCRPException;
@@ -49,7 +51,7 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
  * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class, false) != null</pre>
  * @post Add the Access Token Response to the {@link AccessTokenResponseContext}.
  */
-public class ExchangeCodeForAccessToken extends AbstractHttpOIDCAuthenticationAction<Map<String, Object>> {
+public class ExchangeCodeForAccessToken extends AbstractHttpOIDCAuthenticationAction<TokenResponse> {
     
     /** Class logger.*/
     @Nonnull private final Logger log = LoggerFactory.getLogger(ExchangeCodeForAccessToken.class);
@@ -94,14 +96,18 @@ public class ExchangeCodeForAccessToken extends AbstractHttpOIDCAuthenticationAc
               ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
               return;
           }
-          
           try {   
-              final Map<String, Object> responseObject = handleRequest(profileRequestContext, responseCtx);         
-              responseCtx.setRawTokenResponse(responseObject);
-              log.trace("{}: Token request response '{}'",getLogPrefix(), responseObject);
-
+              final TokenResponse responseObject = handleRequest(profileRequestContext, responseCtx);
+              if (responseObject instanceof OIDCTokenResponse) {
+                  responseCtx.setTokenResponse((OIDCTokenResponse)responseObject);
+                  log.trace("{} Token request response was successful '{}'",getLogPrefix(), 
+                          ((OIDCTokenResponse)responseObject).indicatesSuccess());
+              } else {
+                  throw new OIDCRPException("Token response was not of the expected format, expected OIDC token "
+                          + "response, got " + responseObject.getClass().getSimpleName());
+              }           
           } catch (final OIDCRPException e) {
-              log.error("{} Failed to exchange authorisation code for token result: {}",getLogPrefix(), e.getMessage());
+              log.error("{} Failed to exchange authorisation code for token result",getLogPrefix(), e);
               ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.AUTHN_EXCEPTION);
           }          
     }
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExtractIDTokenFromResponse.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExtractIDTokenFromResponse.java
deleted file mode 100644
index 8a62c6e..0000000
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExtractIDTokenFromResponse.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
-
-import java.text.ParseException;
-import java.util.function.Function;
-
-import javax.annotation.Nonnull;
-
-import org.opensaml.messaging.context.navigate.ChildContextLookup;
-import org.opensaml.profile.action.ActionSupport;
-import org.opensaml.profile.action.EventIds;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.nimbusds.jose.JOSEObject;
-import com.nimbusds.jose.JWEObject;
-import com.nimbusds.jose.JWSObject;
-import com.nimbusds.jose.PlainObject;
-import com.nimbusds.jwt.EncryptedJWT;
-import com.nimbusds.jwt.SignedJWT;
-
-import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
-import net.shibboleth.idp.profile.AbstractProfileAction;
-import net.shibboleth.oidc.profile.core.OidcEventIds;
-import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
-import net.shibboleth.utilities.java.support.component.ComponentSupport;
-import net.shibboleth.utilities.java.support.logic.Constraint;
-
-/** 
- * Action that extracts an id_token from the access token response and sets it onto the 
- * {@link AccessTokenResponseContext}. The id_token must either be signed, or signed and encrypted,
- * plain id_tokens are not supported — this helps prevent 'alg=none' header manipulation.
- * 
- * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
- * @event {@link EventIds#INVALID_PROFILE_CTX}
- * @event {@link OidcEventIds#INVALID_ID_TOKEN}
- * @post id_token added to {@link AccessTokenResponseContext}.
- */
-public class ExtractIDTokenFromResponse extends AbstractProfileAction {
-
-    /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(ExtractIDTokenFromResponse.class);
-    
-    /** Function that looks up a raw id_token value as a base64 encoded string.*/
-    @NonnullAfterInit private Function<ProfileRequestContext, String> rawIdTokenLookupStrategy;
-    
-    /** The token response context to add the decoded id_token too.*/
-    @NonnullAfterInit private AccessTokenResponseContext responseCtx;
-    
-    /** Strategy used to look up the {@link AccessTokenResponseContext} to set the parameters for. */
-    @Nonnull private Function<ProfileRequestContext, AccessTokenResponseContext> 
-            tokenResponseContextLookupStrategy;
-    
-    /** Constructor.*/
-    public ExtractIDTokenFromResponse() {
-        tokenResponseContextLookupStrategy =
-                new ChildContextLookup<>(AccessTokenResponseContext.class).compose(
-                        new InboundMessageContextLookup());
-    }
-    
-    /**
-     * Set the strategy used to look up a {@link AccessTokenResponseContext}.
-     * 
-     * @param strategy lookup strategy
-     */
-    public void setTokenResponseContextLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext, AccessTokenResponseContext> strategy) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
-        tokenResponseContextLookupStrategy = Constraint.isNotNull(strategy,
-                "TokenResponseContext lookup strategy cannot be null");
-    }
-    
-    @Override
-    protected void doInitialize() throws ComponentInitializationException {
-        super.doInitialize();
-        
-        if (rawIdTokenLookupStrategy == null) {
-            throw new ComponentInitializationException("RawIdTokenLookupStrategy cannot be null");
-        }
-    }
-    
-    @Override 
-    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-        
-        responseCtx = tokenResponseContextLookupStrategy.apply(profileRequestContext);
-        if (responseCtx == null) {
-            log.debug("{} No TokenResponseContext returned by lookup strategy", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
-            return false;
-        }
-        return true;
-    }
-        
-    /**
-     * Set the strategy used to lookup a base64 encoded JWT from the profile request context.
-     *
-     * @param strategy the strategy to use.
-     */
-    public void setRawIdTokenLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext, String> strategy) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
-        rawIdTokenLookupStrategy = Constraint.isNotNull(strategy,"Raw id_token lookup strategy can not be null");
-    }
-    
-    @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-        
-        final String rawIdTokenValue = rawIdTokenLookupStrategy.apply(profileRequestContext);
-        if (rawIdTokenValue == null) {
-            log.warn("{} Unable to extract id_token from access token response",getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext,OidcEventIds.INVALID_ID_TOKEN);
-            return;
-        }
-        log.trace("{} Encoded id_token is: '{}'",getLogPrefix(), rawIdTokenValue);
-        try {
-            final JOSEObject joseObject = JOSEObject.parse(rawIdTokenValue);
-            if (joseObject instanceof PlainObject) {
-                log.error("{} Plain id_token not supported", getLogPrefix());
-                ActionSupport.buildEvent(profileRequestContext,OidcEventIds.INVALID_ID_TOKEN);
-                return;
-                
-            } else if (joseObject instanceof JWSObject) {
-                log.trace("{} Signed id_token found", getLogPrefix());
-                responseCtx.setIdToken(SignedJWT.parse(rawIdTokenValue));
-                
-            } else if (joseObject instanceof JWEObject) {
-                log.trace("{} Encrypted id_token found", getLogPrefix());
-                responseCtx.setIdToken(EncryptedJWT.parse(rawIdTokenValue));
-            }            
-        } catch (final ParseException e) {
-            log.warn("{} Unable to parse id_token",getLogPrefix(), e);
-            ActionSupport.buildEvent(profileRequestContext,OidcEventIds.INVALID_ID_TOKEN);
-            return;            
-        }        
-    }
-}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ProcessEndUserClaims.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ProcessEndUserClaims.java
index 2b04d9e..cbb760c 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ProcessEndUserClaims.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ProcessEndUserClaims.java
@@ -33,6 +33,7 @@ import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.nimbusds.common.contenttype.ContentType;
 import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
 
@@ -206,24 +207,37 @@ public class ProcessEndUserClaims extends AbstractOIDCAuthenticationResponseActi
             log.trace("{} No UserInfo response context returned by lookup strategy, creating empty "
                     + "UserInfo claims", getLogPrefix());
             userInfoClaims = new ClaimsSet();
-        } else{
-            userInfoClaims = userInfoCtx.getUserInfo().getClaimsSet();
+        } else if (userInfoCtx.getUserInfo().getEntityContentType() == ContentType.APPLICATION_JSON) {
+            userInfoClaims = userInfoCtx.getUserInfo().getUserInfo();
+        } else if (userInfoCtx.getUserInfo().getEntityContentType() == ContentType.APPLICATION_JWT) {            
+            try {
+                final ClaimsSet claims = new ClaimsSet();
+                claims.putAll(userInfoCtx.getUserInfo().getUserInfoJWT().getJWTClaimsSet().getClaims());
+                userInfoClaims = claims;
+            } catch (final ParseException e) {
+                log.warn("Unable to extract UserInfo claims from JWT claimsset", e);
+                return false;
+            }            
+        } else {
+            log.warn("Unable to extract UserInfo claims, unknown entity content type");
+            return false;
         }
         
         final AccessTokenResponseContext tokenResponseCtx = 
                 accessTokenResponseContextLookupStrategy.apply(profileRequestContext);
-        if (tokenResponseCtx == null) {
-            log.debug("{} No AccessTokenResponseContext returned by lookup strategy", getLogPrefix());
+        if (tokenResponseCtx == null || tokenResponseCtx.getTokenResponse() == null) {
+            log.debug("{} No AccessTokenResponseContext or Access Token returned by lookup strategy", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
             return false;
         }
-        if (tokenResponseCtx.getIdToken() == null) {
+        if (tokenResponseCtx.getTokenResponse().getOIDCTokens() == null || 
+                tokenResponseCtx.getTokenResponse().getOIDCTokens().getIDToken() == null) {
             log.debug("{} AccessTokenResponseContext did not contain an id_token", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
             return false;
         }
         try {
-            idTokenClaims = tokenResponseCtx.getIdToken().getJWTClaimsSet();
+            idTokenClaims = tokenResponseCtx.getTokenResponse().getOIDCTokens().getIDToken().getJWTClaimsSet();
             if (idTokenClaims == null) {
                 log.debug("{} AccessTokenResponseContext did not contain an id_token with accessible claims, "
                         + "possibly still encrypted", 
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/UserInfoEndpointLookup.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/UserInfoEndpointLookup.java
index d6d36e2..246ae81 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/UserInfoEndpointLookup.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/UserInfoEndpointLookup.java
@@ -29,12 +29,14 @@ import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.nimbusds.openid.connect.sdk.UserInfoResponse;
+
 import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.OIDCRPException;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.UserInfoResponseContext;
-import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.UserInfoResponse;
+import net.shibboleth.oidc.profile.core.OidcEventIds;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 
@@ -91,11 +93,14 @@ public class UserInfoEndpointLookup extends AbstractHttpOIDCAuthenticationAction
               return;
           }
           
-          try {              
-              userInfoCtx.setUserInfo(handleRequest(profileRequestContext, userInfoCtx));
+          try {          
+              final UserInfoResponse response = handleRequest(profileRequestContext, userInfoCtx);
+              if (response.indicatesSuccess()) {
+                  userInfoCtx.setUserInfo(response.toSuccessResponse());
+              }
           } catch (final OIDCRPException e) {
               log.error("{} Unable to return claims from UserInfo endpoint",getLogPrefix(),e);
-              ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.AUTHN_EXCEPTION);
+              ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_USERINFO_CLAIMS);
           }   
     }
     
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOAuthAccessTokenResponse.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOAuthAccessTokenResponse.java
index 6b820ad..0a19967 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOAuthAccessTokenResponse.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOAuthAccessTokenResponse.java
@@ -17,7 +17,8 @@
 
 package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
 
-import java.util.Map;
+import java.time.Duration;
+import java.time.Instant;
 import java.util.function.Function;
 
 import javax.annotation.Nonnull;
@@ -30,6 +31,8 @@ import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+
 import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
 import net.shibboleth.oidc.profile.core.OidcEventIds;
@@ -41,6 +44,8 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
  * Validation action that validates the OAuth Access Token Response against RFC 6749 section 5.1
  * and OpenID Connect Core 1.0 section 3.1.3.3.
  * 
+ * <p>Validation will also be performed in the response decoders when creating the access token.</p>
+ * 
  * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
  * @event {@link EventIds#INVALID_PROFILE_CTX}
  * @event {@link OidcEventIds#INVALID_ACCESS_TOKEN}
@@ -84,53 +89,48 @@ public class ValidateOAuthAccessTokenResponse extends AbstractOIDCAuthentication
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
             return;
         }
-        final Map<String, Object> rawTokenResponse = responseCtx.getRawTokenResponse();
-        if (rawTokenResponse == null) {
-            log.debug("{} No access token response found, response invalid", getLogPrefix());
+        final OIDCTokenResponse tokenResponse = responseCtx.getTokenResponse();
+        if (tokenResponse == null) {
+            log.warn("{} No Access Token response found, response invalid", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_ACCESS_TOKEN);
             return;
         }
         // Look for an error response. This may never get here depending on the upflow response decoder used.
-        if (rawTokenResponse.containsKey("error")) {
-            log.debug("{} Error response found instead of access token: '{}'", getLogPrefix(),
-                    rawTokenResponse.get("error"));
+        if (!tokenResponse.indicatesSuccess()) {
+            if (log.isWarnEnabled()) {
+                log.warn("{} Error response found instead of access token response", getLogPrefix());
+            }
             ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_ACCESS_TOKEN);
             return;
         }
-        if (!rawTokenResponse.containsKey("id_token")) {
+        
+        if (tokenResponse.getOIDCTokens().getIDToken() == null) {
             log.warn("{} Access token response is invalid, no id_token found", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_ACCESS_TOKEN);
             return;
         }
         // Otherwise check is valid success response
-        if (!rawTokenResponse.containsKey("access_token")) {
+        if (tokenResponse.getTokens().getAccessToken() == null) {
             log.warn("{} Access token response is invalid, no access_token found", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_ACCESS_TOKEN);
             return;
         }
-        if (!rawTokenResponse.containsKey("token_type")) {
-            log.warn("{} Access token response is invalid, no token_type found", getLogPrefix());
+        if (tokenResponse.getTokens().getBearerAccessToken() == null) {
+            log.warn("{} Access token response is invalid, bearer token_type required", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_ACCESS_TOKEN);
             return;
-        } else {
-            // Only support Bearer type
-            final Object tokenTypeObject = rawTokenResponse.get("token_type");
-            if (tokenTypeObject instanceof String) {
-                if (!"Bearer".equals(tokenTypeObject)) {
-                    log.warn("{} Access token response is invalid, bearer token_type required", getLogPrefix());
-                    ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_ACCESS_TOKEN);
-                    return;
-                }
-            } else {
-                log.warn("{} Access token response is invalid, bearer token_type required", getLogPrefix());
+        } 
+        if (tokenResponse.getTokens().getAccessToken().getLifetime() != 0 && 
+                responseCtx.getTokenResponseCreatedAt() != null) {
+            final Instant now = Instant.now();
+            final Instant expiresAt = responseCtx.getTokenResponseCreatedAt().plus(Duration.ofSeconds(
+                    tokenResponse.getTokens().getAccessToken().getLifetime()));
+            if (expiresAt.isBefore(now)) {
+                log.warn("{} Access token response is invalid, token has expired", getLogPrefix());
                 ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_ACCESS_TOKEN);
                 return;
             }
         }
-        // Is recommended, but we do not use it
-        if (!rawTokenResponse.containsKey("expires_in")) {
-            log.trace("{} Expires_in not found in access token, is recomended ", getLogPrefix());
-        }
 
         
     }
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateUserInfoJSONObjectClaims.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateUserInfoJSONObjectClaims.java
index 1f22a5a..ec4e78a 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateUserInfoJSONObjectClaims.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateUserInfoJSONObjectClaims.java
@@ -31,13 +31,16 @@ import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.nimbusds.common.contenttype.ContentType;
 import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.openid.connect.sdk.UserInfoResponse;
+import com.nimbusds.openid.connect.sdk.UserInfoSuccessResponse;
+import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
 
 import net.shibboleth.idp.authn.AbstractAuthenticationAction;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.UserInfoResponseContext;
-import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.UserInfoResponse;
 import net.shibboleth.oidc.profile.core.OidcEventIds;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
@@ -122,13 +125,13 @@ public class ValidateUserInfoJSONObjectClaims extends AbstractAuthenticationActi
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
             return false;
         }
-        if (tokenResponseCtx.getIdToken() == null) {
+        if (tokenResponseCtx.getTokenResponse().getOIDCTokens().getIDToken() == null) {
             log.debug("{} AccessTokenResponseContext did not contain an id_token", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
             return false;
         }
         try {
-            idTokenClaims = tokenResponseCtx.getIdToken().getJWTClaimsSet();
+            idTokenClaims = tokenResponseCtx.getTokenResponse().getOIDCTokens().getIDToken().getJWTClaimsSet();
             if (idTokenClaims == null) {
                 log.debug("{} AccessTokenResponseContext did not contain an id_token with accessible claims, "
                         + "possibly still encrypted", 
@@ -148,14 +151,14 @@ public class ValidateUserInfoJSONObjectClaims extends AbstractAuthenticationActi
             @Nonnull final AuthenticationContext authenticationContext) { 
         
           log.trace("{} Validating UserInfo JSON Object claims", getLogPrefix());          
-         
-          final UserInfoResponse response = userInfoCtx.getUserInfo();
-          if (!response.isClaimsSetAvailable()) {
-              log.debug("{} UserInfo claims are not available, check response is not still encrypted", getLogPrefix());
+
+          final ClaimsSet claims = resolveClaimsSet(userInfoCtx.getUserInfo());
+          if (claims == null) {
+              log.debug("{} UserInfo claims can not be resolved", getLogPrefix());
               ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_USERINFO_CLAIMS);
               return;
           }
-          final String subFromUserInfo = response.getClaimsSet().getStringClaim("sub");
+          final String subFromUserInfo = claims.getStringClaim("sub");
           if (subFromUserInfo == null) {
               log.debug("{} UserInfo claims does not contain the 'sub' claim, it must", getLogPrefix());
               ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_USERINFO_CLAIMS);
@@ -163,14 +166,50 @@ public class ValidateUserInfoJSONObjectClaims extends AbstractAuthenticationActi
           }
           // sub must match to id_token sub
           if (!idTokenClaims.getSubject().equals(subFromUserInfo)){
-              log.debug("{} UserInfo claims about subject '{}' but id_token about subject '{}', mismatch", 
+              log.error("{} UserInfo claims about subject '{}' but id_token about subject '{}', mismatch", 
                       getLogPrefix(), subFromUserInfo, idTokenClaims.getSubject());
               ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_USERINFO_CLAIMS);
               return;
           }          
-          log.debug("{} UserInfo claims are valid for '{}'", getLogPrefix(), 
-                  response.getClaimsSet().getStringClaim("sub"));
+          log.debug("{} UserInfo claims are valid for '{}'", getLogPrefix(), claims.getStringClaim("sub"));
          
     }
+    
+    /**
+     * Resolve the UserInfo claims from the user info response. If JWT type, extract from the JWT claims. If plain JSON
+     * object type, resolve directly from the UserInfo claims set. 
+     * 
+     * @param response the UserInfo response
+     * 
+     * @return the UserInfo claims. Return {@code null} if not found, wrong response type, or there was a parsing exception
+     */
+    @Nullable private ClaimsSet resolveClaimsSet(@Nonnull final UserInfoResponse response) {
+        if (!response.indicatesSuccess()) {
+            return null;
+        }
+        final UserInfoSuccessResponse successResponse = response.toSuccessResponse();
+        if (successResponse.getEntityContentType() == ContentType.APPLICATION_JWT) {
+            try {
+                final JWTClaimsSet claims = successResponse.getUserInfoJWT().getJWTClaimsSet();
+                if (claims == null) {
+                    log.debug("{} UserInfo claims are not available, check response is not still encrypted", 
+                            getLogPrefix());
+                    return null;
+                }
+                final ClaimsSet claimsConverted = new ClaimsSet();
+                claimsConverted.putAll(claims.toJSONObject());
+                return claimsConverted;
+            } catch (final ParseException e) {
+                log.warn("{} UserInfo claims could not be extracted from the JWT claims set", getLogPrefix(), e);
+                return null;
+            }
+            
+        } else if (successResponse.getEntityContentType() == ContentType.APPLICATION_JSON) {
+            return successResponse.getUserInfo();
+        } else {
+            log.debug("{} UserInfo claims are not available, unknown entity content type", getLogPrefix());
+            return null;
+        }
+    }
 
 }
diff --git a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml
index 2295310..cd3c474 100644
--- a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml
+++ b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml
@@ -384,17 +384,9 @@
         p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
         p:authenticationContextLookupStrategy-ref="ParentAuthenticiationContextLookup" />
 
-    <bean id="ExtractIDTokenFromTokenResponse" scope="prototype"
-        class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ExtractIDTokenFromResponse"
-        p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
-        p:rawIdTokenLookupStrategy-ref="TokenResponseIDTokenLookupStrategy" />
-
-    <bean id="TokenResponseIDTokenLookupStrategy"
-        class="net.shibboleth.idp.plugin.authn.oidc.rp.context.navigate.TokenResponseIDTokenLookupStrategy" />
-
     <!-- could these be singletons? -->
     <bean id="shibboleth.authn.oidc.rp.DefaultTokenResponseDecoder" scope="prototype"
-        class="net.shibboleth.idp.plugin.authn.oidc.rp.decoding.impl.DefaultMapResponseDecoder"
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.decoding.impl.DefaultAccessTokenResponseDecoder"
         p:objectMapper-ref="shibboleth.authn.oidc.rp.JSONObjectMapper" />
 
     <bean id="shibboleth.authn.oidc.rp.DefaultAuthCodeTokenResponseEncoder" scope="prototype"
@@ -449,7 +441,7 @@
             <bean class="net.shibboleth.idp.profile.context.navigate.SpringExpressionContextLookupFunction"
                 c:_0="#{ T(org.opensaml.profile.context.ProfileRequestContext) }"
                 c:outputType="#{T(com.nimbusds.jwt.EncryptedJWT)}"
-                c:expression="#input.getInboundMessageContext().getSubcontext(T(net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext)).getIdToken()" />
+                c:expression="#input.getInboundMessageContext().getSubcontext(T(net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext)).getTokenResponse().getOIDCTokens().getIDToken()" />
         </property>
         <property name="jwtUpdateStrategy">
             <bean class="net.shibboleth.idp.plugin.authn.oidc.rp.context.navigate.IDTokenInAccessTokenUpdateStrategy" />
@@ -493,7 +485,7 @@
                                     class="net.shibboleth.idp.profile.context.navigate.SpringExpressionContextLookupFunction"
                                     c:_0="#{ T(org.opensaml.messaging.context.MessageContext) }"
                                     c:outputType="#{T(com.nimbusds.jwt.SignedJWT)}"
-                                    c:expression="#input.getSubcontext(T(net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext)).getIdToken()" />
+                                    c:expression="#input.getSubcontext(T(net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext)).getTokenResponse().getOIDCTokens().getIDToken()" />
                             </property>
                             <property name="providerMetadataLookupStrategy">
                                 <ref bean="shibboleth.ChildLookup.OIDCProviderMetadataFromPeerEntityContext" />
@@ -638,13 +630,13 @@
         
     <bean id="AtHashValidator" class="net.shibboleth.oidc.security.jwt.claims.impl.AccessTokenHashValidator"
         p:allowMissing="%{idp.authn.oidc.rp.client.tokenresponse.allowMissingAtHash:true}"
-        p:tokenResponseLookupStrategy="#{getObject('shibboleth.authn.oidc.rp.jwt.TokenResponseLookupStrategy') ?: 
-                                getObject('shibboleth.authn.oidc.rp.jwt.DefaultTokenResponseLookupStrategy')}"
+        p:accessTokenLookupStrategy="#{getObject('shibboleth.authn.oidc.rp.jwt.AccessTokenLookupStrategy') ?: 
+                                getObject('shibboleth.authn.oidc.rp.jwt.DefaultAccessTokenLookupStrategy')}"
         p:joseHeaderLookupStrategy="#{getObject('shibboleth.authn.oidc.rp.jwt.IDTokenJOSEHeaderLookupStrategy') ?: 
                                 getObject('shibboleth.authn.oidc.rp.jwt.DefaultIDTokenJOSEHeaderLookupStrategy')}"/>
                                 
-    <bean id="shibboleth.authn.oidc.rp.jwt.DefaultTokenResponseLookupStrategy" 
-        class="net.shibboleth.idp.plugin.authn.oidc.rp.context.navigate.RawTokenResponseLookupStrategy"/>
+    <bean id="shibboleth.authn.oidc.rp.jwt.DefaultAccessTokenLookupStrategy" 
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.context.navigate.AccessTokenLookupStrategy"/>
     
     <bean id="shibboleth.authn.oidc.rp.jwt.DefaultIDTokenJOSEHeaderLookupStrategy" 
         class="net.shibboleth.idp.plugin.authn.oidc.rp.context.navigate.IDTokenJOSEHeaderLookupStrategy"/>
@@ -719,7 +711,7 @@
             <bean class="net.shibboleth.idp.profile.context.navigate.SpringExpressionContextLookupFunction"
                 c:_0="#{ T(org.opensaml.profile.context.ProfileRequestContext) }"
                 c:outputType="#{T(com.nimbusds.jwt.EncryptedJWT)}"
-                c:expression="#input.getInboundMessageContext().getSubcontext(T(net.shibboleth.idp.plugin.authn.oidc.rp.context.UserInfoResponseContext)).getUserInfo().getResponseJwt()" />
+                c:expression="#input.getInboundMessageContext().getSubcontext(T(net.shibboleth.idp.plugin.authn.oidc.rp.context.UserInfoResponseContext)).getUserInfo().getUserInfoJWT()" />
         </property>
         <property name="jwtUpdateStrategy">
             <bean class="net.shibboleth.idp.plugin.authn.oidc.rp.context.navigate.UserInfoInUserInfoResponseContextUpdateStrategy" />
@@ -765,7 +757,7 @@
                                     class="net.shibboleth.idp.profile.context.navigate.SpringExpressionContextLookupFunction"
                                     c:_0="#{ T(org.opensaml.messaging.context.MessageContext) }"
                                     c:outputType="#{T(com.nimbusds.jwt.SignedJWT)}"
-                                    c:expression="#input.getSubcontext(T(net.shibboleth.idp.plugin.authn.oidc.rp.context.UserInfoResponseContext)).getUserInfo().getResponseJwt()" />
+                                    c:expression="#input.getSubcontext(T(net.shibboleth.idp.plugin.authn.oidc.rp.context.UserInfoResponseContext)).getUserInfo().getUserInfoJWT()" />
                             </property>
                             <property name="providerMetadataLookupStrategy">
                                 <ref bean="shibboleth.ChildLookup.OIDCProviderMetadataFromPeerEntityContext" />
@@ -782,7 +774,7 @@
     </bean>
 
     <bean id="shibboleth.authn.oidc.rp.DefaultUserInfoTokenLookupStrategy"
-        class="net.shibboleth.idp.plugin.authn.oidc.rp.context.navigate.DefaultUserInfoTokenLookupStrategy" />
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.context.navigate.DefaultUserInfoJWTLookupStrategy" />
 
     <bean id="ValidateUserInfoTokenClaims" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateTokenClaims"
diff --git a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml
index 0344758..29aa18d 100644
--- a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml
+++ b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml
@@ -115,7 +115,6 @@
         <evaluate expression="InitializeOAuth2ClientAuthenticationContext" />
         <evaluate expression="ExchangeCodeForAccessToken" />
         <evaluate expression="ValidateOAuthAccessTokenResponse" />
-        <evaluate expression="ExtractIDTokenFromTokenResponse" />
         <evaluate expression="PopulateIDTokenDecryptionParameters" />
         <evaluate expression="DecryptIDTokenJWE" />
         <!--Validation of the JWT signature is optional if TLS server validation was performed -->
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultMapResponseDecoderTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultAccessTokenResponseDecoderTest.java
similarity index 62%
rename from idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultMapResponseDecoderTest.java
rename to idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultAccessTokenResponseDecoderTest.java
index 7a81c8c..5f537ae 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultMapResponseDecoderTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultAccessTokenResponseDecoderTest.java
@@ -18,35 +18,42 @@
 package net.shibboleth.idp.plugin.authn.oidc.rp.decoding.impl;
 
 import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
 
-import java.io.ByteArrayInputStream;
 import java.io.IOException;
-import java.util.Map;
 
-import org.apache.http.HttpEntity;
 import org.apache.http.HttpResponse;
 import org.apache.http.StatusLine;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
 import org.mockito.Mockito;
+import org.springframework.http.MediaType;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.oauth2.sdk.AccessTokenResponse;
+import com.nimbusds.oauth2.sdk.ErrorResponse;
+import com.nimbusds.oauth2.sdk.TokenResponse;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
 
 import net.shibboleth.idp.plugin.authn.oidc.rp.impl.AbstractOIDCTest;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 
-/** Tests for the DefaultTokenResponseDecoder.*/
-public class DefaultMapResponseDecoderTest extends AbstractOIDCTest {
+/** Tests for the DefaultAccessTokenResponseDecoder.*/
+public class DefaultAccessTokenResponseDecoderTest extends AbstractOIDCTest {
     
     /** The encoder to test.*/
-    private DefaultMapResponseDecoder decoder;
+    private DefaultAccessTokenResponseDecoder decoder;
     
+    @Override
     @BeforeMethod
     public void setup() throws Exception {
         super.setup();
-        decoder = new DefaultMapResponseDecoder();
+        decoder = new DefaultAccessTokenResponseDecoder();
         decoder.setObjectMapper(new ObjectMapper());
     }
     
@@ -55,18 +62,20 @@ public class DefaultMapResponseDecoderTest extends AbstractOIDCTest {
     public void testEncoder_Success() throws ComponentInitializationException, UnsupportedOperationException, IOException {
         decoder.initialize();
         final HttpResponse response = Mockito.mock(HttpResponse.class);
-        final HttpEntity mockHttpEntity = Mockito.mock(HttpEntity.class);
         final StatusLine mockStatusLine = Mockito.mock(StatusLine.class);
         when(mockStatusLine.getStatusCode()).thenReturn(200);
         when(response.getStatusLine()).thenReturn(mockStatusLine);
-        when(response.getEntity()).thenReturn(mockHttpEntity);
-        when(mockHttpEntity.getContent()).thenReturn(new ByteArrayInputStream(ACCESS_TOKEN_RESPONSE.getBytes()));
-        final Map<String, Object> decodedResponse = decoder.apply(response);
+        Mockito.when(response.getEntity()).thenReturn(new StringEntity(ACCESS_TOKEN_RESPONSE, 
+                ContentType.parse(MediaType.APPLICATION_JSON_VALUE)));
+
+        final TokenResponse decodedResponse = decoder.apply(response);        
         assertNotNull(decodedResponse);
-        assertNotNull(decodedResponse.get("id_token"));
-        assertNotNull(decodedResponse.get("access_token"));
-        assertNotNull(decodedResponse.get("token_type"));
-        assertNotNull(decodedResponse.get("scope"));
+        assertTrue(decodedResponse.indicatesSuccess());
+        final AccessTokenResponse tokenResponse = decodedResponse.toSuccessResponse();
+        assertTrue(tokenResponse instanceof OIDCTokenResponse);
+        assertNotNull(((OIDCTokenResponse)tokenResponse).getTokens().getAccessToken());
+        assertNotNull(((OIDCTokenResponse)tokenResponse).getTokens().getBearerAccessToken());
+        assertNotNull(((OIDCTokenResponse)tokenResponse).getOIDCTokens().getIDToken());
     }
     
     @Test
@@ -74,9 +83,8 @@ public class DefaultMapResponseDecoderTest extends AbstractOIDCTest {
         decoder.initialize();
         final HttpResponse response = Mockito.mock(HttpResponse.class);
         
-        final Map<String, Object> decodedResponse = decoder.apply(response);
+        final TokenResponse decodedResponse = decoder.apply(response);
         assertNull(decodedResponse);
-        
     }
     
     @Test
@@ -86,10 +94,15 @@ public class DefaultMapResponseDecoderTest extends AbstractOIDCTest {
         final StatusLine mockStatusLine = Mockito.mock(StatusLine.class);
         when(mockStatusLine.getStatusCode()).thenReturn(500);
         when(response.getStatusLine()).thenReturn(mockStatusLine);
+        Mockito.when(response.getEntity()).thenReturn(new StringEntity(TOKEN_RESPONSE_ERROR, 
+                ContentType.parse(MediaType.APPLICATION_JSON_VALUE)));
         
-        final Map<String, Object> decodedResponse = decoder.apply(response);
-        assertNull(decodedResponse);
+        final TokenResponse decodedResponse = decoder.apply(response);
         
+        assertNotNull(decodedResponse);
+        assertFalse(decodedResponse.indicatesSuccess());
+        final ErrorResponse error = decodedResponse.toErrorResponse();
+        assertNotNull(error.getErrorObject());
     }
     
     @Test
@@ -101,7 +114,7 @@ public class DefaultMapResponseDecoderTest extends AbstractOIDCTest {
         when(response.getStatusLine()).thenReturn(mockStatusLine);
         when(response.getEntity()).thenReturn(null);
         
-        final Map<String, Object> decodedResponse = decoder.apply(response);
+        final TokenResponse decodedResponse = decoder.apply(response);        
         assertNull(decodedResponse);
         
     }
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultUserInfoResponseDecoderTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultUserInfoResponseDecoderTest.java
index 859d7cf..c4b0406 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultUserInfoResponseDecoderTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultUserInfoResponseDecoderTest.java
@@ -17,11 +17,11 @@
 
 package net.shibboleth.idp.plugin.authn.oidc.rp.decoding.impl;
 
+import static org.junit.Assert.assertNull;
 import static org.mockito.Mockito.when;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
 
 import java.io.ByteArrayInputStream;
@@ -37,10 +37,14 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.common.contenttype.ContentType;
+import com.nimbusds.jwt.EncryptedJWT;
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.ErrorObject;
+import com.nimbusds.openid.connect.sdk.UserInfoErrorResponse;
+import com.nimbusds.openid.connect.sdk.UserInfoResponse;
 
 import net.shibboleth.idp.plugin.authn.oidc.rp.impl.AbstractOIDCTest;
-import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.UserInfoResponse;
-import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.UserInfoResponse.UserInfoResponseType;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 
 /** Tests for the {@link DefaultUserInfoResponseDecoder}.*/
@@ -64,10 +68,20 @@ public class DefaultUserInfoResponseDecoderTest extends AbstractOIDCTest  {
         final StatusLine mockStatusLine = Mockito.mock(StatusLine.class);
         when(mockStatusLine.getStatusCode()).thenReturn(500);
         when(response.getStatusLine()).thenReturn(mockStatusLine);
+        when(response.getStatusLine()).thenReturn(mockStatusLine);
+
+        final Header[] headers = new Header[1];
+        headers[0] = new BasicHeader(
+                DefaultUserInfoResponseDecoder.USERINFO_ERROR_RESPONSE_HEADER,"Bearer realm=\"example\",\n"
+                + "                       error=\"invalid_token\",\n"
+                + "                       error_description=\"The access token expired\"");
+        when(response.getHeaders(DefaultUserInfoResponseDecoder.USERINFO_ERROR_RESPONSE_HEADER))
+            .thenReturn(headers);
         
         final UserInfoResponse userInfoResponse = decoder.apply(response);
-        assertNull(userInfoResponse);
-        
+        assertFalse(userInfoResponse.indicatesSuccess());
+        final ErrorObject errorMsg = ((UserInfoErrorResponse)userInfoResponse).getErrorObject();
+        assertNotNull(errorMsg);
     }
     
     @Test
@@ -99,9 +113,10 @@ public class DefaultUserInfoResponseDecoderTest extends AbstractOIDCTest  {
         final UserInfoResponse userInfoResponse = decoder.apply(response);
         
         assertNotNull(userInfoResponse);
-        assertTrue(userInfoResponse.getType() == UserInfoResponseType.PLAIN);
-        assertNotNull(userInfoResponse.getClaimsSet());
-        assertEquals(userInfoResponse.getSub(), "248289761001");
+        assertTrue(userInfoResponse.indicatesSuccess());
+        assertNotNull(userInfoResponse.toSuccessResponse().getUserInfo());
+        assertNull(userInfoResponse.toSuccessResponse().getUserInfoJWT());
+        assertEquals(userInfoResponse.toSuccessResponse().getUserInfo().getSubject().getValue(), "248289761001");
 
     }
     
@@ -124,7 +139,6 @@ public class DefaultUserInfoResponseDecoderTest extends AbstractOIDCTest  {
         
         when(mockHttpEntity.getContent()).thenReturn(new ByteArrayInputStream(USERINFO_RESPONSE_JWS.getBytes()));
         final UserInfoResponse userInfoResponse = decoder.apply(response);
-        
         assertNull(userInfoResponse);
     }
 
@@ -139,11 +153,28 @@ public class DefaultUserInfoResponseDecoderTest extends AbstractOIDCTest  {
         when(response.getEntity()).thenReturn(mockHttpEntity);
         // Set the WRONG content type here
         when(mockHttpEntity.getContentType()).thenReturn(
-                new BasicHeader("Content-Type",DefaultUserInfoResponseDecoder.APPLICATION_JWT.toString()));
+                new BasicHeader("Content-Type",ContentType.APPLICATION_JWT.getType()));
         
         when(mockHttpEntity.getContent()).thenReturn(new ByteArrayInputStream(USERINFO_RESPONSE.getBytes()));
         final UserInfoResponse userInfoResponse = decoder.apply(response);
+        assertNull(userInfoResponse);
+    }
+    
+    @Test
+    public void testEncoder_UnknownContentType() throws Exception {
+        decoder.initialize();
+        final HttpResponse response = Mockito.mock(HttpResponse.class);
+        final HttpEntity mockHttpEntity = Mockito.mock(HttpEntity.class);
+        final StatusLine mockStatusLine = Mockito.mock(StatusLine.class);
+        when(mockStatusLine.getStatusCode()).thenReturn(200);
+        when(response.getStatusLine()).thenReturn(mockStatusLine);
+        when(response.getEntity()).thenReturn(mockHttpEntity);
+        // Set the WRONG content type here
+        when(mockHttpEntity.getContentType()).thenReturn(
+                new BasicHeader("Content-Type","application/unknown"));
         
+        when(mockHttpEntity.getContent()).thenReturn(new ByteArrayInputStream(USERINFO_RESPONSE_JWS.getBytes()));
+        final UserInfoResponse userInfoResponse = decoder.apply(response);
         assertNull(userInfoResponse);
     }
     
@@ -157,16 +188,19 @@ public class DefaultUserInfoResponseDecoderTest extends AbstractOIDCTest  {
         when(response.getStatusLine()).thenReturn(mockStatusLine);
         when(response.getEntity()).thenReturn(mockHttpEntity);
         when(mockHttpEntity.getContentType()).thenReturn(
-                new BasicHeader("Content-Type",DefaultUserInfoResponseDecoder.APPLICATION_JWT.toString()));
+                new BasicHeader("Content-Type",ContentType.APPLICATION_JWT.getType()));
         when(mockHttpEntity.getContent()).thenReturn(new ByteArrayInputStream(USERINFO_RESPONSE_JWS.getBytes()));
-        final UserInfoResponse userInfoResponse = decoder.apply(response);
         
+        final UserInfoResponse userInfoResponse = decoder.apply(response);
         assertNotNull(userInfoResponse);
-        assertTrue(userInfoResponse.getType() == UserInfoResponseType.JWT);
-        assertTrue(userInfoResponse.isSigned());
-        assertNotNull(userInfoResponse.getClaimsSet());
-        assertEquals(userInfoResponse.getSub(), "user-subject-1234531");
-        assertTrue(userInfoResponse.isClaimsSetAvailable());
+        assertTrue(userInfoResponse.indicatesSuccess());   
+        assertEquals(userInfoResponse.toSuccessResponse().getEntityContentType(), ContentType.APPLICATION_JWT);
+        assertNull(userInfoResponse.toSuccessResponse().getUserInfo());
+        assertNotNull(userInfoResponse.toSuccessResponse().getUserInfoJWT());
+        assertTrue(userInfoResponse.toSuccessResponse().getUserInfoJWT() instanceof SignedJWT);
+        assertNotNull(userInfoResponse.toSuccessResponse().getUserInfoJWT().getJWTClaimsSet());
+        assertEquals(userInfoResponse.toSuccessResponse().getUserInfoJWT().getJWTClaimsSet()
+                .getSubject(), "user-subject-1234531");
     }
     
     @Test
@@ -179,14 +213,17 @@ public class DefaultUserInfoResponseDecoderTest extends AbstractOIDCTest  {
         when(response.getStatusLine()).thenReturn(mockStatusLine);
         when(response.getEntity()).thenReturn(mockHttpEntity);
         final Header[] headers = new Header[1];
-        headers[0] = new BasicHeader("WWW-Authenticate"," Bearer realm=\"example\",\n"
-                + "error=\"invalid_token\",\n"
-                + "error_description=\"The access token expired\"");
-        when(response.getHeaders("WWW-Authenticate")).thenReturn(headers);
+        headers[0] = new BasicHeader(
+                DefaultUserInfoResponseDecoder.USERINFO_ERROR_RESPONSE_HEADER,"Bearer realm=\"example\",\n"
+                + "                       error=\"invalid_token\",\n"
+                + "                       error_description=\"The access token expired\"");
+        when(response.getHeaders(DefaultUserInfoResponseDecoder.USERINFO_ERROR_RESPONSE_HEADER))
+            .thenReturn(headers);
 
         final UserInfoResponse userInfoResponse = decoder.apply(response);
         
-        assertNull(userInfoResponse);
+        assertNotNull(userInfoResponse);
+        assertFalse(userInfoResponse.indicatesSuccess());
     }
     
     @Test
@@ -199,14 +236,17 @@ public class DefaultUserInfoResponseDecoderTest extends AbstractOIDCTest  {
         when(response.getStatusLine()).thenReturn(mockStatusLine);
         when(response.getEntity()).thenReturn(mockHttpEntity);
         when(mockHttpEntity.getContentType()).thenReturn(
-                new BasicHeader("Content-Type",DefaultUserInfoResponseDecoder.APPLICATION_JWT.toString()));
+                new BasicHeader("Content-Type",ContentType.APPLICATION_JWT.getType()));
         when(mockHttpEntity.getContent()).thenReturn(new ByteArrayInputStream(USERINFO_RESPONSE_JWE.getBytes()));
-        final UserInfoResponse userInfoResponse = decoder.apply(response);
         
+        final UserInfoResponse userInfoResponse = decoder.apply(response);        
         assertNotNull(userInfoResponse);
-        assertTrue(userInfoResponse.getType() == UserInfoResponseType.JWT);
-        assertTrue(userInfoResponse.isEncrypted());
-        assertFalse(userInfoResponse.isClaimsSetAvailable());
+        assertTrue(userInfoResponse.indicatesSuccess());   
+        assertEquals(userInfoResponse.toSuccessResponse().getEntityContentType(), ContentType.APPLICATION_JWT);
+        assertNull(userInfoResponse.toSuccessResponse().getUserInfo());
+        assertNotNull(userInfoResponse.toSuccessResponse().getUserInfoJWT());
+        assertTrue(userInfoResponse.toSuccessResponse().getUserInfoJWT() instanceof EncryptedJWT);
+
     }
 
 }
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java
index e8ce877..42de561 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java
@@ -19,14 +19,18 @@ package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
 
 import java.net.URI;
 import java.time.Duration;
+import java.util.Map;
 import java.util.Set;
 
 import javax.annotation.Nonnull;
 
+import org.apache.http.HttpResponse;
 import org.opensaml.messaging.context.MessageContext;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.springframework.webflow.execution.RequestContext;
 
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.nimbusds.oauth2.sdk.id.ClientID;
 import com.nimbusds.oauth2.sdk.id.State;
 import com.nimbusds.openid.connect.sdk.AuthenticationResponseParser;
@@ -102,7 +106,9 @@ public abstract class AbstractOIDCTest {
     @Nonnull @NotEmpty
     protected final String ACCESS_TOKEN_RESPONSE = "{\n"
             + "  \"access_token\": \"W0y5aDNAzEPNpSzu1cuMG904BZuQFZJUUwG5F3ct0zydZWy1ji\",\n"
+            + "\"refresh_token\": \"W0y5aDNAzEPNpSzu1cuMG904BZuQFZJUUwG5F3ct0zydZWy1ji\",\n"
             + "  \"token_type\": \"Bearer\",\n"
+            + "  \"expires_in\": 3600,\n"
             + "  \"id_token\": \"eyJraWQiOiJiNjliYzcyOS05NDJjLTQzNjItYmM2YS03OWU3MjAwOWY0Yzg"
             + "iLCJhbGciOiJSUzI1NiJ9.eyJhdF9oYXNoIjoiM0pBZmFibUx4eWVnNjJJM2JiT0RrdyIsInN1YiI"
             + "6InVzZXItc3ViamVjdC0xMjM0NTMxIiwiYXVkIjoibXl0ZXN0Y2xpZW50IiwiaXNzIjoiaHR0cHM6"
@@ -114,6 +120,39 @@ public abstract class AbstractOIDCTest {
             + "o8rji7juk3szTOclbsYFrrd9iokWHl0qJa0G1ev5XQLsiUxYxgO1Nw-CAnHtESZ9w7JIfLLX1YlY73g\",\n"
             + "  \"scope\": \"openid\"\n"
             + "}";
+   
+    
+    @Nonnull @NotEmpty
+    protected final String ACCESS_TOKEN_RESPONSE_NO_ACCESSTOKEN = "{\n"
+            + " \"refresh_token\": \"W0y5aDNAzEPNpSzu1cuMG904BZuQFZJUUwG5F3ct0zydZWy1ji\",\n"
+            + "  \"token_type\": \"Bearer\",\n"
+            + " \"expires_in\": 3600,\n"
+            + "  \"id_token\": \"eyJraWQiOiJiNjliYzcyOS05NDJjLTQzNjItYmM2YS03OWU3MjAwOWY0Yzg"
+            + "iLCJhbGciOiJSUzI1NiJ9.eyJhdF9oYXNoIjoiM0pBZmFibUx4eWVnNjJJM2JiT0RrdyIsInN1YiI"
+            + "6InVzZXItc3ViamVjdC0xMjM0NTMxIiwiYXVkIjoibXl0ZXN0Y2xpZW50IiwiaXNzIjoiaHR0cHM6"
+            + "XC9cL3d3dy5jZXJ0aWZpY2F0aW9uLm9wZW5pZC5uZXRcL3Rlc3RcL2FcL3Rlc3RfcnBfcHJveHlcLy"
+            + "IsImV4cCI6MTY0MzI3NTQyNCwiaWF0IjoxNjQzMjc1MTI0fQ.igU798iSZYUR7Z6_MQQendFCqm9Pf"
+            + "6Xpd4KtnHcPGG5FxtXPiDhlyhP_7ewSIQtzWxQ5wEatixDJWl2oRasivNCJyl6ZNeUG4YuT8S7xlgp"
+            + "UumoycBxrmomS7wgZSola8oZo8BuiRKlWzqLLtFkAChGi5iJEtHfVBe3k3PyQAsWRQdtixkvdxvyLq"
+            + "FLznIaPmMH-OZ82w7sJf4bpalZSAaR5NLpyzLFw-r81IOtOVrpQtnycCw0w_JxAP16GyZXCJc56vCE"
+            + "o8rji7juk3szTOclbsYFrrd9iokWHl0qJa0G1ev5XQLsiUxYxgO1Nw-CAnHtESZ9w7JIfLLX1YlY73g\",\n"
+            + "  \"scope\": \"openid\"\n"
+            + "}";
+   
+    @Nonnull @NotEmpty
+    protected final String ACCESS_TOKEN_RESPONSE_NO_IDTOKEN = "{\n"
+            + "  \"access_token\": \"W0y5aDNAzEPNpSzu1cuMG904BZuQFZJUUwG5F3ct0zydZWy1ji\",\n"
+            + " \"refresh_token\": \"W0y5aDNAzEPNpSzu1cuMG904BZuQFZJUUwG5F3ct0zydZWy1ji\",\n"
+            + "  \"token_type\": \"Bearer\",\n"
+            + " \"expires_in\": 3600,\n"            
+            + "  \"scope\": \"openid\"\n"
+            + "}";
+    
+    @Nonnull @NotEmpty
+    protected final String TOKEN_RESPONSE_ERROR = "{\n"
+            + "       \"error\":\"invalid_request\",\n"
+            + "       \"error_description\":\"this request was bad\"\n"
+            + "     }";
     
     /** Mock a UserInfo JWE reponse.*/
     @Nonnull @NotEmpty
@@ -271,5 +310,14 @@ public abstract class AbstractOIDCTest {
         prc.setInboundMessageContext(inMsgCtx);
         prc.setOutboundMessageContext(outMsgCtx);
     }
+    
+    
+    protected JSONObject convertHttpResponseToJSONObject(final HttpResponse response) throws Exception {
+        final ObjectMapper mapper = new ObjectMapper();
+        final Map<String, Object> tokenResponseAsMap = mapper.readValue(
+                response.getEntity().getContent(), new TypeReference<Map<String, Object>>() {});
+        return new JSONObject(tokenResponseAsMap);
+    }
+    
 
 }
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExchangeCodeForTokenTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExchangeCodeForAccessTokenTest.java
similarity index 56%
rename from idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExchangeCodeForTokenTest.java
rename to idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExchangeCodeForAccessTokenTest.java
index b4750c6..9db95bc 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExchangeCodeForTokenTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExchangeCodeForAccessTokenTest.java
@@ -21,11 +21,10 @@ package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertNull;
+import static org.testng.Assert.fail;
 
-import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.util.Map;
 
 import javax.annotation.Nonnull;
 
@@ -42,13 +41,16 @@ import org.mockito.Mockito;
 import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.messaging.context.navigate.ParentContextLookup;
 import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.http.MediaType;
 import org.springframework.webflow.execution.Event;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
-import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.nimbusds.jose.util.StandardCharset;
+import com.nimbusds.oauth2.sdk.TokenErrorResponse;
+import com.nimbusds.oauth2.sdk.TokenResponse;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
 
 import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
@@ -56,7 +58,7 @@ import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileR
 /**
  * Tests for the {@link ExchangeCodeForAccessToken} action.
  */
-public class ExchangeCodeForTokenTest extends AbstractOIDCTest {
+public class ExchangeCodeForAccessTokenTest extends AbstractOIDCTest {
     
     /** The action to test.*/
     @Nonnull private ExchangeCodeForAccessToken exchangeAction;
@@ -92,7 +94,8 @@ public class ExchangeCodeForTokenTest extends AbstractOIDCTest {
         Mockito.when(httpResponse.getStatusLine()).thenReturn(statusLine);
 
         Mockito.when(statusLine.getStatusCode()).thenReturn(200);
-        Mockito.when(httpResponse.getEntity()).thenReturn(new StringEntity(ACCESS_TOKEN_RESPONSE));
+        Mockito.when(httpResponse.getEntity()).thenReturn(
+                new StringEntity(ACCESS_TOKEN_RESPONSE, ContentType.parse(MediaType.APPLICATION_JSON_VALUE)));
         Mockito.when(httpClient.execute((HttpUriRequest) Mockito.any(), (HttpContext) Mockito.any()))
                 .thenReturn(httpResponse);
 
@@ -119,9 +122,8 @@ public class ExchangeCodeForTokenTest extends AbstractOIDCTest {
         exchangeAction.setHttpResponseDecoderStrategy(response -> {
             final ObjectMapper mapper = new ObjectMapper();
             try {
-                return mapper.readValue(
-                        httpResponse.getEntity().getContent(), new TypeReference<Map<String, Object>>() {});
-            } catch (final UnsupportedOperationException | IOException e) {                
+                return OIDCTokenResponse.parse(convertHttpResponseToJSONObject(httpResponse));
+            } catch (final Exception e) {                
                 return null;
             }
         });
@@ -134,6 +136,110 @@ public class ExchangeCodeForTokenTest extends AbstractOIDCTest {
 
     }
     
+    @Test
+    public void testTokenExchange_WrongTokenType() throws Exception {
+
+        final HttpClient httpClient = Mockito.mock(HttpClient.class);
+        final HttpResponse httpResponse = Mockito.mock(HttpResponse.class);
+        final StatusLine statusLine = Mockito.mock(StatusLine.class);
+
+        Mockito.when(httpResponse.getStatusLine()).thenReturn(statusLine);
+
+        Mockito.when(statusLine.getStatusCode()).thenReturn(200);
+        Mockito.when(httpResponse.getEntity()).thenReturn(
+                new StringEntity(ACCESS_TOKEN_RESPONSE_NO_IDTOKEN, ContentType.parse(MediaType.APPLICATION_JSON_VALUE)));
+        Mockito.when(httpClient.execute((HttpUriRequest) Mockito.any(), (HttpContext) Mockito.any()))
+                .thenReturn(httpResponse);
+
+        // create new client with mock response
+        exchangeAction.setHttpClient(httpClient);
+        
+        exchangeAction.setHttpRequestEncoderStrategy(prc -> {
+            URI uri;
+            try {
+                uri = new URIBuilder().setScheme("https")
+                        .setHost("op.example.com")
+                        .setPath("/token")
+                        .build();
+            } catch (final URISyntaxException e) {
+                return null;
+            }                 
+            
+            // Add headers and create request.
+            final RequestBuilder rb = RequestBuilder.post().setUri(uri)
+                    .setHeader("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType())
+                    .setCharset(StandardCharset.UTF_8);
+            return rb.build();
+        });     
+        exchangeAction.setHttpResponseDecoderStrategy(response -> {
+            final ObjectMapper mapper = new ObjectMapper();
+            try {
+                return TokenResponse.parse(convertHttpResponseToJSONObject(httpResponse));
+            } catch (final Exception e) {                
+                return null;
+            }
+        });
+        
+        exchangeAction.initialize();
+
+        final Event event = exchangeAction.execute(src);
+        assertNotNull(event);
+        assertEquals("AuthenticationException",event.getId());
+
+    }
+
+    @Test
+    public void testTokenExchange_ErrorResponse() throws Exception {
+
+        final HttpClient httpClient = Mockito.mock(HttpClient.class);
+        final HttpResponse httpResponse = Mockito.mock(HttpResponse.class);
+        final StatusLine statusLine = Mockito.mock(StatusLine.class);
+
+        Mockito.when(httpResponse.getStatusLine()).thenReturn(statusLine);
+
+        Mockito.when(statusLine.getStatusCode()).thenReturn(400);
+        Mockito.when(httpResponse.getEntity()).thenReturn(
+                new StringEntity(TOKEN_RESPONSE_ERROR, ContentType.parse(MediaType.APPLICATION_JSON_VALUE)));
+        Mockito.when(httpClient.execute((HttpUriRequest) Mockito.any(), (HttpContext) Mockito.any()))
+                .thenReturn(httpResponse);
+
+        // create new client with mock response
+        exchangeAction.setHttpClient(httpClient);
+        
+        exchangeAction.setHttpRequestEncoderStrategy(prc -> {
+            URI uri;
+            try {
+                uri = new URIBuilder().setScheme("https")
+                        .setHost("op.example.com")
+                        .setPath("/token")
+                        .build();
+            } catch (final URISyntaxException e) {
+                return null;
+            }                 
+            
+            // Add headers and create request.
+            final RequestBuilder rb = RequestBuilder.post().setUri(uri)
+                    .setHeader("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType())
+                    .setCharset(StandardCharset.UTF_8);
+            return rb.build();
+        });     
+        exchangeAction.setHttpResponseDecoderStrategy(response -> {
+            try {
+                return TokenErrorResponse.parse(convertHttpResponseToJSONObject(httpResponse));
+            } catch (final Exception e) {                
+                fail(e.getMessage());
+                return null;
+            }
+        });
+        
+        exchangeAction.initialize();
+
+        final Event event = exchangeAction.execute(src);
+        assertNotNull(event);
+        assertEquals("AuthenticationException",event.getId());
+
+    }
+    
     @Test
     public void testNullTokenResponse() throws Exception {
 
@@ -168,7 +274,6 @@ public class ExchangeCodeForTokenTest extends AbstractOIDCTest {
         exchangeAction.initialize();
 
         final Event event = exchangeAction.execute(src);
-        //Null is success
         assertNotNull(event);
         assertEquals("AuthenticationException",event.getId());
 
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExtractIDTokenFromResponseTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExtractIDTokenFromResponseTest.java
deleted file mode 100644
index 5cd9818..0000000
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExtractIDTokenFromResponseTest.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
-
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
-
-import org.opensaml.messaging.context.navigate.ChildContextLookup;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.springframework.webflow.execution.Event;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
-import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
-import net.shibboleth.oidc.profile.core.OidcEventIds;
-import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
-
-/** Tests for ExtractIDTokenFromResponse.*/
-public class ExtractIDTokenFromResponseTest extends AbstractOIDCTest {
-    
-    private ExtractIDTokenFromResponse action;
-    
-    
-    @Override
-    @BeforeMethod
-    public void setup() throws Exception {
-        super.setup();
-        action = new ExtractIDTokenFromResponse();       
-
-        final AccessTokenResponseContext trc = new AccessTokenResponseContext();
-        prc.getInboundMessageContext().addSubcontext(trc);        
-
-        action.setProfileContextLookupStrategy(new ChildContextLookup<>(ProfileRequestContext.class).compose(
-                new ChildContextLookup<>(AuthenticationContext.class)
-                .compose(new WebflowRequestContextProfileRequestContextLookup())));      
-    }
-    
-    @Test
-    public void testExtractSignedToken_Success() throws ComponentInitializationException {
-        action.setRawIdTokenLookupStrategy(prc -> "eyJraWQiOiJiNjliY"
-                + "zcyOS05NDJjLTQzNjItYmM2YS03OWU3MjAwOWY0YzgiLCJhbG"
-                + "ciOiJSUzI1NiJ9.eyJhdF9oYXNoIjoiM0pBZmFibUx4eWVnNj"
-                + "JJM2JiT0RrdyIsInN1YiI6InVzZXItc3ViamVjdC0xMjM0NTM"
-                + "xIiwiYXVkIjoibXl0ZXN0Y2xpZW50IiwiaXNzIjoiaHR0cHM6X"
-                + "C9cL3d3dy5jZXJ0aWZpY2F0aW9uLm9wZW5pZC5uZXRcL3Rlc3"
-                + "RcL2FcL3Rlc3RfcnBfcHJveHlcLyIsImV4cCI6MTY0MzI3NTQy"
-                + "NCwiaWF0IjoxNjQzMjc1MTI0fQ.igU798iSZYUR7Z6_MQQend"
-                + "FCqm9Pf6Xpd4KtnHcPGG5FxtXPiDhlyhP_7ewSIQtzWxQ5wEat"
-                + "ixDJWl2oRasivNCJyl6ZNeUG4YuT8S7xlgpUumoycBxrmomS7"
-                + "wgZSola8oZo8BuiRKlWzqLLtFkAChGi5iJEtHfVBe3k3PyQAs"
-                + "WRQdtixkvdxvyLqFLznIaPmMH-OZ82w7sJf4bpalZSAaR5NLpy"
-                + "zLFw-r81IOtOVrpQtnycCw0w_JxAP16GyZXCJc56vCEo8rji7"
-                + "juk3szTOclbsYFrrd9iokWHl0qJa0G1ev5XQLsiUxYxgO1Nw-"
-                + "CAnHtESZ9w7JIfLLX1YlY73g");
-        
-        action.initialize();
-        final Event event = action.execute(src);
-        assertNull(event);
-        assertNotNull(prc.getInboundMessageContext().getSubcontext(AccessTokenResponseContext.class).getIdToken());
-        
-    }
-    
-    /* Header has been changed to use the 'none' algorithm', and signature removed*/
-    @Test
-    public void testExtractSignedToken_NoneAlgInjectedIntoHeaderSuccess() throws ComponentInitializationException {
-        action.setRawIdTokenLookupStrategy(prc -> 
-                "eyJraWQiOiJiNjliYzcyOS05NDJjLTQzNjItYmM2YS03OWU3MjAwOWY0YzgiLCJhbGciOiJub25lIn0=."
-                + "eyJhdF9oYXNoIjoiM0pBZmFibUx4eWVnNj"
-                + "JJM2JiT0RrdyIsInN1YiI6InVzZXItc3ViamVjdC0xMjM0NTM"
-                + "xIiwiYXVkIjoibXl0ZXN0Y2xpZW50IiwiaXNzIjoiaHR0cHM6X"
-                + "C9cL3d3dy5jZXJ0aWZpY2F0aW9uLm9wZW5pZC5uZXRcL3Rlc3"
-                + "RcL2FcL3Rlc3RfcnBfcHJveHlcLyIsImV4cCI6MTY0MzI3NTQy"
-                + "NCwiaWF0IjoxNjQzMjc1MTI0fQ");
-        
-        action.initialize();
-        final Event event = action.execute(src);
-        assertNotNull(event);
-        assertEquals(event.getId(), OidcEventIds.INVALID_ID_TOKEN);
-        
-    }
-    
-    //TODO need encrypted as well?
-
-}
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/MergeUserInfoAndIDTokenClaimsTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/MergeUserInfoAndIDTokenClaimsTest.java
deleted file mode 100644
index b106ffc..0000000
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/MergeUserInfoAndIDTokenClaimsTest.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
-
-import java.time.Instant;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-
-import org.opensaml.messaging.context.navigate.ChildContextLookup;
-import org.opensaml.messaging.context.navigate.ParentContextLookup;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.springframework.webflow.execution.Event;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import com.nimbusds.jwt.JWTClaimsSet;
-import com.nimbusds.jwt.PlainJWT;
-import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
-
-import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
-import net.shibboleth.idp.plugin.authn.oidc.rp.context.EndUserClaimsContext;
-import net.shibboleth.idp.plugin.authn.oidc.rp.context.UserInfoResponseContext;
-import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.PlainUserInfoResponse;
-import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.UserInfoResponse;
-import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
-import net.shibboleth.oidc.security.jwt.claims.impl.JWTClaims;
-import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
-
-/** Tests for {@link ProcessEndUserClaims}.*/
-public class MergeUserInfoAndIDTokenClaimsTest extends AbstractOIDCTest {
-    
-    /** Action to test.*/
-    private ProcessEndUserClaims action;
-    
-    
-    @BeforeMethod
-    public void setup() throws Exception {
-        super.setup();
-        action = new ProcessEndUserClaims();   
-        
-        final AccessTokenResponseContext trc = new AccessTokenResponseContext();
-        final PlainJWT jwt = new PlainJWT(new JWTClaimsSet.Builder()
-                .issuer("https://op.example.com")
-                .audience(List.of("https://rp.example.com"))
-                .subject("jdoe")
-                .claim("nonce", "abadnonce")
-                .claim("azp", "https://rp.example.com")
-                .claim("name","jdoe")
-                .expirationTime(Date.from(Instant.now().plusSeconds(120)))
-                .build());
-        trc.setIdToken(jwt);
-        prc.getInboundMessageContext().addSubcontext(trc);   
-
-        final ClaimsSet claims = new ClaimsSet();
-        claims.putAll(Map.of("sub","jdoe","given_name","John","email","jdoe at example.com"));
-        final UserInfoResponse userInfoResponse = new PlainUserInfoResponse(claims);
-        final UserInfoResponseContext urc = new UserInfoResponseContext();
-        urc.setUserInfo(userInfoResponse);
-        prc.getInboundMessageContext().addSubcontext(urc);        
-
-        action.setAuthenticationContextLookupStrategy(new ParentContextLookup<>(AuthenticationContext.class));
-        
-        action.setProfileContextLookupStrategy(new ChildContextLookup<>(ProfileRequestContext.class).compose(
-                new ChildContextLookup<>(AuthenticationContext.class)
-                .compose(new WebflowRequestContextProfileRequestContextLookup())));      
-    }
-    
-    @Test
-    public void testSuccesfulMerge() throws ComponentInitializationException {
-        
-        action.initialize();
-        final Event event = action.execute(src);
-        assertNull(event);
-        assertNotNull(prc.getInboundMessageContext().getSubcontext(EndUserClaimsContext.class).getEndUserClaims());
-        final ClaimsSet claims =
-                prc.getInboundMessageContext().getSubcontext(EndUserClaimsContext.class).getEndUserClaims();
-        assertEquals(claims.getClaim("name"),"jdoe");
-        assertEquals(claims.getClaim("sub"),"jdoe");
-        assertEquals(claims.getClaim("given_name"),"John");
-        assertEquals(claims.getClaim("email"),"jdoe at example.com");
-        assertNull(claims.getIssuer());
-        assertNull(claims.getAudience());
-        assertNull(claims.getDateClaim(JWTClaims.EXPIRATION_TIME_CLAIM.getClaimName()));
-    }
-    
-    @Test
-    public void testSuccesfulMergeNoSanitization() throws ComponentInitializationException {
-        
-        action.setEnableClaimSanitizationStrategy(false);
-        action.initialize();
-        final Event event = action.execute(src);
-        assertNull(event);
-        assertNotNull(prc.getInboundMessageContext().getSubcontext(EndUserClaimsContext.class).getEndUserClaims());
-        final ClaimsSet claims =
-                prc.getInboundMessageContext().getSubcontext(EndUserClaimsContext.class).getEndUserClaims();
-        assertEquals(claims.getClaim("name"),"jdoe");
-        assertEquals(claims.getClaim("sub"),"jdoe");
-        assertEquals(claims.getClaim("given_name"),"John");
-        assertEquals(claims.getClaim("email"),"jdoe at example.com");
-        assertNotNull(claims.getIssuer());
-        assertNotNull(claims.getAudience());
-        assertNotNull(claims.getDateClaim(JWTClaims.EXPIRATION_TIME_CLAIM.getClaimName()));
-    }
-
-}
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 f00a9a9..c3d3e82 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
@@ -28,6 +28,7 @@ import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.authn.context.ExternalAuthenticationContext;
 import net.shibboleth.idp.authn.context.SubjectCanonicalizationContext;
 import net.shibboleth.idp.authn.impl.ExternalAuthenticationImpl;
+import net.shibboleth.idp.plugin.authn.oidc.rp.test.TestTokenHelper;
 import net.shibboleth.idp.saml.authn.principal.AuthenticationMethodPrincipal;
 import net.shibboleth.oidc.authn.principal.AuthenticationContextClassReferencePrincipal;
 import net.shibboleth.oidc.profile.config.JSONSecurityConfiguration;
@@ -688,5 +689,40 @@ public class OIDCRPFlowFromAuthenticationResponseTest extends OIDCRPFlowTest {
       // Should have failed to produce a result
       assertFlowEndedInErrorConditions(prc, "InvalidUserInfoClaims");  
   }
+  
+  /** 
+   * Test the flow from the external authorization request to the end of the flow.
+   * Using a MAC signed id_token and an invalid UserInfo JSON Response - it has no subject.
+   * 
+   * @throws Exception on error.
+   */
+  @Test 
+  public void test_IDTokenHS256_InvalidPlainUserInfo_SubDoesNotMatchIdToken() throws Exception {
+      
+      basicSetup();
+      
+      final MockWebServer mockOPServer = createSimpleServer();
+      final var accessTokenResp = TestTokenHelper.createAccessTokenResponseJSON(Map.of("iss", OP_ISSUER_ID, "azp", 
+              CLIENT_ID, "aud", List.of(CLIENT_ID)), JWSAlgorithm.HS256, null, null, 
+                      new DefaultClientSecretCredential(CLIENT_SECRET).toSigningCredential(), null);
+      final var userInfoResp = 
+              TestTokenHelper.createPlainUserInfoResponseString(
+                      Map.of("sub","wrong_sub", "iss", OP_ISSUER_ID, "aud", List.of(CLIENT_ID)));
+      
+      // First is token exchange
+      queueMockServerResponse(mockOPServer, 200, accessTokenResp, "application/json");
+      // Second is plain userInfo
+      queueMockServerResponse(mockOPServer, 200, userInfoResp, "application/json");
+      
+      mockOPServer.start(9918);        
+
+      final var prc = resumeBasicFlow();
+      
+      mockOPServer.shutdown();
+      
+      // Assert test conditions   
+      // Should have failed to produce a result
+      assertFlowEndedInErrorConditions(prc, "InvalidUserInfoClaims");  
+  }
 
 }
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 586adbc..2540765 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
@@ -48,6 +48,7 @@ import org.springframework.webflow.engine.Flow;
 import org.springframework.webflow.engine.impl.FlowExecutionImpl;
 import org.springframework.webflow.test.MockFlowBuilderContext;
 
+import com.nimbusds.common.contenttype.ContentType;
 import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.jwt.SignedJWT;
 import com.nimbusds.oauth2.sdk.ParseException;
@@ -72,8 +73,6 @@ import net.shibboleth.idp.plugin.authn.oidc.rp.context.EndUserClaimsContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.OAuth2ClientContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.UserInfoResponseContext;
-import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.JWTUserInfoResponse;
-import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.PlainUserInfoResponse;
 import net.shibboleth.idp.plugin.authn.oidc.rp.principal.OIDCSubjectIdentifierPrincipal;
 import net.shibboleth.idp.plugin.authn.test.flow.AbstractAuthnXmlFlowExecutionTests;
 import net.shibboleth.idp.plugin.authn.test.flow.mock.MockFlowBuilder;
@@ -676,9 +675,10 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
             throws java.text.ParseException {
         final var accessTokenResponse = 
                 nestedPrc.getInboundMessageContext().getSubcontext(AccessTokenResponseContext.class);
-        assertNotNull(accessTokenResponse.getIdToken());
-        assertTrue(accessTokenResponse.getIdToken() instanceof SignedJWT);
-        final var signedJwt = (SignedJWT) accessTokenResponse.getIdToken();
+        assertNotNull(accessTokenResponse.getTokenResponse().getOIDCTokens().getIDToken());
+        //TODO this might change if the token we mutate is not in the original token response
+        assertTrue(accessTokenResponse.getTokenResponse().getOIDCTokens().getIDToken() instanceof SignedJWT);
+        final var signedJwt = (SignedJWT) accessTokenResponse.getTokenResponse().getOIDCTokens().getIDToken();
         assertEquals(signedJwt.getState(), com.nimbusds.jose.JWSObject.State.VERIFIED);
         assertStandardIdTokenClaimsSuccessCondition(signedJwt.getJWTClaimsSet());
     }
@@ -695,9 +695,10 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         final var userInfoResponse = 
                 nestedPrc.getInboundMessageContext().getSubcontext(UserInfoResponseContext.class);
         assertNotNull(userInfoResponse.getUserInfo());
-        assertTrue(userInfoResponse.getUserInfo() instanceof JWTUserInfoResponse);
-        final var jwtResponse = (JWTUserInfoResponse) userInfoResponse.getUserInfo();
-        final var signedJwt = (SignedJWT) jwtResponse.getResponseJwt();
+        assertEquals(userInfoResponse.getUserInfo().getEntityContentType(), ContentType.APPLICATION_JWT);
+        final var jwtResponse = userInfoResponse.getUserInfo().getUserInfoJWT();
+        assertTrue(jwtResponse instanceof SignedJWT);
+        final var signedJwt = (SignedJWT) jwtResponse;
         assertEquals(signedJwt.getState(), com.nimbusds.jose.JWSObject.State.VERIFIED);
         
     }
@@ -714,7 +715,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         final var userInfoResponse = 
                 nestedPrc.getInboundMessageContext().getSubcontext(UserInfoResponseContext.class);
         assertNotNull(userInfoResponse.getUserInfo());
-        assertTrue(userInfoResponse.getUserInfo() instanceof PlainUserInfoResponse);
+        assertTrue(userInfoResponse.getUserInfo().getEntityContentType() == ContentType.APPLICATION_JSON);
         
     }
     
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ProcessEndUserClaimsTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ProcessEndUserClaimsTest.java
index fde38ec..886d33b 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ProcessEndUserClaimsTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ProcessEndUserClaimsTest.java
@@ -23,10 +23,13 @@ import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertNull;
 import static org.testng.Assert.fail;
 
+import java.time.Instant;
+import java.util.Date;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 
+import org.mockito.Mockito;
 import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.messaging.context.navigate.ParentContextLookup;
 import org.opensaml.profile.action.EventIds;
@@ -38,18 +41,24 @@ import org.testng.annotations.Test;
 import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.jwt.PlainJWT;
 import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
+import com.nimbusds.oauth2.sdk.token.RefreshToken;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+import com.nimbusds.openid.connect.sdk.UserInfoSuccessResponse;
 import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
+import com.nimbusds.openid.connect.sdk.claims.UserInfo;
+import com.nimbusds.openid.connect.sdk.token.OIDCTokens;
 
 import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.EndUserClaimsContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.UserInfoResponseContext;
-import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.PlainUserInfoResponse;
 import net.shibboleth.idp.profile.context.RelyingPartyContext;
 import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
 import net.shibboleth.idp.relyingparty.RelyingPartyConfiguration;
 import net.shibboleth.oidc.profile.config.OIDCAuthorizationConfiguration;
 import net.shibboleth.oidc.security.jwt.claims.impl.JWTClaims;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 
 /** Tests for {@link ProcessEndUserClaims}.*/
 public class ProcessEndUserClaimsTest extends AbstractOIDCTest {
@@ -94,7 +103,8 @@ public class ProcessEndUserClaimsTest extends AbstractOIDCTest {
         action.setClaimSanitizationStrategy(claims -> {
             final Set<String> validationClaims = Set.of(                  
                     JWTClaims.ISSUER_CLAIM.getClaimName(),
-                    JWTClaims.AUDIENCE_CLAIM.getClaimName());
+                    JWTClaims.AUDIENCE_CLAIM.getClaimName(),
+                    JWTClaims.EXPIRATION_TIME_CLAIM.getClaimName());
             
             final ClaimsSet sanitizedClaims = new ClaimsSet();
             final Map<String, Object> filteredMap = claims.toJSONObject().entrySet()
@@ -107,13 +117,20 @@ public class ProcessEndUserClaimsTest extends AbstractOIDCTest {
         
         action.setAccessTokenResponseContextLookupStrategy(prc -> {
             final AccessTokenResponseContext atrc = new AccessTokenResponseContext();
+            final OIDCTokenResponse tokenResponse = Mockito.mock(OIDCTokenResponse.class);
+            atrc.setTokenResponse(tokenResponse);
             final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
                     .issuer("https://op.example.com")
                     .audience("https://rp.example.com")
-                    .subject("joe")
+                    .subject("jdoe")
                     .claim("family_name", "blogs")
+                    .claim("name","jdoe")
+                    .expirationTime(Date.from(Instant.now().plusSeconds(120)))
                     .build();
-            atrc.setIdToken(new PlainJWT(claimsSet));
+            final OIDCTokens tokens = new OIDCTokens(new PlainJWT(claimsSet), 
+                    new BearerAccessToken("access_token_value"), 
+                    new RefreshToken("refresh_token_value"));
+            Mockito.when(tokenResponse.getOIDCTokens()).thenReturn(tokens);
             return atrc;
         });
         
@@ -122,7 +139,8 @@ public class ProcessEndUserClaimsTest extends AbstractOIDCTest {
             final ClaimsSet claims = new ClaimsSet();
             claims.setClaim("given_name", "joe");
             claims.setClaim("email", "joe at example.com");
-            final PlainUserInfoResponse uir = new PlainUserInfoResponse(claims);
+            claims.setClaim("sub", "jdoe");
+            final UserInfoSuccessResponse uir = new UserInfoSuccessResponse(new UserInfo(claims.toJSONObject()));
             uirc.setUserInfo(uir);
             return uirc;
         });
@@ -138,12 +156,49 @@ public class ProcessEndUserClaimsTest extends AbstractOIDCTest {
                 prc.getInboundMessageContext().getSubcontext(EndUserClaimsContext.class);
         assertNull(claimsContext.getEndUserClaims().getClaim("iss"));
         assertNull(claimsContext.getEndUserClaims().getClaim("aud"));
-        assertEquals(claimsContext.getEndUserClaims().getClaim("sub"), "joe");
+        assertEquals(claimsContext.getEndUserClaims().getClaim("sub"), "jdoe");
         assertEquals(claimsContext.getEndUserClaims().getClaim("family_name"), "blogs");
         assertEquals(claimsContext.getEndUserClaims().getClaim("given_name"), "joe");
         assertEquals(claimsContext.getEndUserClaims().getClaim("email"), "joe at example.com");
     }
     
+    @Test
+    public void testSuccesfulMerge_ExpirySanatized() throws ComponentInitializationException {
+        
+        action.initialize();
+        final Event event = action.execute(src);
+        assertNull(event);
+        assertNotNull(prc.getInboundMessageContext().getSubcontext(EndUserClaimsContext.class).getEndUserClaims());
+        final ClaimsSet claims =
+                prc.getInboundMessageContext().getSubcontext(EndUserClaimsContext.class).getEndUserClaims();
+        assertEquals(claims.getClaim("name"),"jdoe");
+        assertEquals(claims.getClaim("sub"),"jdoe");
+        assertEquals(claims.getClaim("given_name"),"joe");
+        assertEquals(claims.getClaim("email"),"joe at example.com");
+        assertNull(claims.getIssuer());
+        assertNull(claims.getAudience());
+        assertNull(claims.getDateClaim(JWTClaims.EXPIRATION_TIME_CLAIM.getClaimName()));
+    }
+    
+    @Test
+    public void testSuccesfulMerge_NoSanitization() throws ComponentInitializationException {
+        
+        action.setEnableClaimSanitizationStrategy(false);
+        action.initialize();
+        final Event event = action.execute(src);
+        assertNull(event);
+        assertNotNull(prc.getInboundMessageContext().getSubcontext(EndUserClaimsContext.class).getEndUserClaims());
+        final ClaimsSet claims =
+                prc.getInboundMessageContext().getSubcontext(EndUserClaimsContext.class).getEndUserClaims();
+        assertEquals(claims.getClaim("name"),"jdoe");
+        assertEquals(claims.getClaim("sub"),"jdoe");
+        assertEquals(claims.getClaim("given_name"),"joe");
+        assertEquals(claims.getClaim("email"),"joe at example.com");
+        assertNotNull(claims.getIssuer());
+        assertNotNull(claims.getAudience());
+        assertNotNull(claims.getDateClaim(JWTClaims.EXPIRATION_TIME_CLAIM.getClaimName()));
+    }
+    
     @Test
     public void testSuccess_NoUserInfoClaims() throws Exception {
         
@@ -157,18 +212,20 @@ public class ProcessEndUserClaimsTest extends AbstractOIDCTest {
                 prc.getInboundMessageContext().getSubcontext(EndUserClaimsContext.class);
         assertNull(claimsContext.getEndUserClaims().getClaim("iss"));
         assertNull(claimsContext.getEndUserClaims().getClaim("aud"));
-        assertEquals(claimsContext.getEndUserClaims().getClaim("sub"), "joe");
+        assertEquals(claimsContext.getEndUserClaims().getClaim("sub"), "jdoe");
         assertEquals(claimsContext.getEndUserClaims().getClaim("family_name"), "blogs");
         assertNull(claimsContext.getEndUserClaims().getClaim("given_name"), "joe");
         assertNull(claimsContext.getEndUserClaims().getClaim("email"), "joe at example.com");
     }
     
     @Test
-    public void testSuccess_NoIDToken() throws Exception {
+    public void testSuccess_NoOIDCToken() throws Exception {
         
         action.setAccessTokenResponseContextLookupStrategy(prc -> {
             final AccessTokenResponseContext atrc = new AccessTokenResponseContext();
-            atrc.setIdToken(null);
+            final OIDCTokenResponse tokenResponse = Mockito.mock(OIDCTokenResponse.class);
+            Mockito.when(tokenResponse.getOIDCTokens()).thenReturn(null);
+            atrc.setTokenResponse(tokenResponse); 
             return atrc;
         });
         
@@ -184,10 +241,15 @@ public class ProcessEndUserClaimsTest extends AbstractOIDCTest {
         
         action.setAccessTokenResponseContextLookupStrategy(prc -> {
             final AccessTokenResponseContext atrc = new AccessTokenResponseContext();
+            final OIDCTokenResponse tokenResponse = Mockito.mock(OIDCTokenResponse.class);
+            atrc.setTokenResponse(tokenResponse);
             try {
-                atrc.setIdToken(new PlainJWT(new ClaimsSet().toJWTClaimsSet()));
+                final OIDCTokens tokens = new OIDCTokens(new PlainJWT(new ClaimsSet().toJWTClaimsSet()), 
+                        new BearerAccessToken("access_token_value"), 
+                        new RefreshToken("refresh_token_value"));
+                Mockito.when(tokenResponse.getOIDCTokens()).thenReturn(tokens);
             } catch (final ParseException e) {
-               fail(e.getMessage());
+               fail(e.getMessage());              
             }
             return atrc;
         });
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TokenResponseIDTokenLookupStrategyTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TokenResponseIDTokenLookupStrategyTest.java
deleted file mode 100644
index 639a6c0..0000000
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TokenResponseIDTokenLookupStrategyTest.java
+++ /dev/null
@@ -1,75 +0,0 @@
-package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
-
-import java.io.ByteArrayInputStream;
-import java.util.Map;
-
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
-import net.shibboleth.idp.plugin.authn.oidc.rp.context.navigate.TokenResponseIDTokenLookupStrategy;
-
-/** Tests for TokenResponseIDTokenLookupStrategy.*/
-public class TokenResponseIDTokenLookupStrategyTest extends AbstractOIDCTest {
-    
-    /** The strategy to test.*/
-    private TokenResponseIDTokenLookupStrategy strategy;
-    
-    
-    @BeforeMethod
-    public void setup() throws Exception {
-        super.setup();
-        strategy = new TokenResponseIDTokenLookupStrategy();
-        ObjectMapper mapper = new ObjectMapper();
-        
-        final Map<String, Object> rawResponse = mapper.readValue(
-                new ByteArrayInputStream(ACCESS_TOKEN_RESPONSE.getBytes()), new TypeReference<Map<String, Object>>() {});
-        final AccessTokenResponseContext trc = new AccessTokenResponseContext();
-        trc.setRawTokenResponse(rawResponse);
-        prc.getInboundMessageContext().addSubcontext(trc);
-        
-    }
-    
-    @Test
-    public void testLookup_Success() {
-        final String rawToken = strategy.apply(prc);
-        assertNotNull(rawToken);
-        assertEquals(rawToken, "eyJraWQiOiJiNjliYzcyOS05NDJjLTQzNjItYmM2YS0"
-                + "3OWU3MjAwOWY0YzgiLCJhbGciOiJSUzI1NiJ9.eyJhdF9oYXNoIjoiM"
-                + "0pBZmFibUx4eWVnNjJJM2JiT0RrdyIsInN1YiI6InVzZXItc3ViamVj"
-                + "dC0xMjM0NTMxIiwiYXVkIjoibXl0ZXN0Y2xpZW50IiwiaXNzIjoiaHR0"
-                + "cHM6XC9cL3d3dy5jZXJ0aWZpY2F0aW9uLm9wZW5pZC5uZXRcL3Rlc3Rc"
-                + "L2FcL3Rlc3RfcnBfcHJveHlcLyIsImV4cCI6MTY0MzI3NTQyNCwiaWF0"
-                + "IjoxNjQzMjc1MTI0fQ.igU798iSZYUR7Z6_MQQendFCqm9Pf6Xpd4Ktn"
-                + "HcPGG5FxtXPiDhlyhP_7ewSIQtzWxQ5wEatixDJWl2oRasivNCJyl6ZN"
-                + "eUG4YuT8S7xlgpUumoycBxrmomS7wgZSola8oZo8BuiRKlWzqLLtFkAC"
-                + "hGi5iJEtHfVBe3k3PyQAsWRQdtixkvdxvyLqFLznIaPmMH-OZ82w7sJf"
-                + "4bpalZSAaR5NLpyzLFw-r81IOtOVrpQtnycCw0w_JxAP16GyZXCJc56v"
-                + "CEo8rji7juk3szTOclbsYFrrd9iokWHl0qJa0G1ev5XQLsiUxYxgO1Nw"
-                + "-CAnHtESZ9w7JIfLLX1YlY73g");
-    }
-    
-    @Test
-    public void testLookup_NoResponseContext() {
-        prc.getInboundMessageContext().removeSubcontext(AccessTokenResponseContext.class);
-        final String rawToken = strategy.apply(prc);
-        assertNull(rawToken);
-       
-    }
-    
-    @Test
-    public void testLookup_NoInboundContext() {
-        prc.setInboundMessageContext(null);
-        final String rawToken = strategy.apply(prc);
-        assertNull(rawToken);
-       
-    }
-
-}
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/UserInfoEndpointLookupTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/UserInfoEndpointLookupTest.java
index 45c7694..ce81ceb 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/UserInfoEndpointLookupTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/UserInfoEndpointLookupTest.java
@@ -21,11 +21,10 @@ package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertNull;
+import static org.testng.Assert.fail;
 
-import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.util.Map;
 
 import javax.annotation.Nonnull;
 
@@ -35,7 +34,6 @@ import org.apache.http.client.HttpClient;
 import org.apache.http.client.methods.HttpUriRequest;
 import org.apache.http.client.methods.RequestBuilder;
 import org.apache.http.client.utils.URIBuilder;
-import org.apache.http.entity.ContentType;
 import org.apache.http.entity.StringEntity;
 import org.apache.http.protocol.HttpContext;
 import org.mockito.Mockito;
@@ -46,16 +44,16 @@ import org.springframework.webflow.execution.Event;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.common.contenttype.ContentType;
 import com.nimbusds.jose.util.StandardCharset;
-import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
+import com.nimbusds.openid.connect.sdk.UserInfoErrorResponse;
+import com.nimbusds.openid.connect.sdk.UserInfoSuccessResponse;
+import com.nimbusds.openid.connect.sdk.claims.UserInfo;
 
-import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.UserInfoResponseContext;
-import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.PlainUserInfoResponse;
 import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
+import net.shibboleth.oidc.profile.core.OidcEventIds;
 
 /**
  * Tests for the {@link UserInfoEndpointLookup} action.
@@ -116,19 +114,15 @@ public class UserInfoEndpointLookupTest extends AbstractOIDCTest {
             
             // Add headers and create request.
             final RequestBuilder rb = RequestBuilder.post().setUri(uri)
-                    .setHeader("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType())
+                    .setHeader("Content-Type", ContentType.APPLICATION_URLENCODED.getType())
                     .setCharset(StandardCharset.UTF_8);
             return rb.build();
         });     
         action.setHttpResponseDecoderStrategy(response -> {
-            final ObjectMapper mapper = new ObjectMapper();
             try {                
-                final Map<String, Object> responseMap = mapper.readValue(
-                        httpResponse.getEntity().getContent(), new TypeReference<Map<String, Object>>() {});
-                final ClaimsSet claims = new ClaimsSet();
-                claims.putAll(responseMap);
-                return new PlainUserInfoResponse(claims);
-            } catch (final UnsupportedOperationException | IOException e) {                
+                return new UserInfoSuccessResponse(new UserInfo(convertHttpResponseToJSONObject(httpResponse)));
+            } catch (final Exception e) { 
+                fail(e.getMessage());
                 return null;
             }
         });
@@ -141,10 +135,63 @@ public class UserInfoEndpointLookupTest extends AbstractOIDCTest {
         assertNotNull(prc.getInboundMessageContext().getSubcontext(UserInfoResponseContext.class));
         assertNotNull(prc.getInboundMessageContext().getSubcontext(UserInfoResponseContext.class)
                 .getUserInfo());
+        assertEquals(prc.getInboundMessageContext().getSubcontext(UserInfoResponseContext.class)
+                .getUserInfo().getEntityContentType(), ContentType.APPLICATION_JSON);
         assertNotNull(prc.getInboundMessageContext().getSubcontext(UserInfoResponseContext.class)
-                .getUserInfo().getClaimsSet());
-        assertNotNull(prc.getInboundMessageContext().getSubcontext(UserInfoResponseContext.class)
-                .getUserInfo().getClaimsSet().getClaim("sub"),"248289761001");
+                .getUserInfo().getUserInfo().getClaim("sub"),"248289761001");
+
+    }
+    
+    @Test
+    public void testUserInfoLookup_ErrorResponse() throws Exception {
+
+        final HttpClient httpClient = Mockito.mock(HttpClient.class);
+        final HttpResponse httpResponse = Mockito.mock(HttpResponse.class);
+        final StatusLine statusLine = Mockito.mock(StatusLine.class);
+
+        Mockito.when(httpResponse.getStatusLine()).thenReturn(statusLine);
+
+        Mockito.when(statusLine.getStatusCode()).thenReturn(401);
+        Mockito.when(httpResponse.getEntity()).thenReturn(new StringEntity(TOKEN_RESPONSE_ERROR));
+        Mockito.when(httpClient.execute((HttpUriRequest) Mockito.any(), (HttpContext) Mockito.any()))
+                .thenReturn(httpResponse);
+
+        // create new client with mock response
+        action.setHttpClient(httpClient);
+        
+        action.setHttpRequestEncoderStrategy(prc -> {
+            URI uri;
+            try {
+                uri = new URIBuilder().setScheme("https")
+                        .setHost("op.example.com")
+                        .setPath("/userinfo")
+                        .build();
+            } catch (final URISyntaxException e) {
+                return null;
+            }                 
+            
+            // Add headers and create request.
+            final RequestBuilder rb = RequestBuilder.post().setUri(uri)
+                    .setHeader("Content-Type", ContentType.APPLICATION_URLENCODED.getType())
+                    .setCharset(StandardCharset.UTF_8);
+            return rb.build();
+        });     
+        action.setHttpResponseDecoderStrategy(response -> {
+            try {                
+                return UserInfoErrorResponse.parse("Bearer realm=\"example.com\",\n"
+                        + "                   error=\"invalid_token\",\n"
+                        + "                   error_description=\"The access token expired\"");
+            } catch (final Exception e) {      
+                fail(e.getMessage());
+                return null;
+            }
+        });
+        
+        action.initialize();
+
+        final Event event = action.execute(src);
+        assertNotNull(event);
+        assertEquals(event.getId(), OidcEventIds.INVALID_USERINFO_CLAIMS);
 
     }
     
@@ -168,12 +215,13 @@ public class UserInfoEndpointLookupTest extends AbstractOIDCTest {
                         .setPath("/userinfo")
                         .build();
             } catch (final URISyntaxException e) {
+                fail(e.getMessage());
                 return null;
             }                 
             
             // Add headers and create request.
             final RequestBuilder rb = RequestBuilder.post().setUri(uri)
-                    .setHeader("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType())
+                    .setHeader("Content-Type", ContentType.APPLICATION_URLENCODED.getType())
                     .setCharset(StandardCharset.UTF_8);
             return rb.build();
         });     
@@ -185,7 +233,7 @@ public class UserInfoEndpointLookupTest extends AbstractOIDCTest {
 
         //Null is success
         assertNotNull(event);
-        assertEquals(event.getId(), AuthnEventIds.AUTHN_EXCEPTION);
+        assertEquals(event.getId(),OidcEventIds.INVALID_USERINFO_CLAIMS);
 
     }
 
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOAuthAccessTokenResponseTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOAuthAccessTokenResponseTest.java
index 0c57ed7..88b9f4c 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOAuthAccessTokenResponseTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOAuthAccessTokenResponseTest.java
@@ -1,17 +1,33 @@
 package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
 
+import static org.junit.Assert.assertNotNull;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNull;
 
+import java.time.Duration;
+import java.time.Instant;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.StatusLine;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.mockito.Mockito;
 import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.messaging.context.navigate.ParentContextLookup;
 import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.http.MediaType;
 import org.springframework.webflow.execution.Event;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.jwt.PlainJWT;
+import com.nimbusds.oauth2.sdk.Scope;
+import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
+import com.nimbusds.oauth2.sdk.token.DPoPAccessToken;
+import com.nimbusds.oauth2.sdk.token.RefreshToken;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+import com.nimbusds.openid.connect.sdk.token.OIDCTokens;
 
 import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
@@ -24,6 +40,16 @@ public class ValidateOAuthAccessTokenResponseTest extends AbstractOIDCTest {
     /** The action to test.*/
     private ValidateOAuthAccessTokenResponse action;
     
+    private HttpResponse httpResponse;
+    
+    private AccessTokenResponseContext atrc;
+    
+    private OIDCTokenResponse tokenResponse;
+    
+    private OIDCTokens tokens;
+    
+    private JWTClaimsSet claimsSet;
+    
     
     @Override
     @BeforeMethod
@@ -36,27 +62,37 @@ public class ValidateOAuthAccessTokenResponseTest extends AbstractOIDCTest {
                 .compose(new WebflowRequestContextProfileRequestContextLookup())));     
         
         action.setAuthenticationContextLookupStrategy(new ParentContextLookup<>(AuthenticationContext.class));
+        
+        httpResponse = Mockito.mock(HttpResponse.class);
+        final StatusLine statusLine = Mockito.mock(StatusLine.class);
+        Mockito.when(httpResponse.getStatusLine()).thenReturn(statusLine);
+        Mockito.when(statusLine.getStatusCode()).thenReturn(200);
+        Mockito.when(httpResponse.getEntity()).thenReturn(new StringEntity(ACCESS_TOKEN_RESPONSE, 
+                ContentType.parse(MediaType.APPLICATION_JSON_VALUE)));
+
+        atrc = Mockito.mock(AccessTokenResponseContext.class);
+        tokenResponse = Mockito.mock(OIDCTokenResponse.class);
+        Mockito.when(atrc.getTokenResponse()).thenReturn(tokenResponse);
+        
+        claimsSet = new JWTClaimsSet.Builder()
+              .issuer("https://op.example.com")
+              .audience("https://rp.example.com")
+              .subject("joe")
+              .claim("family_name", "blogs")
+              .build();
+        
+        tokens = new OIDCTokens(new PlainJWT(claimsSet), 
+                new BearerAccessToken("access_token_value", 3600, new Scope("openid")), 
+                new RefreshToken("refresh_token_value"));
+        Mockito.when(tokenResponse.getOIDCTokens()).thenReturn(tokens);
+        Mockito.when(tokenResponse.getTokens()).thenReturn(tokens);
+        Mockito.when(tokenResponse.indicatesSuccess()).thenReturn(true);
 
     }
     
     @Test
     public void testSuccesfulTokenResponse() throws Exception {
-        action.setTokenResponseContextLookupStrategy(prc -> {
-            final AccessTokenResponseContext atrc = new AccessTokenResponseContext();
-            final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
-                    .issuer("https://op.example.com")
-                    .audience("https://rp.example.com")
-                    .subject("joe")
-                    .claim("family_name", "blogs")
-                    .build();
-            atrc.setIdToken(new PlainJWT(claimsSet));
-            atrc.setRawTokenResponse(claimsSet.toJSONObject());
-            atrc.getRawTokenResponse().put("access_token", "access-token");
-            atrc.getRawTokenResponse().put("token_type","Bearer");
-            atrc.getRawTokenResponse().put("expires_in","3600");
-            atrc.getRawTokenResponse().put("id_token",new PlainJWT(claimsSet).serialize());
-            return atrc;
-        });
+        action.setTokenResponseContextLookupStrategy(prc -> atrc);
         
         action.initialize();
         final Event event = action.execute(src);
@@ -66,68 +102,26 @@ public class ValidateOAuthAccessTokenResponseTest extends AbstractOIDCTest {
     }
     
     @Test
-    public void testError_NoAccessToken() throws Exception {
-        action.setTokenResponseContextLookupStrategy(prc -> {
-            final AccessTokenResponseContext atrc = new AccessTokenResponseContext();
-            final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
-                    .issuer("https://op.example.com")
-                    .audience("https://rp.example.com")
-                    .subject("joe")
-                    .claim("family_name", "blogs")
-                    .build();
-            atrc.setIdToken(new PlainJWT(claimsSet));
-            atrc.setRawTokenResponse(claimsSet.toJSONObject());
-            atrc.getRawTokenResponse().put("token_type","Bearer");
-            atrc.getRawTokenResponse().put("expires_in","3600");
-            atrc.getRawTokenResponse().put("id_token",new PlainJWT(claimsSet).serialize());
-            return atrc;
-        });
+    public void testSuccesfulTokenResponse_NoExpiry() throws Exception {
+        tokens = new OIDCTokens(new PlainJWT(claimsSet), 
+                new BearerAccessToken("access_token_value", 0, new Scope("openid")), 
+                new RefreshToken("refresh_token_value"));
+        Mockito.when(tokenResponse.getOIDCTokens()).thenReturn(tokens);
+        Mockito.when(tokenResponse.getTokens()).thenReturn(tokens);
+        action.setTokenResponseContextLookupStrategy(prc -> atrc);
         
         action.initialize();
         final Event event = action.execute(src);
-
-        assertEquals(event.getId(), OidcEventIds.INVALID_ACCESS_TOKEN);
-    }
-    
-    @Test
-    public void testError_NoIDToken() throws Exception {
-        action.setTokenResponseContextLookupStrategy(prc -> {
-            final AccessTokenResponseContext atrc = new AccessTokenResponseContext();
-            final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
-                    .issuer("https://op.example.com")
-                    .audience("https://rp.example.com")
-                    .subject("joe")
-                    .claim("family_name", "blogs")
-                    .build();
-            atrc.setIdToken(new PlainJWT(claimsSet));
-            atrc.setRawTokenResponse(claimsSet.toJSONObject());
-            atrc.getRawTokenResponse().put("access_token", "access-token");
-            atrc.getRawTokenResponse().put("token_type","Bearer");
-            atrc.getRawTokenResponse().put("expires_in","3600");
-            return atrc;
-        });
         
-        action.initialize();
-        final Event event = action.execute(src);
-        
-        assertEquals(event.getId(), OidcEventIds.INVALID_ACCESS_TOKEN);
+        //Null event means response valid
+        assertNull(event);
     }
+
     
     @Test
-    public void testError_NoTokenType() throws Exception {
+    public void testError_NoTokenResponse() throws Exception {
         action.setTokenResponseContextLookupStrategy(prc -> {
-            final AccessTokenResponseContext atrc = new AccessTokenResponseContext();
-            final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
-                    .issuer("https://op.example.com")
-                    .audience("https://rp.example.com")
-                    .subject("joe")
-                    .claim("family_name", "blogs")
-                    .build();
-            atrc.setIdToken(new PlainJWT(claimsSet));
-            atrc.setRawTokenResponse(claimsSet.toJSONObject());
-            atrc.getRawTokenResponse().put("access_token", "access-token");
-            atrc.getRawTokenResponse().put("expires_in","3600");
-            atrc.getRawTokenResponse().put("id_token",new PlainJWT(claimsSet).serialize());
+            final AccessTokenResponseContext atrc = new AccessTokenResponseContext();            
             return atrc;
         });
         
@@ -138,62 +132,53 @@ public class ValidateOAuthAccessTokenResponseTest extends AbstractOIDCTest {
     }
     
     @Test
-    public void testError_NoRawTokenResponse() throws Exception {
-        action.setTokenResponseContextLookupStrategy(prc -> {
-            final AccessTokenResponseContext atrc = new AccessTokenResponseContext();            
-            return atrc;
-        });
+    public void testError_WrongTokenType() throws Exception {
+        tokens = new OIDCTokens(new PlainJWT(claimsSet), 
+                new DPoPAccessToken("access_token_value", 0, new Scope("openid")), 
+                new RefreshToken("refresh_token_value"));
+        Mockito.when(tokenResponse.getOIDCTokens()).thenReturn(tokens);
+        Mockito.when(tokenResponse.getTokens()).thenReturn(tokens);
+        action.setTokenResponseContextLookupStrategy(prc -> atrc);
         
         action.initialize();
         final Event event = action.execute(src);
-        
+        assertNotNull(event);
         assertEquals(event.getId(), OidcEventIds.INVALID_ACCESS_TOKEN);
     }
     
     @Test
-    public void testError_WrongTokenType() throws Exception {
-        action.setTokenResponseContextLookupStrategy(prc -> {
-            final AccessTokenResponseContext atrc = new AccessTokenResponseContext();
-            final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
-                    .issuer("https://op.example.com")
-                    .audience("https://rp.example.com")
-                    .subject("joe")
-                    .claim("family_name", "blogs")
-                    .build();
-            atrc.setIdToken(new PlainJWT(claimsSet));
-            atrc.setRawTokenResponse(claimsSet.toJSONObject());
-            atrc.getRawTokenResponse().put("access_token", "access-token");
-            atrc.getRawTokenResponse().put("token_type","WrongType");
-            atrc.getRawTokenResponse().put("expires_in","3600");
-            atrc.getRawTokenResponse().put("id_token",new PlainJWT(claimsSet).serialize());
-            return atrc;
-        });
+    public void testError_TokenExpired() throws Exception {
+        tokens = new OIDCTokens(new PlainJWT(claimsSet), 
+                new BearerAccessToken("access_token_value", 1, new Scope("openid")), 
+                new RefreshToken("refresh_token_value"));
         
+        Mockito.when(tokenResponse.getOIDCTokens()).thenReturn(tokens);
+        Mockito.when(tokenResponse.getTokens()).thenReturn(tokens);
+        Mockito.when(atrc.getTokenResponseCreatedAt()).thenReturn(Instant.now().minus(Duration.ofSeconds(10)));
+        action.setTokenResponseContextLookupStrategy(prc -> atrc);
+      
         action.initialize();
         final Event event = action.execute(src);
-        
+        assertNotNull(event);
         assertEquals(event.getId(), OidcEventIds.INVALID_ACCESS_TOKEN);
     }
     
+    /**
+     * Fake an error response using a successful token response. Otherwise we can not set the wrong type on the context.
+     * 
+     * @throws Exception on error
+     */
     @Test
     public void testError_ErrorResponse() throws Exception {
-        action.setTokenResponseContextLookupStrategy(prc -> {
-            final AccessTokenResponseContext atrc = new AccessTokenResponseContext();
-            final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
-                    .issuer("https://op.example.com")
-                    .audience("https://rp.example.com")
-                    .subject("joe")
-                    .claim("family_name", "blogs")
-                    .build();
-            atrc.setIdToken(new PlainJWT(claimsSet));
-            atrc.setRawTokenResponse(claimsSet.toJSONObject());
-            atrc.getRawTokenResponse().put("error", "invalid_request");
-            return atrc;
-        });
+        final OIDCTokenResponse tokenErrorResponse = Mockito.mock(OIDCTokenResponse.class);
+        Mockito.when(tokenErrorResponse.indicatesSuccess())
+            .thenReturn(false);
+        Mockito.when(atrc.getTokenResponse()).thenReturn(tokenErrorResponse);
+        action.setTokenResponseContextLookupStrategy(prc -> atrc);
         
         action.initialize();
         final Event event = action.execute(src);
-        
+        assertNotNull(event);
         assertEquals(event.getId(), OidcEventIds.INVALID_ACCESS_TOKEN);
     }
 
diff --git a/pom.xml b/pom.xml
index f74f974..79b06e1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -125,7 +125,7 @@
                 <artifactId>java-support</artifactId>
                 <version>${java-support.version}</version>
                 <scope>provided</scope>
-            </dependency>
+            </dependency>         
             <!-- Test bom dependencies -->
             <dependency>
                 <groupId>${idp.groupId}</groupId>
@@ -152,6 +152,13 @@
                 <version>${oidc.common.version}</version>
                 <type>test-jar</type>
                 <scope>test</scope>
+            </dependency>            
+            <dependency>
+                <groupId>net.shibboleth.idp.plugin.authn</groupId>
+                <artifactId>idp-plugin-oidc-rp-api</artifactId>
+                <version>${project.version}</version>
+                <scope>test</scope>
+                <type>test-jar</type>
             </dependency>
         </dependencies>
     </dependencyManagement>

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


More information about the commits mailing list