[java-idp-plugin-oidc-rp] branch main updated: Add UserInfo token exchange and UserInfo claim validation

Phil Smart philip.smart at jisc.ac.uk
Thu Feb 10 10:06:56 UTC 2022


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=ebaa1b4b2958dad9a14914ee564e6895f7837d31

The following commit(s) were added to refs/heads/main by this push:
     new ebaa1b4  Add UserInfo token exchange and UserInfo claim validation
ebaa1b4 is described below

commit ebaa1b4b2958dad9a14914ee564e6895f7837d31
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Thu Feb 10 10:06:50 2022 +0000

    Add UserInfo token exchange and UserInfo claim validation
    
    Supports JWT UserInfo tokens (pending signature validation and
    decryption) and plain JSON object tokens.
    
    Merge claims from id_token and UserInfo claims.
---
 ...ontext.java => AccessTokenResponseContext.java} |   2 +-
 .../oidc/rp/context/EndUserClaimsContext.java      |  53 +++
 .../oidc/rp/context/OpenIDConnectContext.java      | 454 ---------------------
 .../oidc/rp/context/UserInfoResponseContext.java   |  51 +++
 .../oidc/rp/messaging/JWTUserInfoResponse.java     | 107 +++++
 .../oidc/rp/messaging/PlainUserInfoResponse.java   |  76 ++++
 .../authn/oidc/rp/messaging/UserInfoResponse.java  |  71 ++++
 .../oidc/rp/messaging/JWTUserInfoResponseTest.java | 102 +++++
 idp-oidc-rp-impl/pom.xml                           |  20 +
 ...va => AbstractJSONResponseDecoderFunction.java} |  68 +--
 ...Decoder.java => DefaultMapResponseDecoder.java} |  46 +--
 .../impl/DefaultUserInfoResponseDecoder.java       | 106 +++++
 .../encoding/impl/DefaultTokenRequestEncoder.java  |   1 +
 .../impl/DefaultUserInfoRequestEncoder.java        | 116 ++++++
 .../impl/AbstractHttpOIDCAuthenticationAction.java | 101 ++++-
 .../AbstractOIDCAuthenticationResponseAction.java  |   2 +-
 .../plugin/authn/oidc/rp/impl/AddAuthzRequest.java |  10 +-
 .../oidc/rp/impl/AuthorizationController.java      |   3 -
 .../oidc/rp/impl/DefaultClaimMergingStrategy.java  |  88 ++++
 .../oidc/rp/impl/DefaultIDTokenLookupStrategy.java |  53 ++-
 .../impl/DefaultUserInfoTokenLookupStrategy.java   |  78 ++++
 .../oidc/rp/impl/ExchangeCodeForAccessToken.java   |  88 +---
 .../oidc/rp/impl/ExtractIDTokenFromResponse.java   |  30 +-
 .../rp/impl/MergeUserInfoAndIDTokenClaims.java     | 218 ++++++++++
 .../impl/TokenResponseIDTokenLookupStrategy.java   |   4 +-
 .../authn/oidc/rp/impl/UserInfoEndpointLookup.java |  99 +++++
 .../oidc/rp/impl/ValidateIDTokenSignature.java     |  61 +--
 .../rp/impl/ValidateOAuthAccessTokenResponse.java  |  31 +-
 .../oidc/rp/impl/ValidateOIDCAuthentication.java   | 307 +++++++++-----
 .../authn/oidc/rp/impl/ValidateTokenClaims.java    |  28 +-
 .../authn/oidc/rp/impl/ValidateUserInfoClaims.java | 171 ++++++++
 .../META-INF/net.shibboleth.idp/postconfig.xml     |   8 +
 .../oidc-relying-party-authn-beans.xml             | 112 ++++-
 .../oidc-relying-party-authn-flow.xml              |  61 ++-
 .../attribute/registry/oidc-claim-rules.xml        | 428 +++++++++++++++++++
 .../idp/service/attribute/registry/postconfig.xml  |  25 ++
 .../resources/templates/oidc-request-form-post.vm  |   4 +-
 .../impl/DefaultTokenResponseDecoderTest.java      |   4 +-
 .../authn/oidc/rp/impl/AbstractOIDCTest.java       |  10 +
 .../rp/impl/DefaultClaimMergingStrategyTest.java   |  97 +++++
 .../oidc/rp/impl/ExchangeCodeForTokenTest.java     |  17 +-
 .../rp/impl/ExtractIDTokenFromResponseTest.java    |   6 +-
 .../plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java  | 374 +++++++++++++----
 .../TokenResponseIDTokenLookupStrategyTest.java    |   6 +-
 .../attribute/registry/attribute-registry.xml      | 442 ++++++++++++++++++++
 .../resources/attribute/registry/postconfig.xml    |  67 +++
 .../src/test/resources/logback-test.xml            |   1 +
 pom.xml                                            |  12 +
 48 files changed, 3370 insertions(+), 949 deletions(-)

diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/TokenResponseContext.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/AccessTokenResponseContext.java
similarity index 97%
rename from idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/TokenResponseContext.java
rename to idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/AccessTokenResponseContext.java
index 104db77..b8a16a9 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/TokenResponseContext.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/AccessTokenResponseContext.java
@@ -26,7 +26,7 @@ import org.opensaml.messaging.context.BaseContext;
 import com.nimbusds.jwt.JWT;
 
 /** A context to hold an OIDC token request response.*/
-public class TokenResponseContext extends BaseContext {
+public class AccessTokenResponseContext extends BaseContext {
     
     /** The raw token response as a map.*/
     @Nullable private Map<String, Object> rawTokenResponse;
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/EndUserClaimsContext.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/EndUserClaimsContext.java
new file mode 100644
index 0000000..f4c528b
--- /dev/null
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/EndUserClaimsContext.java
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+
+import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
+
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/** A context to hold the final set of claims associated with an authenticated end-user.*/
+public class EndUserClaimsContext extends BaseContext {
+    
+    /** The claims associated with the authenticated end-user.*/
+    @Nullable private ClaimsSet endUserClaims;
+    
+    /**
+     * Get the claims about the authenticated end-user.
+     * 
+     * @return the claims.
+     */
+    @Nullable public ClaimsSet getEndUserClaims() {
+        return endUserClaims;
+    }
+    
+    /**
+     * Set the claims about the authenticated end-user.
+     * 
+     * @param claims the claims.
+     */
+    public void setEndUserClaims(@Nonnull final ClaimsSet claims) {
+        endUserClaims = Constraint.isNotNull(claims, "Claims can not be null");
+    }
+
+}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OpenIDConnectContext.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OpenIDConnectContext.java
deleted file mode 100644
index 7c16c92..0000000
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OpenIDConnectContext.java
+++ /dev/null
@@ -1,454 +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;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.List;
-import java.util.Map;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import javax.servlet.http.HttpServletRequest;
-
-import net.shibboleth.idp.attribute.IdPAttribute;
-import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.utilities.java.support.annotation.constraint.Live;
-import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
-import net.shibboleth.utilities.java.support.annotation.constraint.NotLive;
-import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
-import net.shibboleth.utilities.java.support.logic.Constraint;
-
-import org.opensaml.messaging.context.BaseContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.base.Predicates;
-import com.google.common.collect.Collections2;
-import com.google.common.collect.ImmutableList;
-import com.nimbusds.jwt.JWT;
-import com.nimbusds.oauth2.sdk.Scope;
-import com.nimbusds.oauth2.sdk.auth.Secret;
-import com.nimbusds.oauth2.sdk.id.ClientID;
-import com.nimbusds.oauth2.sdk.id.State;
-import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
-import com.nimbusds.openid.connect.sdk.Display;
-import com.nimbusds.openid.connect.sdk.Nonce;
-import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
-import com.nimbusds.openid.connect.sdk.Prompt;
-import com.nimbusds.openid.connect.sdk.claims.ACR;
-import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
-
-
-/**
- * Context that carries OpenID Connect client and provider metadata used during authentication of a subject from
- * an OpenID Connect Provider.
- * 
- * <p>Any OPTIONAL parameters of an ODIC authentication request are allowably <code>null</code> - even Lists.
- * For a list of OPTIONAL authn parameters, see OpenID Connect Core 1.0 section 3.1.2.1</p>
- *
- * 
- * @parent {@link AuthenticationContext}
- * @added After the RelyingPartyUIContext has been added, before external redirect to the OpenID Connect servlet. 
- * 
- * @since 4.0.0
- */
-public class OpenIDConnectContext extends BaseContext {
-
-    /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(OpenIDConnectContext.class);
-
-    /** Client Id. */
-    @Nullable private ClientID clientID;
-
-    /** Client Secret. */
-    @Nullable private Secret clientSecret;
-
-    /** Scope. Must contain an openid scope.*/
-    @Nullable private Scope scope;
-
-    /**
-     *  OIDC Prompt. Specifies whether the Authorization Server prompts
-     *  the End-User for re-authentication and consent.
-     */
-    @Nullable private Prompt prompt;
-
-    /** OIDC Authentication Class Reference values.*/
-    @Nullable @NonnullElements private List<ACR> acrs;
-
-    /**
-     *  OIDC Display. Value that specifies how the Authorization Server
-     *  displays the authentication and consent user interface pages to the End-User. 
-     */
-    @Nullable private Display display;
-
-    /** OIDC provider metadata. */
-    @Nullable private OIDCProviderMetadata oIDCProviderMetadata;
-
-    /** OIDC authentication request URI. */
-    @Nullable private URI authenticationRequestURI;
-
-    /** OIDC authentication response URI. */
-    @Nullable private URI authenticationResponseURI;
-
-    /** OIDC authentication success response. */
-    @Nullable private AuthenticationSuccessResponse authSuccessResponse;
-
-    /** OIDC token response. */
-    @Nullable private OIDCTokenResponse oidcTknResponse;
-
-    /** 
-     * State parameter. Opaque value used to maintain state between the request 
-     * and the callback. Typically for CSRF protection. 
-     */
-    @Nullable private State state;
-
-    /** 
-     * Nonce parameter. String value used to associate a Client session with an 
-     * ID Token, and to mitigate replay attacks.
-     */
-    @Nullable private Nonce nonce;
-
-    /** Redirect URI to which the response will be sent. */
-    @Nullable private URI redirectURI;
-
-    /** ID Token from the token endpoint response. */
-    @Nullable private JWT idToken;
-
-    /** Resolved attributes. */
-    //TODO Resolved Attributes never get added to the Context.
-    @Nullable private Map<String, IdPAttribute> resolvedIdPAttributes;
-    
-
-    /**
-     * Get the resolved attributes.
-     * 
-     * @return resolved attributes, may be null.
-     */
-    @Nullable @Live public Map<String, IdPAttribute> getResolvedIdPAttributes() {
-        return resolvedIdPAttributes;
-    }
-
-    /**
-     * Set resolved attributes to context to help form requested objects.
-     * 
-     * @param idPAttributes resolved attributes
-     */
-    public void setResolvedIdPAttributes(@Nullable final Map<String, IdPAttribute> idPAttributes) {
-        resolvedIdPAttributes = idPAttributes;
-    }
-
-    /**
-     * Get the id token received from OpenID Connect Provider.
-     * 
-     * @return id token or <code>null</code> if not set.
-     */
-    @Nullable public JWT getIDToken() {
-        return idToken;
-    }
-
-    /**
-     * Set the id token received from OpenID Connect Provider.
-     * 
-     * @param token from the OpenID Connect Provider.
-     */
-    public void setIDToken(@Nullable final JWT token) {
-        idToken = token;
-    }
-
-    /**
-     * Get the redirect URI of the client.
-     * 
-     * @return redirect uri
-     */
-    @Nullable public URI getRedirectURI() {
-        return redirectURI;
-    }
-
-    /**
-     * Set the redirect URI of the client. The redirect URI is required for authorisation code flows. 
-     * 
-     * @param uri of the client, must not be <code>null</code>.
-     */
-    public void setRedirectURI(@Nonnull final URI uri) {
-        redirectURI = Constraint.isNotNull(uri, "Redirect URI cannot be null");
-    }
-
-    /**
-     * Get the OAuth2 client id.
-     * 
-     * @return client id.
-     */
-    @Nullable public ClientID getClientID() {
-        return clientID;
-    }
-
-    /**
-     * Set the Oauth2 client id. The client id can not be <code>null</code>.
-     * 
-     * @param id Oauth2 Client ID, must not be <code>null</code>.
-     */
-    public void setClientID(@Nonnull final ClientID id) {
-        clientID = Constraint.isNotNull(id, "Client ID cannot be null");
-    }
-
-    /**
-     * Set the client secret.
-     * 
-     * @param secret of the client, must not be <code>null</code>.
-     */
-    public void setClientSecret(@Nonnull final Secret secret) {
-        clientSecret = Constraint.isNotNull(secret, "Client secret cannot be null");
-    }
-    
-    /**
-     * Get the client secret.
-     * 
-     * @return client secret.
-     */
-    @Nullable public Secret getClientSecret() {
-        return clientSecret;
-    }
-
-    /**
-     * Get the scope used for authentication request.
-     * 
-     * @return scope
-     */
-    public Scope getScope() {
-        return scope;
-    }
-
-    /**
-     * Set the scope used for forming an authentication request.
-     * 
-     * @param scp scope of the request, must not be <code>null</code>
-     */
-    public void setScope(@Nonnull final Scope scp) {
-        scope = Constraint.isNotNull(scp, "Scope cannot be null");
-    }
-
-    /**
-     * Get the value for prompt used for forming an authentication request.
-     * 
-     * @return prompt
-     */
-    @Nullable public Prompt getPrompt() {
-        return prompt;
-    }
-
-    /**
-     * Set the value for prompt used for forming an authentication request.
-     * 
-     * @param prmpt used for forming an authentication request
-     */
-    public void setPrompt(@Nullable final Prompt prmpt) {
-        prompt = prmpt;
-    }
-
-    /**
-     * Get the values for acr used for forming authentication request. 
-     * Allowably a <code>null</code> {@link List} if not set.
-     * 
-     * @return acrs or <code>null</code> if not set.
-     */
-    @Nullable @NonnullElements @Unmodifiable @NotLive public List<ACR> getAcrs() {
-       return acrs;
-    }
-
-    /**
-     * Set the values for acr used for forming authentication request. Sets a 
-     * <code>null</code> {@link List} if no ACRs are defined.
-     * 
-     * @param acrList values used for forming authentication request
-     */
-    public void setAcrs(@Nullable @NonnullElements final List<ACR> acrList) {
-        if (acrList!=null) {
-            ImmutableList.copyOf(Collections2.filter(acrList, Predicates.notNull()));
-        } else {
-            acrs = null;           
-        }
-
-    }
-
-    /**
-     * Get the display value used for forming authentication request.
-     * 
-     * @return display
-     */
-    @Nullable public Display getDisplay() {
-        return display;
-    }
-
-    /**
-     * Set the display value used for forming authentication request.
-     * 
-     * @param dspl value for forming authentication request.
-     */
-    public void setDisplay(@Nullable final Display dspl) {
-        display = dspl;
-    }
-
-    /**
-     * Get OpenID Connect Provider metadata.
-     * 
-     * @return OpenID Connect Provider metadata
-     */
-    @Nullable public OIDCProviderMetadata getoIDCProviderMetadata() {
-        return oIDCProviderMetadata;
-    }
-
-    /**
-     * Set OpenID Connect Provider metatadata.
-     * 
-     * @param oIDCPrvdrMtdt OpenID Connect Provider metadata, must not be <code>null</code>.
-     */
-    public void setoIDCProviderMetadata(@Nonnull final OIDCProviderMetadata oIDCPrvdrMtdt) {
-        oIDCProviderMetadata = Constraint.isNotNull(oIDCPrvdrMtdt, "OpenID Connect metadata location cannot be null ");
-    }
-
-    /**
-     * Returns the oidc authentication request URI to be used for authentication.
-     * 
-     * @return request URI for authentication
-     */
-    @Nullable public URI getAuthenticationRequestURI() {
-        return authenticationRequestURI;
-    }
-
-    /**
-     * Set the oidc provider request URI for authentication.
-     * 
-     * @param request to be used for authentication, must not be <code>null</code>.
-     */
-    public void setAuthenticationRequestURI(@Nonnull final URI request) {
-        authenticationRequestURI = Constraint.isNotNull(request, 
-                "Authentication Request URI request can not be null");      
-
-    }
-
-    /**
-     * Returns the OIDC token response or null.
-     * 
-     * @return token response
-     */
-    @Nullable public OIDCTokenResponse getOidcTokenResponse() {
-        return oidcTknResponse;
-    }
-
-    /**
-     * Sets both the token response and the <code>idToken</code> from the
-     * token response if it is not <code>null</code>.
-     * 
-     * @param oidcTokenResponse response from provider
-     */
-    public void setOidcTokenResponse(@Nullable final OIDCTokenResponse oidcTokenResponse) {
-        oidcTknResponse = oidcTokenResponse;
-        if (oidcTokenResponse != null && oidcTokenResponse.getOIDCTokens() != null) {
-            idToken = oidcTokenResponse.getOIDCTokens().getIDToken();
-        }
-
-    }
-
-    /**
-     * Getter for State parameter.
-     * 
-     * @return state parameter
-     */
-    @Nullable public State getState() {
-        return state;
-    }
-
-    /**
-     * Setter for State parameter.
-     * 
-     * @param stateParam parameter
-     */
-    public void setState(@Nullable final State stateParam) {
-        state = stateParam;
-    }
-
-    /**
-     * Getter for Nonce parameter.
-     * 
-     * @return nonce parameter.
-     */
-    @Nullable public Nonce getNonce() {
-        return nonce;
-    }
-
-    /**
-     * Setter for Nonce parameter.
-     * 
-     * @param newNonce nonce parameter
-     */
-    public void setNonce(@Nullable final Nonce newNonce) {
-        nonce = newNonce;
-    }
-
-    /**
-     * Returns the authentication success response or null.
-     * 
-     * @return authentication success response.
-     */
-    @Nullable public AuthenticationSuccessResponse getAuthenticationSuccessResponse() {
-        return authSuccessResponse;
-    }
-
-    /**
-     * Sets the authentication success response.
-     * 
-     * @param authenticationSuccessResponse response from the OpenIDConenct Provider
-     */
-    public void setAuthenticationSuccessResponse(
-            @Nonnull final AuthenticationSuccessResponse authenticationSuccessResponse) {
-        authSuccessResponse = Constraint.isNotNull(authenticationSuccessResponse, 
-                "AuthenticationSuccessResponse can not be null");      
-
-    }
-
-    /**
-     * Returns the authentication response URI or null.
-     * 
-     * @return authentication response URI
-     */
-    @Nullable public URI getAuthenticationResponseURI() {
-        return authenticationResponseURI;
-    }
-
-    /**
-     * Parses the authentication response URI from the authenticationResponseHttpRequest.
-     * 
-     * @param authenticationResponseHttpRequest request
-     * 
-     * @throws URISyntaxException if request has malformed URL and/or query parameters
-     */
-    public void setAuthenticationResponseURI(@Nonnull final HttpServletRequest authenticationResponseHttpRequest)
-            throws URISyntaxException {
-
-        Constraint.isNotNull(authenticationResponseHttpRequest, "Authentication response http request can not be null");
-        
-        final String temp = authenticationResponseHttpRequest.getRequestURL() + "?"
-                + authenticationResponseHttpRequest.getQueryString();
-        authenticationResponseURI = new URI(temp);
-
-    }
-
-  
-
-}
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
new file mode 100644
index 0000000..3ad71c8
--- /dev/null
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/UserInfoResponseContext.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.UserInfoResponse;
+
+
+/** A context to hold the response from a UserInfo endpoint.*/
+public class UserInfoResponseContext  extends BaseContext {
+    
+    /** The UserInfo response.*/
+    @Nullable private UserInfoResponse userInfo;
+    
+    /**
+     * Get the user info response.
+     * 
+     * @return the user info.
+     */
+    @Nullable public UserInfoResponse getUserInfo() {
+        return userInfo;
+    }
+    
+    /**
+     * Set the user info response.
+     * 
+     * @param info the user info response.
+     */
+    public void setUserInfo(@Nullable final UserInfoResponse info) {
+        userInfo = info;
+    }
+
+}
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
new file mode 100644
index 0000000..82a0423
--- /dev/null
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/JWTUserInfoResponse.java
@@ -0,0 +1,107 @@
+/*
+ * 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;
+    }
+
+}
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
new file mode 100644
index 0000000..25587d0
--- /dev/null
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/PlainUserInfoResponse.java
@@ -0,0 +1,76 @@
+/*
+ * 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;
+    }
+
+}
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
new file mode 100644
index 0000000..a7dc0ca
--- /dev/null
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/UserInfoResponse.java
@@ -0,0 +1,71 @@
+/*
+ * 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 {
+    
+    /**
+     * 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();
+
+}
+
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
new file mode 100644
index 0000000..63f23fe
--- /dev/null
+++ b/idp-oidc-rp-api/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/JWTUserInfoResponseTest.java
@@ -0,0 +1,102 @@
+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/pom.xml b/idp-oidc-rp-impl/pom.xml
index a209dfd..dcb64d2 100644
--- a/idp-oidc-rp-impl/pom.xml
+++ b/idp-oidc-rp-impl/pom.xml
@@ -76,6 +76,16 @@
             <artifactId>oidc-common-metadata-api</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>net.shibboleth.oidc</groupId>
+            <artifactId>oidc-common-attribute-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>net.shibboleth.oidc</groupId>
+            <artifactId>oidc-common-attribute-impl</artifactId>
+            <scope>provided</scope>
+        </dependency>
         <dependency>
             <groupId>net.shibboleth.oidc</groupId>
             <artifactId>oidc-common-profile-api</artifactId>
@@ -139,6 +149,16 @@
             <scope>test</scope>
             <type>test-jar</type>
         </dependency>
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>mockwebserver</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp-tls</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultTokenResponseDecoder.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/AbstractJSONResponseDecoderFunction.java
similarity index 51%
copy from idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultTokenResponseDecoder.java
copy to idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/AbstractJSONResponseDecoderFunction.java
index 4950a60..d14c86c 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultTokenResponseDecoder.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/AbstractJSONResponseDecoderFunction.java
@@ -17,20 +17,13 @@
 
 package net.shibboleth.idp.plugin.authn.oidc.rp.decoding.impl;
 
-import java.util.Map;
 import java.util.function.Function;
 
 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.fasterxml.jackson.databind.ObjectMapper;
-import com.nimbusds.jose.util.IOUtils;
 
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
@@ -38,26 +31,17 @@ import net.shibboleth.utilities.java.support.component.ComponentInitializationEx
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 
-/** Default token response decoder, which converts a succesful HTTP response into an Map.*/
-public class DefaultTokenResponseDecoder extends AbstractInitializableComponent
-        implements Function<HttpResponse, Map<String, Object>> {
+/**
+ * Abstract class for JSON based response decoders. 
+ *
+ * @param <T> the return type of the function.
+ */
+public abstract class AbstractJSONResponseDecoderFunction<T> extends AbstractInitializableComponent 
+                                                    implements Function<HttpResponse, T> {
     
-    /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultTokenResponseDecoder.class);
-
     /** JSON object mapper. */
     @NonnullAfterInit private ObjectMapper objectMapper;
     
-    
-    @Override
-    protected void doInitialize() throws ComponentInitializationException {
-        super.doInitialize();
-        
-        if (objectMapper == null) {
-            throw new ComponentInitializationException("objectMapper cannot be null");
-        }
-    }
-
     /**
      * Set the JSON Object Mapper to use.
      * 
@@ -69,33 +53,23 @@ public class DefaultTokenResponseDecoder extends AbstractInitializableComponent
 
         objectMapper = Constraint.isNotNull(mapper, "ObjectMapper cannot be null");
     }
-
-    //TODO we should handle the error response better? just return it and make the decision later?
+    
+    /**
+     * Get the object mapper.
+     * 
+     * @return the object mapper.
+     */
+    @NonnullAfterInit public ObjectMapper getObjectMapper() {
+        return objectMapper;
+    }
+    
     @Override
-    @Nullable public Map<String, Object> apply(@Nonnull final HttpResponse httpResponse) {
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
         
-        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("Token endpoint returned a Non-ok message of '{}'",errorContent);
-                }
-                log.warn("Non-ok status code ({}) returned from token endpoint: {}", httpStatusCode, 
-                        httpResponse.getStatusLine().getReasonPhrase());
-                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 objectMapper.readValue(httpResponse.getEntity().getContent(), 
-                    new TypeReference<Map<String, Object>>() {});
-        } catch (final Exception e) {
-            log.warn("Unable to decode OIDC Token Request Response", e);
+        if (objectMapper == null) {
+            throw new ComponentInitializationException("objectMapper cannot be null");
         }
-        return null;
     }
 
 }
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultTokenResponseDecoder.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultMapResponseDecoder.java
similarity index 60%
rename from idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultTokenResponseDecoder.java
rename to idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultMapResponseDecoder.java
index 4950a60..3a66ff2 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultTokenResponseDecoder.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultMapResponseDecoder.java
@@ -18,7 +18,6 @@
 package net.shibboleth.idp.plugin.authn.oidc.rp.decoding.impl;
 
 import java.util.Map;
-import java.util.function.Function;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -29,46 +28,14 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import com.nimbusds.jose.util.IOUtils;
 
-import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
-import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
-import net.shibboleth.utilities.java.support.component.ComponentSupport;
-import net.shibboleth.utilities.java.support.logic.Constraint;
-
 /** Default token response decoder, which converts a succesful HTTP response into an Map.*/
-public class DefaultTokenResponseDecoder extends AbstractInitializableComponent
-        implements Function<HttpResponse, Map<String, Object>> {
+public class DefaultMapResponseDecoder extends AbstractJSONResponseDecoderFunction<Map<String, Object>> {
     
     /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultTokenResponseDecoder.class);
-
-    /** JSON object mapper. */
-    @NonnullAfterInit private ObjectMapper objectMapper;
-    
-    
-    @Override
-    protected void doInitialize() throws ComponentInitializationException {
-        super.doInitialize();
-        
-        if (objectMapper == null) {
-            throw new ComponentInitializationException("objectMapper cannot be null");
-        }
-    }
+    @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultMapResponseDecoder.class);
 
-    /**
-     * Set the JSON Object Mapper to use.
-     * 
-     * @param mapper the object mapper.
-     */
-    public void setObjectMapper(@Nonnull final ObjectMapper mapper) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
-
-        objectMapper = Constraint.isNotNull(mapper, "ObjectMapper cannot be null");
-    }
 
     //TODO we should handle the error response better? just return it and make the decision later?
     @Override
@@ -80,9 +47,9 @@ public class DefaultTokenResponseDecoder extends AbstractInitializableComponent
                 //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("Token endpoint returned a Non-ok message of '{}'",errorContent);
+                    log.error("HTTP endpoint returned a Non-ok message of '{}'",errorContent);
                 }
-                log.warn("Non-ok status code ({}) returned from token endpoint: {}", httpStatusCode, 
+                log.warn("Non-ok status code ({}) returned from HTTP endpoint: {}", httpStatusCode, 
                         httpResponse.getStatusLine().getReasonPhrase());
                 return null;
             } else if (httpResponse.getEntity() == null || httpResponse.getEntity().getContent() == null) {
@@ -90,10 +57,11 @@ public class DefaultTokenResponseDecoder extends AbstractInitializableComponent
                 return null;
             }
 
-            return objectMapper.readValue(httpResponse.getEntity().getContent(), 
+            return getObjectMapper().readValue(httpResponse.getEntity().getContent(), 
                     new TypeReference<Map<String, Object>>() {});
+          
         } catch (final Exception e) {
-            log.warn("Unable to decode OIDC Token Request Response", 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
new file mode 100644
index 0000000..260a1c8
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultUserInfoResponseDecoder.java
@@ -0,0 +1,106 @@
+/*
+ * 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 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.jose.util.IOUtils;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTParser;
+import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
+
+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.*/
+public class DefaultUserInfoResponseDecoder extends AbstractJSONResponseDecoderFunction<UserInfoResponse> {
+    
+    /** The application/jwt media type.*/
+    @Nonnull private static final MediaType APPLICATION_JWT = new MediaType("application", "jwt"); 
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultUserInfoResponseDecoder.class);    
+    
+    @Override
+    public UserInfoResponse apply(@Nonnull final HttpResponse httpResponse) {
+        
+        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("HTTP endpoint returned a Non-ok message of '{}'",errorContent);
+                }
+                log.warn("Non-ok status code ({}) returned from HTTP endpoint: {}", httpStatusCode, 
+                        httpResponse.getStatusLine().getReasonPhrase());
+                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) {
+                final JWT parsedJwt = JWTParser.parse(content);
+                log.trace("UserInfo response parsed a JWT type");
+                return new JWTUserInfoResponse(parsedJwt);
+                
+            } else if (MediaType.APPLICATION_JSON.compareTo(MimeType.valueOf(contentType.getMimeType())) == 0){
+                final Map<String, Object> claims = getObjectMapper().readValue(content, 
+                        new TypeReference<Map<String, Object>>() {});
+                final ClaimsSet claimsSet = new ClaimsSet();
+                claimsSet.putAll(claims);
+                log.debug("UserInfo endpoint returned claims for '{}'", claimsSet.getStringClaim("sub"));
+                if (log.isTraceEnabled()) {
+                    log.trace("UserInfo endpoint claims for '{}' are '{}'", claimsSet.getStringClaim("sub"), 
+                            claimsSet.toJSONString());
+                }
+                return new PlainUserInfoResponse(claimsSet);
+            }            
+          
+        } catch (final Exception e) {
+            log.warn("Unable to decode response", e);
+            return null;
+        }
+        return null;
+        
+    }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultTokenRequestEncoder.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultTokenRequestEncoder.java
index 3da272e..8d4b0cd 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultTokenRequestEncoder.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultTokenRequestEncoder.java
@@ -58,6 +58,7 @@ public class DefaultTokenRequestEncoder extends AbstractRequestEncoderFunction {
             }
             // Mandate HTTPS, so construct the URL from that.
             final URI uri = new URIBuilder().setScheme(HTTPS)
+                    .setPort(getProviderMetadataContext().getProviderInformation().getTokenEndpointURI().getPort())
                     .setHost(getProviderMetadataContext()
                             .getProviderInformation()
                             .getTokenEndpointURI().getHost())
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
new file mode 100644
index 0000000..3c7499f
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultUserInfoRequestEncoder.java
@@ -0,0 +1,116 @@
+package net.shibboleth.idp.plugin.authn.oidc.rp.encoding.impl;
+
+import java.net.URI;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+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.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.jose.util.StandardCharset;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.OIDCRPException;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+
+/**
+ * Default encoder for create UserInfo requests.
+ */
+//TODO This could be GET or POST - how to signal that? profile config, client metadata etc.
+public class DefaultUserInfoRequestEncoder extends AbstractRequestEncoderFunction {
+    
+    /** The HTTPS scheme.*/
+    @Nonnull @NotEmpty private static final String HTTPS = "https";
+    
+    /** Class logger. */
+    @Nonnull
+    private final Logger log = LoggerFactory.getLogger(DefaultUserInfoRequestEncoder.class);
+    
+    /** Strategy used to look up the {@link AccessTokenResponseContext} to set the parameters for. */
+    @Nonnull private Function<ProfileRequestContext, AccessTokenResponseContext> 
+            tokenResponseContextLookupStrategy;
+
+    
+    /** Constructor.*/
+    public DefaultUserInfoRequestEncoder() {
+        tokenResponseContextLookupStrategy =
+                new ChildContextLookup<>(AccessTokenResponseContext.class, true).compose(
+                        new InboundMessageContextLookup());
+    }
+    
+    @Override
+    public HttpUriRequest doApply(@Nonnull final ProfileRequestContext profileRequestContext) {
+        
+        try {
+            if (getClientMetadataContext().getClientInformation() == null) {
+                log.warn("No client information present to base token request off");
+                return null;
+            }
+            final AccessTokenResponseContext responseCtx = tokenResponseContextLookupStrategy.apply(profileRequestContext);
+            if (responseCtx == null) {
+                log.debug("No TokenResponseContext returned by lookup strategy");
+                return null;
+            }
+            
+            // Mandate HTTPS GET, so construct the URL from that.
+            final URI uri = new URIBuilder().setScheme(HTTPS)
+                    .setPort(getProviderMetadataContext().getProviderInformation().getUserInfoEndpointURI().getPort())
+                    .setHost(getProviderMetadataContext()
+                            .getProviderInformation()
+                            .getUserInfoEndpointURI().getHost())
+                    .setPath(getProviderMetadataContext()
+                            .getProviderInformation()
+                            .getUserInfoEndpointURI().getPath())
+                    .build();                 
+            
+            // Add headers and create request.
+            final RequestBuilder rb = RequestBuilder.get().setUri(uri)
+                    .setHeader("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType())
+                    .setCharset(StandardCharset.UTF_8);
+             
+            // Add bearer token as authorization header.
+            addBearerToken(rb, responseCtx);            
+            
+            //If POST request add access_token= to query URL (unitutively)
+            
+            final HttpUriRequest request = rb.build();                  
+            log.debug("UserInfo request URL '{}'",request);            
+            return request;
+            
+        } catch (final Exception e) {
+            log.warn("Unable to encode token request", e);
+        }
+        return null;
+    }
+    
+    /** 
+     * Add the bearer token to the Authorization header, used when issuing HTTP GET requests.
+     * 
+     * @param rb the request builder to use.
+     * @param responseCtx the response context to find the access_token from.
+     * 
+     * @throws OIDCRPException if there is an issue adding the bearer token to the Authorization header.
+     */
+    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");
+        }
+        
+    }
+
+
+}
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 9dad4af..742bfc6 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
@@ -18,6 +18,8 @@
 package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
 
 import java.io.IOException;
+import java.util.Map;
+import java.util.function.Function;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -26,11 +28,13 @@ import org.apache.http.HttpResponse;
 import org.apache.http.client.HttpClient;
 import org.apache.http.client.methods.HttpUriRequest;
 import org.apache.http.client.protocol.HttpClientContext;
+import org.opensaml.profile.context.ProfileRequestContext;
 import org.opensaml.security.httpclient.HttpClientSecurityParameters;
 import org.opensaml.security.httpclient.HttpClientSecuritySupport;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import net.shibboleth.idp.plugin.authn.oidc.rp.OIDCRPException;
 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;
@@ -38,17 +42,26 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
 
 /**
  * An abstract class for actions that want to make synchronous HTTP requests.
+ * 
+ * @param <T> the response type of the object returned as a result of the request.
  */
-public class AbstractHttpOIDCAuthenticationAction extends AbstractOIDCAuthenticationAction {
+public class AbstractHttpOIDCAuthenticationAction<T> extends AbstractOIDCAuthenticationAction {
     
     /** Class logger.*/
     @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractHttpOIDCAuthenticationAction.class);
     
-    /** HttpClient for contacting Duo. */
+    /** The message encoder to encode the HTTP request into a {@link HttpUriRequest}.*/
+    @NonnullAfterInit private Function<ProfileRequestContext, HttpUriRequest> httpRequestEncoderStrategy;
+    
+    /** The message decoder to decode the HTTP response.*/
+    @NonnullAfterInit private Function<HttpResponse, T> httpResponseDecoderStrategy;
+    
+    /** HttpClient for contacting the endpoint. */
     @NonnullAfterInit private HttpClient httpClient;
 
     /** HTTP client security parameters. */
     @Nullable private HttpClientSecurityParameters httpClientSecurityParameters; 
+   
     
     @Override
     protected void doInitialize() throws ComponentInitializationException {
@@ -57,6 +70,55 @@ public class AbstractHttpOIDCAuthenticationAction extends AbstractOIDCAuthentica
         if (httpClient == null) {
             throw new ComponentInitializationException("httpClient cannot be null");
         }
+        if (httpRequestEncoderStrategy == null) {
+            throw new ComponentInitializationException("HTTP request encoder strategy cannot be null");
+        }
+        if (httpResponseDecoderStrategy == null) {
+            throw new ComponentInitializationException("HTTP response decoder strategy cannot be null");
+        }
+    }
+    
+    /**
+     * Get the HTTP request encoder strategy.
+     * 
+     * @return the request encoder.
+     */
+    @NonnullAfterInit public Function<ProfileRequestContext, HttpUriRequest> getHttpRequestEncoderStrategy() {
+        return httpRequestEncoderStrategy;
+    }
+    
+    /**
+     * Get the HTTP response decoder strategy.
+     * 
+     * @return the response decoder.
+     */
+    @NonnullAfterInit public Function<HttpResponse, T> getHttpResponseDecoderStrategy() {
+        return httpResponseDecoderStrategy;
+    }
+    
+    /**
+     * Set the strategy used to map a HTTP response the response type.
+     * 
+     * @param strategy the strategy
+     */
+    public void setHttpResponseDecoderStrategy(@Nonnull final Function<HttpResponse, T> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+        
+        httpResponseDecoderStrategy = Constraint.isNotNull(strategy, "Http decoder strategy can not be null");
+    }
+    
+    /**
+     * Set the strategy used to map a HTTP request to a {@link HttpUriRequest} object.
+     * 
+     * @param strategy the strategy
+     */
+    public void setHttpRequestEncoderStrategy(
+            @Nonnull final Function<ProfileRequestContext, HttpUriRequest> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+        
+        httpRequestEncoderStrategy = Constraint.isNotNull(strategy, "Http encoder strategy can not be null");
     }
 
     /**
@@ -82,15 +144,44 @@ public class AbstractHttpOIDCAuthenticationAction extends AbstractOIDCAuthentica
 
         httpClientSecurityParameters = params;
     }
+    
+    /**
+     * Encode the request using the supplied request encoder strategy. Execute a synchronous HTTP request
+     * and decode the response using the supplied decoder strategy. 
+     * 
+     * @param profileRequestContext the context to pull information out of for encoding the request.
+     * 
+     * @return the decoded response.
+     * 
+     * @throws OIDCRPException on error making the request.
+     */
+    @Nonnull protected T handleRequest(
+            @Nonnull final ProfileRequestContext profileRequestContext) throws OIDCRPException {
+        try {              
+            final HttpUriRequest request = getHttpRequestEncoderStrategy().apply(profileRequestContext);
+            if (request == null) {
+                throw new OIDCRPException("Unable to encode HTTP request");
+            }
+            final HttpResponse response = executeHttpRequest(request);
+            final T responseObject = getHttpResponseDecoderStrategy().apply(response);
+            if (responseObject == null) {
+                throw new OIDCRPException(
+                        "Unable to decode HTTP response");                
+            }
+            return responseObject;
+        } catch (final IOException e) {
+            log.error("{} Unable to perform HTTP request and return response",getLogPrefix(),e);
+            throw new OIDCRPException(e);
+        }  
+    }
 
 
     /**
-     * Performs a call to an OIDC endpoint that expects a message in the body of the response.
-     * Iff successful, the JSON response is mapped into the appropriate type.
+     * Performs a call to an HTTP endpoint using the configured HttpClient and security parameters.
      * 
      * @param request the prepared HTTP request
      * 
-     * @return the response, never {@code null}.
+     * @return the HTTP response, never {@code null}.
      * 
      * @throws IOException if there is an error producing a response
      */
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCAuthenticationResponseAction.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCAuthenticationResponseAction.java
index 83d8dc8..011597b 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCAuthenticationResponseAction.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCAuthenticationResponseAction.java
@@ -76,7 +76,7 @@ abstract class AbstractOIDCAuthenticationAction extends AbstractAuthenticationAc
     @Nullable private OIDCProviderMetadataContext providerMetadataContext;
     
     /** Constructor.*/
-    public AbstractOIDCAuthenticationAction() {
+    protected AbstractOIDCAuthenticationAction() {
         providerMetadataLookupStrategy = new ChildContextLookup<>(OIDCProviderMetadataContext.class).compose(
                 new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
                         new OutboundMessageContextLookup()));
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AddAuthzRequest.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AddAuthzRequest.java
index 8049654..7cd1a66 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AddAuthzRequest.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AddAuthzRequest.java
@@ -33,6 +33,8 @@ import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.nimbusds.openid.connect.sdk.Nonce;
+
 import net.shibboleth.idp.authn.AbstractAuthenticationAction;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
@@ -219,7 +221,8 @@ public class AddAuthzRequest extends AbstractAuthenticationAction {
                 getLogPrefix(), authenticationContext.getAuthenticatingAuthority());
         
         
-        final OIDCAuthenticationRequest request = new OIDCAuthenticationRequest(clientMetadata.getClientInformation().getID());
+        final OIDCAuthenticationRequest request = 
+                new OIDCAuthenticationRequest(clientMetadata.getClientInformation().getID());
 
         request.setResponseType(responseTypeAndModeContext.getResponseType());
         //TODO spec says response mode not recommended if the default type for response_type. Check here?
@@ -227,6 +230,11 @@ public class AddAuthzRequest extends AbstractAuthenticationAction {
         request.setEndpointURI(providerMetadata.getProviderInformation().getAuthorizationEndpointURI());
         request.setRedirectURI(clientMetadata.getClientInformation().getMetadata().getRedirectionURI());
         
+        request.getScope().add("profile");
+        
+        //TODO use strategy with injectable secure random implementation?
+        request.setNonce(new Nonce(OIDCProxySupport.generateNonce(16)));
+        
         //TODO if force-authn
 //        try {
 //            request.setPrompt(Prompt.parse("none"));            
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationController.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationController.java
index 137789d..c3d8b43 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationController.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationController.java
@@ -35,14 +35,12 @@ import org.opensaml.messaging.handler.MessageHandlerException;
 import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.EventContext;
 import org.opensaml.profile.context.ProfileRequestContext;
-import org.opensaml.saml.common.messaging.context.SAMLMessageReceivedEndpointContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 
-import com.nimbusds.oauth2.sdk.AuthorizationResponse;
 import com.nimbusds.oauth2.sdk.id.State;
 
 import net.shibboleth.idp.authn.ExternalAuthentication;
@@ -50,7 +48,6 @@ import net.shibboleth.idp.authn.ExternalAuthenticationException;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.OIDCProxyException;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCAuthnContext;
-import net.shibboleth.idp.plugin.authn.oidc.rp.context.OpenIDConnectContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.ResponseTypeAndModeContext;
 import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultClaimMergingStrategy.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultClaimMergingStrategy.java
new file mode 100644
index 0000000..a72cc7b
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultClaimMergingStrategy.java
@@ -0,0 +1,88 @@
+/*
+ * 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.util.HashMap;
+import java.util.Map;
+import java.util.function.BiFunction;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
+
+/** 
+ * A default merging strategy for combing claims in the UserInfo response with those from the id_token. 
+ * <ol>
+ * <li>If one of userInfo or idToken claims are null, the other is returned.</li>
+ * <li>If both input claims are null, an empty claimsset is returned.</li>
+ * <li>Merges the id_token claims into the UserInfo claims, the value of a claim from the id_token
+ * is taken over that from the UserInfo response if they claim keys clash.</li>
+ * </ol>
+ */
+//TODO maybe this should filter claims as well e.g. exp etc. Security check this logic.
+public class DefaultClaimMergingStrategy implements BiFunction<ClaimsSet, JWTClaimsSet, ClaimsSet> {
+    
+    /** Class logger.*/
+    @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultClaimMergingStrategy.class);
+
+    @Override
+    @Nonnull public ClaimsSet apply(@Nullable final ClaimsSet userInfo, @Nullable final JWTClaimsSet idToken) {
+        
+        if (userInfo == null && idToken != null) {
+            final ClaimsSet singleSet = new ClaimsSet();
+            singleSet.putAll(idToken.getClaims());
+            return singleSet;
+        }
+        if (userInfo != null && idToken == null) {
+            final ClaimsSet singleSet = new ClaimsSet();
+            singleSet.putAll(userInfo.toJSONObject());
+            return singleSet;
+        }
+        if (userInfo == null && idToken == null) {
+            // return empty claimsset
+            return new ClaimsSet();
+        }
+        
+        
+        
+        final Map<String, Object> idTokenAsMap = idToken.getClaims();
+        // Treat JSONObject as the base map representation.
+        final Map<String, Object> userInfoClaimsAsMap = userInfo.toJSONObject();
+        
+        // add UserInfo claims as a base
+        final Map<String, Object> mergedClaimsMap = new HashMap<String, Object>(userInfoClaimsAsMap);
+        
+        // Merge id_token claims into userInfo claims, take userinfo claim if conflict
+        idTokenAsMap.forEach((key, value) ->
+            mergedClaimsMap.merge(key, value, (v1, v2)-> {
+                log.trace("Claim '{}' exists in id_token and UserInfo response, taking id_token value '{}'",key, v2);
+                return v2;   
+            }));
+
+        
+        final ClaimsSet mergedClaimsSet = new ClaimsSet();
+        mergedClaimsSet.putAll(mergedClaimsMap);
+        return mergedClaimsSet;
+    }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultIDTokenLookupStrategy.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultIDTokenLookupStrategy.java
index ddad58f..8a8366f 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultIDTokenLookupStrategy.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultIDTokenLookupStrategy.java
@@ -1,9 +1,27 @@
+/*
+ * 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.util.function.Function;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
 
 import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.context.ProfileRequestContext;
@@ -11,36 +29,39 @@ import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
 
 import com.nimbusds.jwt.JWT;
 
-import net.shibboleth.idp.plugin.authn.oidc.rp.context.TokenResponseContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 
-/** Function that extracts the id_token from the {@link TokenResponseContext}.*/
+/** Function that extracts the id_token from the {@link AccessTokenResponseContext}.*/
+ at ThreadSafe
 public class DefaultIDTokenLookupStrategy implements Function<ProfileRequestContext, JWT> {
     
-    /** Strategy used to locate the TokenResponseContext to extract the id_token from.*/
-    @Nonnull private Function<ProfileRequestContext, TokenResponseContext> tokenResponseContextLookupStrategy;
+    /** Strategy used to locate the {@link AccessTokenResponseContext} to extract the id_token from.*/
+    @Nonnull 
+    private final Function<ProfileRequestContext, AccessTokenResponseContext> tokenResponseContextLookupStrategy;
     
     /** Constructor.*/
     public DefaultIDTokenLookupStrategy() {
         tokenResponseContextLookupStrategy =
-                new ChildContextLookup<>(TokenResponseContext.class, true).compose(
+                new ChildContextLookup<>(AccessTokenResponseContext.class, true).compose(
                         new InboundMessageContextLookup()); 
     }
     
-    /**
-     * Set the strategy used to look up a {@link TokenResponseContext}.
-     * 
-     * @param strategy lookup strategy
-     */
-    public void setTokenResponseContextLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext, TokenResponseContext> strategy) {        
-        tokenResponseContextLookupStrategy = Constraint.isNotNull(strategy,
-                "TokenResponseContext lookup strategy cannot be null");
-    }
+   /**
+    * 
+    * Constructor.
+    *
+    * @param strategy the AccessTokenResponseContext lookup strategy to use.
+    */
+   public DefaultIDTokenLookupStrategy(
+           @Nonnull final Function<ProfileRequestContext, AccessTokenResponseContext> strategy) {
+       tokenResponseContextLookupStrategy = 
+               Constraint.isNotNull(strategy, "AccessTokenResponseContext lookup strategy can not be null");
+   }
 
     @Override
     @Nullable public JWT apply(@Nonnull final ProfileRequestContext prc) {
-        final TokenResponseContext tokenContext = tokenResponseContextLookupStrategy.apply(prc);
+        final AccessTokenResponseContext tokenContext = tokenResponseContextLookupStrategy.apply(prc);
         if (tokenContext == null) {
             return null;
         }
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultUserInfoTokenLookupStrategy.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultUserInfoTokenLookupStrategy.java
new file mode 100644
index 0000000..9910f35
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultUserInfoTokenLookupStrategy.java
@@ -0,0 +1,78 @@
+/*
+ * 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.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+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 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.utilities.java.support.logic.Constraint;
+
+/** Function that extracts the id_token from the {@link AccessTokenResponseContext}.*/
+//TODO could this be replaced in the method using it by a ContextDataLookupFunction?
+ at ThreadSafe
+public class DefaultUserInfoTokenLookupStrategy implements Function<ProfileRequestContext, JWT> {
+    
+    /** Strategy used to look up the {@link UserInfoResponseContext}. */
+    @Nonnull private final Function<ProfileRequestContext, UserInfoResponseContext> 
+            userInfoResponseContextLookupStrategy;
+    
+    /** Constructor.*/
+    public DefaultUserInfoTokenLookupStrategy() {
+        userInfoResponseContextLookupStrategy =
+                new ChildContextLookup<>(UserInfoResponseContext.class).compose(
+                        new InboundMessageContextLookup());
+    }
+    
+    /**
+     * 
+     * Constructor.
+     *
+     * @param strategy the UserInfo response context lookup strategy to use.
+     */
+    public DefaultUserInfoTokenLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, UserInfoResponseContext> strategy) {
+        userInfoResponseContextLookupStrategy = 
+                Constraint.isNotNull(strategy, "UserInfoResponseContext lookup strategy can not be null");
+    }
+
+
+    @Override
+    @Nullable public JWT apply(@Nonnull final ProfileRequestContext prc) {
+        
+        final UserInfoResponseContext userInfoContext = userInfoResponseContextLookupStrategy.apply(prc);
+        
+        if (userInfoContext == null || userInfoContext.getUserInfo() == null ||
+                !(userInfoContext.getUserInfo() instanceof JWTUserInfoResponse)) {
+            return null;
+        }
+        return ((JWTUserInfoResponse)userInfoContext.getUserInfo()).getResponseJwt();
+    }
+
+}
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 b71ce90..8d5934d 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,14 +17,11 @@
 
 package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
 
-import java.io.IOException;
 import java.util.Map;
 import java.util.function.Function;
 
 import javax.annotation.Nonnull;
 
-import org.apache.http.HttpResponse;
-import org.apache.http.client.methods.HttpUriRequest;
 import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.action.EventIds;
@@ -35,87 +32,55 @@ import org.slf4j.LoggerFactory;
 
 import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.idp.plugin.authn.oidc.rp.context.TokenResponseContext;
-import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.idp.plugin.authn.oidc.rp.OIDCRPException;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 
 
 /**
  * Action to exchange the authorization code in the authentication response for an OAuth access token which 
- * contains an OIDC id_token. Once obtained, adds the token to the {@link TokenResponseContext}.
+ * contains an OIDC id_token. Once obtained, adds the token to the {@link AccessTokenResponseContext}.
  * 
- * FIXME: these conditions
  * 
  * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
  * @event {@link AuthnEventIds#AUTHN_EXCEPTION}
- * @event {@link AuthnEventIds#NO_CREDENTIALS}
- * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class, false) != null and 
- * AuthenticationContext.getSubcontect(DuoOIDCAuthenticationContext.class,false)!=null</pre>
- * @post Add the Duo authentication token to the context. 
+ * 
+ * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class, false) != null</pre>
+ * @post Add the Access Token Response to the {@link AccessTokenResponseContext}.
  */
-public class ExchangeCodeForAccessToken extends AbstractHttpOIDCAuthenticationAction {
+public class ExchangeCodeForAccessToken extends AbstractHttpOIDCAuthenticationAction<Map<String, Object>> {
     
     /** Class logger.*/
     @Nonnull private final Logger log = LoggerFactory.getLogger(ExchangeCodeForAccessToken.class);
     
-    /** The message encoder to use encode the token request into a HttpRequest.*/
-    @NonnullAfterInit private Function<ProfileRequestContext, HttpUriRequest> tokenRequestEncoderStrategy;
-    
-    /** The message decoder to use decode a HTTP response to a token object.*/
-    @NonnullAfterInit private Function<HttpResponse, Map<String, Object>> tokenResponseDecoderStrategy;
     
-    /** Strategy used to look up the {@link TokenResponseContext} to set the parameters for. */
-    @Nonnull private Function<ProfileRequestContext, TokenResponseContext> 
+    /** Strategy used to look up the {@link AccessTokenResponseContext} to set the parameters for. */
+    @Nonnull private Function<ProfileRequestContext, AccessTokenResponseContext> 
             tokenResponseContextLookupStrategy;
     
     /** Constructor.*/
     public ExchangeCodeForAccessToken() {
         tokenResponseContextLookupStrategy =
-                new ChildContextLookup<>(TokenResponseContext.class, true).compose(
+                new ChildContextLookup<>(AccessTokenResponseContext.class, true).compose(
                         new InboundMessageContextLookup());
     }
     
     /**
-     * Set the strategy used to look up a {@link TokenResponseContext}.
+     * Set the strategy used to look up a {@link AccessTokenResponseContext}.
      * 
      * @param strategy lookup strategy
      */
     public void setTokenResponseContextLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext, TokenResponseContext> strategy) {
+            @Nonnull final Function<ProfileRequestContext, AccessTokenResponseContext> strategy) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
         
         tokenResponseContextLookupStrategy = Constraint.isNotNull(strategy,
                 "TokenResponseContext lookup strategy cannot be null");
     }
     
-    
-    /**
-     * Set the strategy used to map a request...TODO.
-     * 
-     * @param strategy the strategy
-     */
-    public void setTokenResponseDecoderStrategy(@Nonnull final Function<HttpResponse, Map<String, Object>> strategy) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
-        
-        tokenResponseDecoderStrategy = Constraint.isNotNull(strategy, "Token decoder strategy can not be null");
-    }
-    
-    /**
-     * Set the strategy used to map a request...TODO.
-     * 
-     * @param strategy the strategy
-     */
-    public void setTokenRequestEncoderStrategy(
-            @Nonnull final Function<ProfileRequestContext, HttpUriRequest> strategy) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
-        
-        tokenRequestEncoderStrategy = Constraint.isNotNull(strategy, "Token encoder strategy can not be null");
-    }
-      
-    
+
     @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
             @Nonnull final AuthenticationContext authenticationContext) { 
         
@@ -123,33 +88,20 @@ public class ExchangeCodeForAccessToken extends AbstractHttpOIDCAuthenticationAc
                   getAuthenticationResponse().getAuthorizationCode(),
                   authenticationContext.getAuthenticatingAuthority());
           
-          final TokenResponseContext responseCtx = tokenResponseContextLookupStrategy.apply(profileRequestContext);
+          final AccessTokenResponseContext responseCtx = 
+                  tokenResponseContextLookupStrategy.apply(profileRequestContext);
           if (responseCtx == null) {
               log.debug("{} No TokenResponseContext returned by lookup strategy", getLogPrefix());
               ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
               return;
           }
           
-          try {              
-              final HttpUriRequest request = tokenRequestEncoderStrategy.apply(profileRequestContext);
-              if (request == null) {
-                  log.warn("{} Unable to exhange authorization_code for token, request could "
-                          + "not be constructed", getLogPrefix());
-                  ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.AUTHN_EXCEPTION);
-                  return;
-              }
-              final HttpResponse response = executeHttpRequest(request);
-              final Map<String, Object> responseObject = tokenResponseDecoderStrategy.apply(response);
-              if (responseObject == null) {
-                  log.warn("{} Unable to exhange authorization_code for token, response could "
-                          + "not be decoded", getLogPrefix());
-                  ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.AUTHN_EXCEPTION);
-                  return;
-              }
+          try {   
+              final Map<String, Object> responseObject = handleRequest(profileRequestContext);         
               responseCtx.setRawTokenResponse(responseObject);
-              log.debug("{}: Token request response '{}'",getLogPrefix(), responseObject);
+              log.trace("{}: Token request response '{}'",getLogPrefix(), responseObject);
 
-          } catch (final IOException e) {
+          } catch (final OIDCRPException e) {
               log.error("{} Unable 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
index d76f4d8..cb88676 100644
--- 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
@@ -38,7 +38,7 @@ import com.nimbusds.jwt.EncryptedJWT;
 import com.nimbusds.jwt.PlainJWT;
 import com.nimbusds.jwt.SignedJWT;
 
-import net.shibboleth.idp.plugin.authn.oidc.rp.context.TokenResponseContext;
+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;
@@ -46,7 +46,8 @@ import net.shibboleth.utilities.java.support.component.ComponentInitializationEx
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 
-/** Action that extracts an id_token from the inbound message context and sets it onto a TODO context.*/
+/** Action that extracts an id_token from the access token response from the
+ *  inbound message context and sets it onto a TODO context.*/
 public class ExtractIDTokenFromResponse extends AbstractProfileAction {
 
     /** Class logger. */
@@ -56,26 +57,26 @@ public class ExtractIDTokenFromResponse extends AbstractProfileAction {
     @NonnullAfterInit private Function<ProfileRequestContext, String> rawIdTokenLookupStrategy;
     
     /** The token response context to add the decoded id_token too.*/
-    @NonnullAfterInit private TokenResponseContext responseCtx;
+    @NonnullAfterInit private AccessTokenResponseContext responseCtx;
     
-    /** Strategy used to look up the {@link TokenResponseContext} to set the parameters for. */
-    @Nonnull private Function<ProfileRequestContext, TokenResponseContext> 
+    /** 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<>(TokenResponseContext.class).compose(
+                new ChildContextLookup<>(AccessTokenResponseContext.class).compose(
                         new InboundMessageContextLookup());
     }
     
     /**
-     * Set the strategy used to look up a {@link TokenResponseContext}.
+     * Set the strategy used to look up a {@link AccessTokenResponseContext}.
      * 
      * @param strategy lookup strategy
      */
     public void setTokenResponseContextLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext, TokenResponseContext> strategy) {
+            @Nonnull final Function<ProfileRequestContext, AccessTokenResponseContext> strategy) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
         
         tokenResponseContextLookupStrategy = Constraint.isNotNull(strategy,
@@ -101,8 +102,7 @@ public class ExtractIDTokenFromResponse extends AbstractProfileAction {
         }
         return true;
     }
-    
-    
+        
     /**
      * Set the strategy used to lookup a base64 encoded JWT from the profile request context.
      *
@@ -126,7 +126,6 @@ public class ExtractIDTokenFromResponse extends AbstractProfileAction {
         log.trace("{} (Assumed) Base64 encoded id_token is '{}'",getLogPrefix(), rawIdTokenValue);
         try {
             final JOSEObject joseObject = JOSEObject.parse(rawIdTokenValue);
-            log.trace("{} Parsed JOSE Object '{}'",getLogPrefix(), joseObject);
             if (joseObject instanceof PlainObject) {
                 responseCtx.setIdToken(PlainJWT.parse(rawIdTokenValue));
                 
@@ -135,14 +134,11 @@ public class ExtractIDTokenFromResponse extends AbstractProfileAction {
                 
             } else if (joseObject instanceof JWEObject) {
                 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;
-            
-        }
-        
+            return;            
+        }        
     }
 }
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/MergeUserInfoAndIDTokenClaims.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/MergeUserInfoAndIDTokenClaims.java
new file mode 100644
index 0000000..cd8c549
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/MergeUserInfoAndIDTokenClaims.java
@@ -0,0 +1,218 @@
+/*
+ * 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.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+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.jwt.JWTClaimsSet;
+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.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;
+
+/**
+ * Merge the claims in the id_token with the claims from the UserInfo response. 
+ */
+//TODO similar too ValidateUserInfoClaims, do we need to extend OIDC action
+public class MergeUserInfoAndIDTokenClaims extends AbstractOIDCAuthenticationAction {
+    
+    /** Class logger.*/
+    @Nonnull private final Logger log = LoggerFactory.getLogger(MergeUserInfoAndIDTokenClaims.class);
+    
+    /** Strategy used to look up the {@link UserInfoResponseContext}. */
+    @Nonnull private Function<ProfileRequestContext, UserInfoResponseContext> 
+            userInfoResponseContextLookupStrategy;
+    
+    /** Strategy used to look up the {@link AccessTokenResponseContext} . */
+    @Nonnull private Function<ProfileRequestContext, AccessTokenResponseContext> 
+            tokenResponseContextLookupStrategy;
+    
+    /** Strategy used to look up the {@link EndUserClaimsContext} to set the parameters for. */
+    @Nonnull private Function<ProfileRequestContext, EndUserClaimsContext> 
+            endUserClaimsContextLookupStrategy;
+    
+    /** The strategy used to merge UserInfo claims with id_token claims.*/
+    @NonnullAfterInit private BiFunction<ClaimsSet, JWTClaimsSet, ClaimsSet> claimMergingStrategy;
+    
+    /** The stashed user info claims.*/
+    @Nullable private ClaimsSet userInfoClaims; 
+    
+    /** The stashed id_token claims.*/
+    @Nullable private JWTClaimsSet idTokenClaims; 
+    
+    /** Constructor.*/
+    public MergeUserInfoAndIDTokenClaims() {
+        userInfoResponseContextLookupStrategy =
+                new ChildContextLookup<>(UserInfoResponseContext.class).compose(
+                        new InboundMessageContextLookup());
+        
+        tokenResponseContextLookupStrategy =
+                new ChildContextLookup<>(AccessTokenResponseContext.class).compose(
+                        new InboundMessageContextLookup());
+        
+        endUserClaimsContextLookupStrategy = 
+                new ChildContextLookup<>(EndUserClaimsContext.class, true).compose(
+                        new InboundMessageContextLookup());
+        
+        claimMergingStrategy = new DefaultClaimMergingStrategy();
+    }
+    
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        if (claimMergingStrategy == null) {
+            throw new ComponentInitializationException("ClaimMergingStrategy cannot be null");
+        }
+    }
+    
+    /**
+     * Set the strategy used to merge UserInfo claims with id_token claims.
+     * 
+     * @param strategy the strategy to use.
+     */
+    public void setClaimMergingStrategy(
+            @Nonnull final BiFunction<ClaimsSet, JWTClaimsSet, ClaimsSet> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        claimMergingStrategy =  Constraint.isNotNull(strategy,
+                "ClaimMergingStrategy cannot be null");
+    }
+    
+    /**
+     * Set the strategy used to lookup a {@link EndUserClaimsContext}.
+     * 
+     * @param strategy the strategy
+     */
+    public void setEndUserClaimsContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, EndUserClaimsContext> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        endUserClaimsContextLookupStrategy = Constraint.isNotNull(strategy,
+                "EndUserClaimsContextLookupStrategy cannot be null");
+    }
+
+    
+    /**
+     * 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");
+    }
+    
+    /**
+     * Set the strategy used to look up a {@link UserInfoResponseContext}.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setUserInfoResponseContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, UserInfoResponseContext> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        userInfoResponseContextLookupStrategy = Constraint.isNotNull(strategy,
+                "UserInfoResponseContext lookup strategy cannot be null");
+    }
+    
+    @Override protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final AuthenticationContext authenticationContext) {
+        
+        final UserInfoResponseContext userInfoCtx = 
+                userInfoResponseContextLookupStrategy.apply(profileRequestContext);
+        if (userInfoCtx == null) {
+            log.debug("{} No UserInfo response context returned by lookup strategy", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            return false;
+        }
+        if (userInfoCtx.getUserInfo() == null) {
+            log.debug("{} No UserInfo returned by lookup strategy", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            return false;
+        }
+        userInfoClaims = userInfoCtx.getUserInfo().getClaimsSet();
+        
+        final AccessTokenResponseContext tokenResponseCtx = 
+                tokenResponseContextLookupStrategy.apply(profileRequestContext);
+        if (tokenResponseCtx == null) {
+            log.debug("{} No AccessTokenResponseContext returned by lookup strategy", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            return false;
+        }
+        if (tokenResponseCtx.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();
+            if (idTokenClaims == null) {
+                log.debug("{} AccessTokenResponseContext did not contain an id_token with accessible claims, "
+                        + "possibly still encrypted", 
+                        getLogPrefix());
+                ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+                return false;
+            }
+        } catch (final ParseException e) {
+            log.debug("{} Unable to parse claims from id_token", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+        }
+        
+        return true;
+    }
+    
+    @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final AuthenticationContext authenticationContext) { 
+        
+          log.trace("{} Merging UserInfo and id_token claims", getLogPrefix());    
+          
+          final ClaimsSet mergedClaims = claimMergingStrategy.apply(userInfoClaims, idTokenClaims);
+          
+          // Add to end user claims context
+          endUserClaimsContextLookupStrategy.apply(profileRequestContext).setEndUserClaims(mergedClaims);
+          
+          if (log.isTraceEnabled()) {
+              log.trace("{} Merged UserInfo and id_token claims to produce '{}'", 
+                  getLogPrefix(), mergedClaims.toJSONString());
+          }
+    }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TokenResponseIDTokenLookupStrategy.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TokenResponseIDTokenLookupStrategy.java
index 49e7b63..dd4ebbb 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TokenResponseIDTokenLookupStrategy.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TokenResponseIDTokenLookupStrategy.java
@@ -27,7 +27,7 @@ import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import net.shibboleth.idp.plugin.authn.oidc.rp.context.TokenResponseContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
 
 /** Return the raw base64 encoded id_token from the TokenResponseContext, or null if not found.*/
 public class TokenResponseIDTokenLookupStrategy implements Function<ProfileRequestContext, String> {
@@ -43,7 +43,7 @@ public class TokenResponseIDTokenLookupStrategy implements Function<ProfileReque
             log.debug("Inbound message context was null, no id_token found");
             return null;
         }
-        final TokenResponseContext tokenResponseContext = inbound.getSubcontext(TokenResponseContext.class);
+        final AccessTokenResponseContext tokenResponseContext = inbound.getSubcontext(AccessTokenResponseContext.class);
         if (tokenResponseContext == null) {
             log.debug("Token response context was null, no id_token found");
             return null;
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
new file mode 100644
index 0000000..615fa12
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/UserInfoEndpointLookup.java
@@ -0,0 +1,99 @@
+/*
+ * 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.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 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.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Request information from the UserInfo OAuth2.0 endpoint using the access_token already present
+ * in the context. Return consented claims about the subject. 
+ */
+public class UserInfoEndpointLookup extends AbstractHttpOIDCAuthenticationAction<UserInfoResponse> {
+    
+    /** Class logger.*/
+    @Nonnull private final Logger log = LoggerFactory.getLogger(UserInfoEndpointLookup.class);
+    
+    /** Strategy used to look up the {@link AccessTokenResponseContext} to set the parameters for. */
+    @Nonnull private Function<ProfileRequestContext, UserInfoResponseContext> 
+            userInfoResponseContextLookupStrategy;
+    
+    
+    /** Constructor.*/
+    public UserInfoEndpointLookup() {
+        userInfoResponseContextLookupStrategy =
+                new ChildContextLookup<>(UserInfoResponseContext.class, true).compose(
+                        new InboundMessageContextLookup());
+    }
+    
+    /**
+     * Set the strategy used to look up a {@link UserInfoResponseContext}.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setUserInfoResponseContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, UserInfoResponseContext> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        userInfoResponseContextLookupStrategy = Constraint.isNotNull(strategy,
+                "UserInfoResponseContext lookup strategy cannot be null");
+    }
+
+    @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final AuthenticationContext authenticationContext) { 
+        
+          log.debug("{} Requesting claims from UserInfo endpoint from upstream OP '{}'", getLogPrefix(), 
+                  authenticationContext.getAuthenticatingAuthority());
+          
+          final UserInfoResponseContext userInfoCtx = 
+                  userInfoResponseContextLookupStrategy.apply(profileRequestContext);
+          if (userInfoCtx == null) {
+              log.debug("{} No UserInfo response context returned by lookup strategy", getLogPrefix());
+              ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+              return;
+          }
+          
+          try {              
+              final UserInfoResponse responseObject = handleRequest(profileRequestContext);
+              userInfoCtx.setUserInfo(responseObject);
+
+          } catch (final OIDCRPException e) {
+              log.error("{} Unable to return claims from UserInfo endpoint",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/ValidateIDTokenSignature.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenSignature.java
index 8e1b26b..992a6eb 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenSignature.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenSignature.java
@@ -21,32 +21,25 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.StringWriter;
-import java.security.interfaces.RSAPublicKey;
 import java.text.ParseException;
 import java.util.Map;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
-import net.shibboleth.idp.authn.AbstractAuthenticationAction;
-import net.shibboleth.idp.authn.AuthnEventIds;
-import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.idp.plugin.authn.oidc.rp.context.OpenIDConnectContext;
-
-import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Charsets;
 import com.google.common.io.CharStreams;
-import com.nimbusds.jose.JOSEException;
-import com.nimbusds.jose.crypto.RSASSAVerifier;
-import com.nimbusds.jose.jwk.RSAKey;
 import com.nimbusds.jose.shaded.json.JSONArray;
 import com.nimbusds.jose.shaded.json.JSONObject;
 import com.nimbusds.jose.util.JSONObjectUtils;
-import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.idp.authn.AbstractAuthenticationAction;
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
 
 /**
  * An action that verifies the signature of a JWS id_token using the RSA key belonging to the keyID
@@ -72,51 +65,7 @@ public class ValidateIDTokenSignature extends AbstractAuthenticationAction {
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext, 
             @Nonnull final AuthenticationContext authenticationContext) {
         
-        final OpenIDConnectContext oidcCtx =
-                authenticationContext.getSubcontext(OpenIDConnectContext.class);
-        if (oidcCtx == null) {
-            log.error("{} Unable to find oidc context", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);            
-            return;
-        }
-        SignedJWT signedJWT = null;
-        try {
-            //TODO P.S. oidcCtx.getIDToken() could be null.
-            signedJWT = SignedJWT.parse(oidcCtx.getIDToken().serialize());
-        } catch (final ParseException e) {
-            log.error("{} Error when parsing signed JWT", getLogPrefix(), e);
-            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);            
-            return;
-        }
-        RSAPublicKey providerKey = null;
-        try {
-            final JSONObject key = getProviderRSAJWK(oidcCtx.getoIDCProviderMetadata().getJWKSetURI()
-                    .toURL().openStream(),signedJWT.getHeader().getKeyID());
-            if (key == null) {
-                log.error("{} Unable to find key to verify signature", getLogPrefix());
-                ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);                
-                return;
-            }
-            providerKey = RSAKey.parse(key).toRSAPublicKey();
-        } catch (final IOException | java.text.ParseException | JOSEException e) {
-            log.error("{} Error when parsing key to verify signature", getLogPrefix(), e);
-            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);            
-            return;
-        }
-        final RSASSAVerifier verifier = new RSASSAVerifier(providerKey);
-        try {
-            if (!signedJWT.verify(verifier)) {
-                log.error("{} JWT signature verification failed", getLogPrefix());
-                ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);               
-                return;
-            }
-        } catch (final JOSEException e) {
-            log.error("{} JWT signature verification not performed", getLogPrefix(), e);
-            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);            
-            return;
-        }
-        log.debug("{} ID Token signature verified",getLogPrefix());        
-        return;
+        
     }
 
     /**
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 f442388..7e1b373 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
@@ -1,3 +1,20 @@
+/*
+ * 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.util.Map;
@@ -14,7 +31,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.idp.plugin.authn.oidc.rp.context.TokenResponseContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
 import net.shibboleth.oidc.profile.core.OidcEventIds;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
@@ -27,24 +44,24 @@ public class ValidateOAuthAccessTokenResponse extends AbstractOIDCAuthentication
     /** Class logger. */    
     @Nonnull @NotEmpty private final Logger log = LoggerFactory.getLogger(ValidateOAuthAccessTokenResponse.class); 
     
-    /** Strategy used to look up the {@link TokenResponseContext} to validate. */
-    @Nonnull private Function<ProfileRequestContext, TokenResponseContext> 
+    /** Strategy used to look up the {@link AccessTokenResponseContext} to validate. */
+    @Nonnull private Function<ProfileRequestContext, AccessTokenResponseContext> 
             tokenResponseContextLookupStrategy;
     
     /** Constructor.*/
     public ValidateOAuthAccessTokenResponse() {
         tokenResponseContextLookupStrategy =
-                new ChildContextLookup<>(TokenResponseContext.class, true).compose(
+                new ChildContextLookup<>(AccessTokenResponseContext.class, true).compose(
                         new InboundMessageContextLookup());
     }
     
     /**
-     * Set the strategy used to look up a {@link TokenResponseContext}.
+     * Set the strategy used to look up a {@link AccessTokenResponseContext}.
      * 
      * @param strategy lookup strategy
      */
     public void setTokenResponseContextLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext, TokenResponseContext> strategy) {
+            @Nonnull final Function<ProfileRequestContext, AccessTokenResponseContext> strategy) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
         
         tokenResponseContextLookupStrategy = Constraint.isNotNull(strategy,
@@ -55,7 +72,7 @@ public class ValidateOAuthAccessTokenResponse extends AbstractOIDCAuthentication
     @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
             @Nonnull final AuthenticationContext authenticationContext) {
         
-        final TokenResponseContext responseCtx = tokenResponseContextLookupStrategy.apply(profileRequestContext);
+        final AccessTokenResponseContext responseCtx = tokenResponseContextLookupStrategy.apply(profileRequestContext);
         if (responseCtx == null) {
             log.debug("{} No TokenResponseContext returned by lookup strategy", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthentication.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthentication.java
index d0eac70..67d8fb0 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthentication.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthentication.java
@@ -18,35 +18,55 @@
 package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
 
 import java.text.ParseException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
+import java.util.Collection;
 import java.util.Map;
+import java.util.function.Function;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import javax.security.auth.Subject;
 
+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.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
+
+import net.minidev.json.JSONObject;
+import net.shibboleth.idp.attribute.AttributeDecodingException;
+import net.shibboleth.idp.attribute.AttributeEncodingException;
 import net.shibboleth.idp.attribute.IdPAttribute;
-import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.attribute.transcoding.AttributeTranscoder;
+import net.shibboleth.idp.attribute.transcoding.AttributeTranscoderRegistry;
+import net.shibboleth.idp.attribute.transcoding.TranscoderSupport;
+import net.shibboleth.idp.attribute.transcoding.TranscodingRule;
 import net.shibboleth.idp.authn.AbstractValidationAction;
 import net.shibboleth.idp.authn.AuthenticationResult;
 import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.idp.plugin.authn.oidc.rp.context.OpenIDConnectContext;
 import net.shibboleth.idp.authn.principal.IdPAttributePrincipal;
 import net.shibboleth.idp.authn.principal.UsernamePrincipal;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.EndUserClaimsContext;
+import net.shibboleth.idp.profile.IdPEventIds;
+import net.shibboleth.idp.profile.context.RelyingPartyContext;
+import net.shibboleth.oidc.profile.config.OIDCAuthorizationConfiguration;
 import net.shibboleth.utilities.java.support.annotation.constraint.Live;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
-import net.shibboleth.utilities.java.support.primitive.StringSupport;
-
-import org.opensaml.profile.action.ActionSupport;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.nimbusds.jwt.JWTClaimsSet;
-import com.nimbusds.openid.connect.sdk.claims.UserInfo;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.service.ReloadableService;
+import net.shibboleth.utilities.java.support.service.ServiceableComponent;
 
 /**
  * An action that builds an {@link AuthenticationResult} from the subject (sub claim) of the OpenID Connect token.
@@ -61,11 +81,13 @@ import com.nimbusds.openid.connect.sdk.claims.UserInfo;
  * @post If AuthenticationContext.getSubcontext(OpenIDConnectContext.class, false).getIDToken()
  * .getJWTClaimsSet().getSubject()!= null, then an {@link net.shibboleth.idp.authn.AuthenticationResult} 
  * is saved to the {@link AuthenticationContext}.
- * 
- * @since 4.0.0
  */
- at SuppressWarnings({"rawtypes", "unchecked"})
+//TODO if we pull claims from a UserInfo endpoint, we need to augment with claims in the id_token and this
+// only looks at the id_token.
 public class ValidateOIDCAuthentication extends AbstractValidationAction {
+    
+    /** Default prefix for metrics. */
+    @Nonnull @NotEmpty private static final String DEFAULT_METRIC_NAME = "net.shibboleth.idp.authn.oidc.rp";
 
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateOIDCAuthentication.class);
@@ -75,9 +97,57 @@ public class ValidateOIDCAuthentication extends AbstractValidationAction {
 
     /** the subject received from id token. */
     @Nullable private String oidcSubject;
+    
+    /** Transcoder registry service object. */
+    @NonnullAfterInit private ReloadableService<AttributeTranscoderRegistry> transcoderRegistry;
+    
+    /** Strategy used to look up a {@link RelyingPartyContext} for configuration options. */
+    @Nonnull private Function<ProfileRequestContext,RelyingPartyContext> relyingPartyContextLookupStrategy;
+    
+    /** Store off profile config. */
+    @Nullable private OIDCAuthorizationConfiguration profileConfiguration;
+       
+    /** Claims Set to extract and decode claims from. */
+    @Nullable private ClaimsSet claimsSet;
+    
+    /** Strategy used to look up the {@link EndUserClaimsContext} to set the parameters for. */
+    @Nonnull private Function<ProfileRequestContext, EndUserClaimsContext> 
+            endUserClaimsContextLookupStrategy;
+    
+    /** Constructor.*/
+    public ValidateOIDCAuthentication() {
+        setMetricName(DEFAULT_METRIC_NAME);
+        relyingPartyContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class);
+        
+        endUserClaimsContextLookupStrategy = 
+                new ChildContextLookup<>(EndUserClaimsContext.class, true).compose(
+                        new InboundMessageContextLookup());
+    }
+    
+    
+    /**
+     * Sets the registry of transcoding rules to apply to encode attributes.
+     * 
+     * @param registry registry service interface
+     */
+    public void setTranscoderRegistry(@Nonnull final ReloadableService<AttributeTranscoderRegistry> registry) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        transcoderRegistry = Constraint.isNotNull(registry, "AttributeTranscoderRegistry cannot be null");
+    }
+    
+    /**
+     * Set the strategy used to return the {@link RelyingPartyContext} for configuration options.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setRelyingPartyContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext,RelyingPartyContext> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
 
-    /** The JWT Claim Set of the ID Token acquired from the token endpoint. */
-    @Nullable private JWTClaimsSet jwtClaims;
+        relyingPartyContextLookupStrategy =
+                Constraint.isNotNull(strategy, "RelyingPartyContext lookup strategy cannot be null");
+    }
 
     /**
      * In MFA use case prior authentication may have created a usernameprincipal already with value not matching to MFA.
@@ -88,6 +158,16 @@ public class ValidateOIDCAuthentication extends AbstractValidationAction {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
         avoidMultiplePrincipal = avoid;
     }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+//        if (transcoderRegistry == null) {
+//            throw new ComponentInitializationException("AttributeTranscoderRegistry cannot be null");
+//        }
+    }
 
     /** {@inheritDoc} */
     @Override
@@ -97,35 +177,36 @@ public class ValidateOIDCAuthentication extends AbstractValidationAction {
         if (!super.doPreExecute(profileRequestContext, authenticationContext)) {
             return false;
         }
-        log.trace("{}: Prerequisities fulfilled to start doPreExecute", getLogPrefix());
 
-        final OpenIDConnectContext oidcCtx = authenticationContext.getSubcontext(OpenIDConnectContext.class);
-        if (oidcCtx == null) {
-            log.error("{} Unable to find oidc context", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);            
+        final RelyingPartyContext rpContext = relyingPartyContextLookupStrategy.apply(profileRequestContext);
+        if (rpContext == null) {
+            log.error("{} Unable to locate RelyingPartyContext", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CTX);
             return false;
-        }
-
-        if (oidcCtx.getIDToken() == null) {
-            log.error("{} No ID Token in response", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);            
+        } else if (rpContext.getProfileConfig() == null) {
+            log.error("{} Unable to locate profile configuration", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
+            return false;
+        } else if (!(rpContext.getProfileConfig() instanceof OIDCAuthorizationConfiguration)) {
+            log.error("{} No OIDC SSO profile configuration", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
             return false;
         }
-        try {
-            //TODO P.S. this will fail if not decrypted JWE. 
-            oidcSubject = StringSupport.trimOrNull(oidcCtx.getIDToken().getJWTClaimsSet().getSubject());
-            jwtClaims = oidcCtx.getIDToken().getJWTClaimsSet();
-        } catch (final ParseException e) {
-            log.error("{} unable to parse ID Token", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);            
+        profileConfiguration = (OIDCAuthorizationConfiguration) rpContext.getProfileConfig();
+        
+        final EndUserClaimsContext claimsContext = endUserClaimsContextLookupStrategy.apply(profileRequestContext);
+        if (claimsContext == null) {
+            log.error("{} Unable to locate end-user claims context", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
             return false;
         }
-
-        if (oidcSubject == null) {
-            log.error("{} Subject is null in ID Token response", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);            
+        if (claimsContext.getEndUserClaims() == null) {
+            log.error("{} Unable to locate end-user claims", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
             return false;
         }
+        claimsSet = claimsContext.getEndUserClaims();
+
         return true;
     }
 
@@ -134,64 +215,20 @@ public class ValidateOIDCAuthentication extends AbstractValidationAction {
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext, 
             @Nonnull final AuthenticationContext authenticationContext) {
         
+        recordSuccess(profileRequestContext);
+        
+        log.trace("{} Validating OIDC proxy authentication", getLogPrefix());
+         
+        if (transcoderRegistry != null) {
+            processAttributes(profileRequestContext);
+        }
+        //TODO use an attributeExtractionStrategy
+        
         buildAuthenticationResult(profileRequestContext, authenticationContext);
         return;
     }
 
-    /**
-     * For each OIDC Standard Claim (OpenID Connect Core 1.0 section 5.1) in the JWT Claim set {@code jwtClaimSet},
-     * build a {@link IdPAttributePrincipal} using a {@link StringAttributeValue}.
-     * <p>
-     * Any claim that is not understood (not in the standard claim set) *will* be ignored.
-     * <p>
-     * All claims are strings except {@code email_verified} and {code phone_verified) (both booleans), {@code address}
-     * (JSON Object), and {@code updated_at} (number). Both booleans are represented as string attribute values, the
-     * {@code address} and {@code updated_at} claims are currently ignored.
-     * 
-     * 
-     * @return a list of standard OIDC claims as {@link IdPAttributePrincipal}s.
-     */
-    @Nonnull 
-    @Live
-    private List<IdPAttributePrincipal> buildIdPAttributePrincipalsFromStandardClaims() {
-
-        final List<IdPAttributePrincipal> claimPrincipals = new ArrayList<>();
-        if (jwtClaims != null) {
-            // jwtClaims.getClaims() is never null
-            for (final Map.Entry<String, Object> claim : jwtClaims.getClaims().entrySet()) {
-
-                if (UserInfo.getStandardClaimNames().contains(claim.getKey())) {
-
-                    String claimValue = null;
-                    if (claim.getValue() instanceof String) {
-                        claimValue = StringSupport.trimOrNull((String) claim.getValue());
-                    }
-
-                    if (claim.getValue() instanceof Boolean) {
-                        claimValue = StringSupport.trimOrNull(Boolean.toString((Boolean) claim.getValue()));
-                    }
-                    if (claimValue == null) {
-                        log.trace("{} JWT Claim [{}] is not of a supported type or is null/empty, ignored",
-                                getLogPrefix(), claim);
-                        continue;
-                    }
-
-                    final IdPAttribute idpAttr = new IdPAttribute(claim.getKey());
-                    idpAttr.setValues(Collections.singletonList(new StringAttributeValue(claimValue)));
-
-                    final IdPAttributePrincipal attrPrincipal = new IdPAttributePrincipal(idpAttr);
-                    log.trace("{} Constructed IdPAttributePrincipal from OIDC claim [{}]", getLogPrefix(),
-                            attrPrincipal);
-                    claimPrincipals.add(attrPrincipal);
-                }
-
-            }
-        }
-
-        return claimPrincipals;
-
-    }
-
+   
     @Override
     protected Subject populateSubject(@Nonnull final Subject subject) {    
         
@@ -199,11 +236,89 @@ public class ValidateOIDCAuthentication extends AbstractValidationAction {
             log.debug("{} Subject already contains principal, not populated", getLogPrefix());
 
         } else {            
-            subject.getPrincipals().add(new UsernamePrincipal(oidcSubject));
-            subject.getPrincipals().addAll(buildIdPAttributePrincipalsFromStandardClaims());
+            subject.getPrincipals().add(new UsernamePrincipal(claimsSet.getStringClaim("sub")));
+            //subject.getPrincipals().addAll(buildIdPAttributePrincipalsFromStandardClaims());
 
         }        
         return subject;
     }
+    
+    /**
+     * Process the inbound OIDC claims.
+     * 
+     * @param profileRequestContext current profile request context
+     */
+    private void processAttributes(@Nonnull final ProfileRequestContext profileRequestContext) {
+        
+        log.debug("{} Decoding incoming OIDC claims", getLogPrefix());
+        
+        final Multimap<String,IdPAttribute> mapped = HashMultimap.create();
+
+        ServiceableComponent<AttributeTranscoderRegistry> component = null;
+        try {
+            component = transcoderRegistry.getServiceableComponent();
+            if (component == null) {
+                log.error("Attribute transcoder service unavailable");
+                return;
+            }
+            
+            for (final Map.Entry<String, Object> claim : claimsSet.toJSONObject().entrySet()) {                
+                try {    
+                    //Only allow the standard claims? so no exp. etc
+                    final JSONObject jsonClaim = new JSONObject();
+                    jsonClaim.put(claim.getKey(), claim.getValue());                
+                    decodeAttribute(component.getComponent(), profileRequestContext, jsonClaim, mapped);
+                } catch (final AttributeDecodingException e) {
+                    log.error("{} Error decoding inbound claim", getLogPrefix(), e);
+                }                    
+            }
+        } finally {
+            if (component != null) {
+                component.unpinComponent();
+            }
+        }
+                
+        log.debug("{} Incoming OIDC Attributes mapped to attribute IDs: {}", getLogPrefix(), mapped.keySet());
+        
+        if (!mapped.isEmpty()) {
+//            attributeContext = profileRequestContext
+//                    .getSubcontext(RelyingPartyContext.class)
+//                    .getSubcontext(AttributeContext.class, true);
+//            attributeContext.setUnfilteredIdPAttributes(mapped.values());
+//            attributeContext.setIdPAttributes(null);
+//            filterAttributes(profileRequestContext);
+        }
+    }
+    
+    
+    /**
+     * Access the registry of transcoding rules to transform (decode) the input claims to IdP Attributes.
+     * 
+     * @param registry  registry of transcoding rules
+     * @param profileRequestContext current profile request context
+     * @param input input attribute
+     * @param results collection to add results to
+     * 
+     * @throws AttributeEncodingException if a non-ignorable error occurs
+     */
+    private void decodeAttribute(@Nonnull final AttributeTranscoderRegistry registry,
+            @Nonnull final ProfileRequestContext profileRequestContext, @Nonnull final JSONObject input,
+            @Nonnull @NonnullElements @Live final Multimap<String,IdPAttribute> results)
+                    throws AttributeDecodingException {
+        
+        final Collection<TranscodingRule> transcodingRules = registry.getTranscodingRules(input);
+        if (transcodingRules.isEmpty()) {
+            log.info("{} No transcoding rule for Attribute '{}'", getLogPrefix(), input);
+            return;
+        }
+        
+        for (final TranscodingRule rules : transcodingRules) {
+            final AttributeTranscoder<JSONObject> transcoder = TranscoderSupport.getTranscoder(rules);
+            final IdPAttribute decodedAttribute = transcoder.decode(profileRequestContext, input, rules);
+            if (decodedAttribute != null) {
+                results.put(decodedAttribute.getId(), decodedAttribute);
+            }
+        }
+    }
 
 }
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateTokenClaims.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateTokenClaims.java
index a740a3d..b1a2bb9 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateTokenClaims.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateTokenClaims.java
@@ -1,3 +1,20 @@
+/*
+ * 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;
@@ -18,7 +35,6 @@ import com.nimbusds.jwt.JWTClaimsSet;
 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.OpenIDConnectContext;
 import net.shibboleth.oidc.jwt.claims.ClaimsValidator;
 import net.shibboleth.oidc.jwt.claims.JWTValidationException;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
@@ -134,7 +150,7 @@ public class ValidateTokenClaims extends AbstractOIDCAuthenticationAction {
 
         final JWT token = jwtLookupStrategy.apply(profileRequestContext);
         if (token == null) {
-            log.error("{} id_token was not located, nothing to validate", getLogPrefix());
+            log.error("{} JWT was not located, nothing to validate", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_AUTHN_CTX);
             return false;
         }
@@ -145,7 +161,7 @@ public class ValidateTokenClaims extends AbstractOIDCAuthenticationAction {
                 throw new OIDCRPException("JWT ClaimsSet is null");
             }
         } catch (final ParseException | OIDCRPException e) {
-            log.error("{} Claimset of id_token is not available", getLogPrefix(),e);
+            log.error("{} JWT Claimset is not available", getLogPrefix(),e);
             ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_AUTHN_CTX);
             return false;
         }        
@@ -157,7 +173,7 @@ public class ValidateTokenClaims extends AbstractOIDCAuthenticationAction {
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
             @Nonnull final AuthenticationContext authenticationContext) {
 
-        log.debug("{} Validating token claims for subject '{}'",getLogPrefix(),
+        log.debug("{} Validating JWT token claims for subject '{}'",getLogPrefix(),
                 claimsSet.getSubject() != null ? claimsSet.getSubject() : "unknown subject");
          
         try {
@@ -166,7 +182,7 @@ public class ValidateTokenClaims extends AbstractOIDCAuthenticationAction {
                 cleanupHook.accept(profileRequestContext);
             }
         } catch (final JWTValidationException e) {
-            log.error("{} Token verification failed for subject '{}'", getLogPrefix(),claimsSet.getSubject(),e);
+            log.error("{} JWT token verification failed for subject '{}'", getLogPrefix(),claimsSet.getSubject(),e);
             ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
             if (cleanupHook != null) {
                 cleanupHook.accept(profileRequestContext);
@@ -174,7 +190,7 @@ public class ValidateTokenClaims extends AbstractOIDCAuthenticationAction {
             return;
         }
         //fine.
-        log.debug("{} Token claims are valid for subject '{}'",getLogPrefix(),claimsSet.getSubject());
+        log.debug("{} JWT token claims are valid for subject '{}'",getLogPrefix(),claimsSet.getSubject());
     }
     
 
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateUserInfoClaims.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateUserInfoClaims.java
new file mode 100644
index 0000000..5254b4d
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateUserInfoClaims.java
@@ -0,0 +1,171 @@
+/*
+ * 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 javax.annotation.Nullable;
+
+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.jwt.JWTClaimsSet;
+
+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;
+
+/**
+ * Validate a successful UserInfo Response according to section 5.3.2 of OpenID Connect Core 1.0. 
+ */
+public class ValidateUserInfoClaims extends AbstractOIDCAuthenticationAction {
+    
+    /** Class logger.*/
+    @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateUserInfoClaims.class);
+    
+    /** Strategy used to look up the {@link UserInfoResponseContext}. */
+    @Nonnull private Function<ProfileRequestContext, UserInfoResponseContext> 
+            userInfoResponseContextLookupStrategy;
+    
+    /** Strategy used to look up the {@link AccessTokenResponseContext}. */
+    @Nonnull private Function<ProfileRequestContext, AccessTokenResponseContext> 
+            tokenResponseContextLookupStrategy;
+    
+    /** The stashed user info response context.*/
+    @Nullable private UserInfoResponseContext userInfoCtx; 
+    
+    /** The stashed id_token claims.*/
+    @Nullable private JWTClaimsSet idTokenClaims; 
+
+    /** Constructor.*/
+    public ValidateUserInfoClaims() {
+        userInfoResponseContextLookupStrategy =
+                new ChildContextLookup<>(UserInfoResponseContext.class).compose(
+                        new InboundMessageContextLookup());
+        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");
+    }
+    
+    /**
+     * Set the strategy used to look up a {@link UserInfoResponseContext}.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setUserInfoResponseContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, UserInfoResponseContext> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        userInfoResponseContextLookupStrategy = Constraint.isNotNull(strategy,
+                "UserInfoResponseContext lookup strategy cannot be null");
+    }
+    
+    @Override protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final AuthenticationContext authenticationContext) {
+        
+        userInfoCtx = 
+                userInfoResponseContextLookupStrategy.apply(profileRequestContext);
+        if (userInfoCtx == null) {
+            log.debug("{} No UserInfo response context returned by lookup strategy", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            return false;
+        }
+        
+        final AccessTokenResponseContext tokenResponseCtx = 
+                tokenResponseContextLookupStrategy.apply(profileRequestContext);
+        if (tokenResponseCtx == null) {
+            log.debug("{} No AccessTokenResponseContext returned by lookup strategy", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            return false;
+        }
+        if (tokenResponseCtx.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();
+            if (idTokenClaims == null) {
+                log.debug("{} AccessTokenResponseContext did not contain an id_token with accessible claims, "
+                        + "possibly still encrypted", 
+                        getLogPrefix());
+                ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+                return false;
+            }
+        } catch (final ParseException e) {
+            log.debug("{} Unable to parse claims from id_token", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+        }
+        
+        return true;
+    }
+    
+    @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final AuthenticationContext authenticationContext) { 
+        
+          log.trace("{} Validating UserInfo claims", getLogPrefix());          
+         
+          final UserInfoResponse response = userInfoCtx.getUserInfo();
+          if (!response.isClaimsSetAvailable()) {
+              log.debug("{} UserInfo claims are not available, check response not still encrypted", getLogPrefix());
+              ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_USERINFO_CLAIMS);
+              return;
+          }
+          final String subFromUserInfo = response.getClaimsSet().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);
+              return;
+          }
+          // sub must match to id_token sub
+          if (!idTokenClaims.getSubject().equals(subFromUserInfo)){
+              log.debug("{} 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"));
+         
+    }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index c5fe8cc..b464d4d 100644
--- a/idp-oidc-rp-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/idp-oidc-rp-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -89,6 +89,14 @@
         </property> -->
     </bean>
     
+    <!-- Necessary for encoder parsing and claims mapping to function. -->
+    
+    <bean parent="shibboleth.RegistryNamingFunction" c:claz="net.minidev.json.JSONObject">
+        <constructor-arg name="function">
+            <bean class="net.shibboleth.oidc.attribute.transcoding.AbstractOIDCAttributeTranscoder.NamingFunction" />
+        </constructor-arg>    
+    </bean>
+    
     <!-- Controller implementation -->
     <bean id="shibboleth.oidc.rp.OpenIDConnectStartServlet"
         class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.AuthorizationController" />
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 a8f2a01..95e9857 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
@@ -206,8 +206,8 @@
         p:authenticationContextLookupStrategy-ref="ParentAuthenticiationContextLookup"
         p:httpClient="#{getObject('shibboleth.authn.oidc.rp.HttpClient') ?: getObject('shibboleth.InternalHttpClient')}"
         p:httpClientSecurityParameters="#{getObject('shibboleth.authn.oidc.rp.HttpClientSecurityParameters')}"
-        p:tokenResponseDecoderStrategy="#{getObject('shibboleth.authn.oidc.rp.TokenResponseDecoder') ?: getObject('shibboleth.authn.oidc.rp.DefaultTokenResponseDecoder')}"
-        p:tokenRequestEncoderStrategy="#{getObject('shibboleth.authn.oidc.rp.TokenRequestEncoder') ?: getObject('shibboleth.authn.oidc.rp.DefaultTokenRequestEncoder')}"/>   
+        p:httpResponseDecoderStrategy="#{getObject('shibboleth.authn.oidc.rp.TokenResponseDecoder') ?: getObject('shibboleth.authn.oidc.rp.DefaultTokenResponseDecoder')}"
+        p:httpRequestEncoderStrategy="#{getObject('shibboleth.authn.oidc.rp.TokenRequestEncoder') ?: getObject('shibboleth.authn.oidc.rp.DefaultTokenRequestEncoder')}"/>   
         
      <bean id="ValidateOAuthAccessTokenResponse" 
         class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateOAuthAccessTokenResponse"
@@ -224,7 +224,7 @@
     
     <!-- could these be singletons? --> 
     <bean id="shibboleth.authn.oidc.rp.DefaultTokenResponseDecoder" scope="prototype" 
-        class="net.shibboleth.idp.plugin.authn.oidc.rp.decoding.impl.DefaultTokenResponseDecoder"
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.decoding.impl.DefaultMapResponseDecoder"
         p:objectMapper-ref="shibboleth.authn.oidc.rp.JSONObjectMapper"/>
         
     <bean id="shibboleth.authn.oidc.rp.DefaultTokenRequestEncoder" scope="prototype" 
@@ -275,15 +275,16 @@
     
     <!-- Default id_token JWT validation wiring. -->
     
+     <!-- No default cleanup, maybe could be to remove nonce etc. -->
     <bean id="ValidateIDTokenClaims" scope="prototype"
        class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateTokenClaims"
        p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
        p:authenticationContextLookupStrategy-ref="ParentAuthenticiationContextLookup"
-       p:cleanupHook="#{getObject('shibboleth.authn.oidc.rp.jwt.claims.CleanUpHook') 
-           ?: getObject('shibboleth.authn.oidc.rp.jwt.claims.DefaultCleanupHook')}"
-       p:claimsValidator="#{getObject('shibboleth.authn.oidc.rp.IDTokenClaimsValidator') 
+       p:cleanupHook="#{getObject('shibboleth.authn.oidc.rp.idtoken.jwt.claims.CleanUpHook') 
+           ?: getObject('DefaultCleanupHook')}"
+       p:claimsValidator="#{getObject('shibboleth.authn.oidc.rp.idtoken.IDTokenClaimsValidator') 
            ?: getObject('DefaultIDTokenClaimsValidator')}"
-        p:jwtLookupStrategy="#{getObject('shibboleth.authn.oidc.rp.IDTokenLookupStrategy') 
+        p:jwtLookupStrategy="#{getObject('shibboleth.authn.oidc.rp.idtoken.IDTokenLookupStrategy') 
            ?: getObject('DefaultIDTokenLookupStrategy')}"/>
         
     <bean id="DefaultIDTokenLookupStrategy" 
@@ -291,7 +292,7 @@
 
     <bean id="DefaultIDTokenClaimsValidator"
         class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator"
-        p:claimValidators-ref="ClaimsValidators" />
+        p:claimValidators-ref="IDTokenClaimsValidators" />
         
     <bean id="OIDCProviderMetadataContextChildLookup"
         class="org.opensaml.messaging.context.navigate.ChildContextLookup"
@@ -366,7 +367,7 @@
         </property>
     </bean>
     
-    <bean id="nonceClaimValidator" 
+    <bean id="NonceClaimValidator" 
                 class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
                 p:claimName="nonce"
                 p:valueToMatchLookupStrategy="#{getObject('shibboleth.authn.oidc.rp.jwt.NonceLookupStrategy') ?: 
@@ -385,7 +386,7 @@
         class="org.opensaml.messaging.context.navigate.ChildContextLookup"
         c:type="#{ T(net.shibboleth.oidc.metadata.context.OIDCMetadataContext) }" />
     
-    <util:list id="ClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+    <util:list id="IDTokenClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
         <ref bean="IssuerClaimsValidator" /> <!-- TODO prevent: if it contains additional audiences not trusted by the Client. -->
         <ref bean="AudienceClaimsValidator" /> 
         <ref bean="AzpClaimRequiredValidator"/>
@@ -393,24 +394,92 @@
         <ref bean="ExpiryClaimsValidator" />
         <ref bean="IssuedAtClaimsValidator" /> 
         <ref bean="NotBeforeClaimsValidator" />
-        <ref bean="nonceClaimValidator" />
-        <!-- missing ACR? nonce, and auth_time, access_token at_hash. -->
+        <ref bean="NonceClaimValidator" />
+        <!-- missing ACR? and auth_time, access_token at_hash. -->
     </util:list> 
     
-   
+   <bean id="ValidateOIDCAuthentication" 
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateOIDCAuthentication" scope="prototype" 
+        p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
+        p:authenticationContextLookupStrategy-ref="ParentAuthenticiationContextLookup"
+        p:transcoderRegistry-ref="shibboleth.AttributeRegistryService"/>
+    
+    
+    <!-- UserInfo endpoint beans -->
+    
+    <bean id="UserInfoEndpointLookup" class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.UserInfoEndpointLookup"
+        p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
+        p:authenticationContextLookupStrategy-ref="ParentAuthenticiationContextLookup"
+        p:httpClient="#{getObject('shibboleth.authn.oidc.rp.HttpClient') ?: getObject('shibboleth.InternalHttpClient')}"
+        p:httpClientSecurityParameters="#{getObject('shibboleth.authn.oidc.rp.HttpClientSecurityParameters')}"
+        p:httpResponseDecoderStrategy="#{getObject('shibboleth.authn.oidc.rp.UserInfoResponseDecoder') ?: getObject('shibboleth.authn.oidc.rp.DefaultUserInfoResponseDecoder')}"
+        p:httpRequestEncoderStrategy="#{getObject('shibboleth.authn.oidc.rp.UserInfoRequestEncoder') ?: getObject('shibboleth.authn.oidc.rp.DefaultUserInfoRequestEncoder')}"/>   
+    
+    
+     <bean id="shibboleth.authn.oidc.rp.DefaultUserInfoResponseDecoder" scope="prototype" 
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.decoding.impl.DefaultUserInfoResponseDecoder"
+        p:objectMapper-ref="shibboleth.authn.oidc.rp.JSONObjectMapper"/>
+        
+    <bean id="shibboleth.authn.oidc.rp.DefaultUserInfoRequestEncoder" scope="prototype" 
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.encoding.impl.DefaultUserInfoRequestEncoder"/>
+    
+    
+    <bean id="ValidateUserInfoClaims" 
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateUserInfoClaims" scope="prototype" 
+        p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
+        p:authenticationContextLookupStrategy-ref="ParentAuthenticiationContextLookup"/>
+    
+    
+    <bean id="MergeUserInfoAndIDTokenClaims" 
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.MergeUserInfoAndIDTokenClaims" scope="prototype" 
+        p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
+        p:authenticationContextLookupStrategy-ref="ParentAuthenticiationContextLookup"/>
+    
+    
+    <!-- UserInfo response JWT validation -->
+    
+    <bean id="ValidateUserInfoTokenClaims" scope="prototype"
+       class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateTokenClaims"
+       p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
+       p:authenticationContextLookupStrategy-ref="ParentAuthenticiationContextLookup"
+       p:cleanupHook="#{getObject('shibboleth.authn.oidc.rp.userinfo.jwt.claims.CleanUpHook')}"
+       p:claimsValidator="#{getObject('shibboleth.authn.oidc.rp.userinfo.UserInfoTokenClaimsValidator') 
+           ?: getObject('DefaultUserInfoTokenClaimsValidator')}"
+        p:jwtLookupStrategy="#{getObject('shibboleth.authn.oidc.rp.userinfo.UserInfoTokenLookupStrategy') 
+           ?: getObject('DefaultUserInfoTokenLookupStrategy')}"/>
+    
+    <bean id="DefaultUserInfoTokenClaimsValidator"
+        class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator"
+        p:claimValidators-ref="UserInfoClaimsValidators" />
+        
+    <bean id="DefaultUserInfoTokenLookupStrategy" 
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.DefaultUserInfoTokenLookupStrategy"/>
+
+    <util:list id="UserInfoClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+        <ref bean="IssuerClaimsValidator" />
+        <ref bean="AudienceClaimsValidator" /> 
+    </util:list> 
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
     
     <!-- OLD STUFF -->
     
     <!-- TODO Add user import here
     <import resource="openidconnect-authn-config.xml" /> -->
 
-    <bean id="SetRPUIInformation" class="net.shibboleth.idp.ui.impl.SetRPUIInformation"
-        scope="prototype" p:activationCondition-ref="shibboleth.authn.oidc.rp.populateUIInfo"
-        p:httpServletRequest-ref="shibboleth.HttpServletRequest">
-        <property name="fallbackLanguages">
-            <bean parent="shibboleth.CommaDelimStringArray" c:_0="%{idp.ui.fallbackLanguages:}" />
-        </property>
-    </bean>
     
     <!-- Populate RP UI info from metadata? -->
 
@@ -418,8 +487,7 @@
     <bean id="ValidateIDTokenSignature"
         class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateIDTokenSignature" />
 
-    <bean id="ValidateOIDCAuthentication" 
-        class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateOIDCAuthentication" scope="prototype" />
+    
 
 
     <bean id="shibboleth.authn.oidc.rp.jwt.DefaultAuthTimeActivationCondition"
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 443ed3b..f200c46 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
@@ -91,8 +91,7 @@
         <evaluate expression="ExchangeCodeForAccessToken"/> 
         <evaluate expression="ValidateOAuthAccessTokenResponse"/>
         <evaluate expression="ExtractIDTokenFromTokenResponse"/>       
-        <evaluate expression="'proceed'" />
-        
+        <evaluate expression="'proceed'" />        
         <transition on="proceed" to="ValidateToken" />
     </action-state>
     
@@ -119,16 +118,60 @@
          <!-- <evaluate expression="PopulateTokenSignatureSigningParameters" /> -->
         <!--  <evaluate expression="PopulateTokenEncryptionParameters" /> -->
          <evaluate expression="ValidateIDTokenClaims" />
-         <transition on="proceed" to="SetPrincipal" />
+         <evaluate expression="'proceed'" />
+         <transition on="proceed" to="CheckUserInfoRequired" />
     </action-state>
-
-   <!--  <action-state id="ValidateOIDCTokenResponse">
-        <evaluate expression="ValidateIDTokenSignature" />
-        <evaluate expression="ValidateTokenClaims" />        
+    
+    <!-- Should we request information from the UserInfo endpoint based on profile config -->
+    <decision-state id="CheckUserInfoRequired">
+        <if test="true"
+            then="UserInfoRequest" />
+       <!-- if else here, we need to set id_token claims into EndUserClaimsContext -->
+    </decision-state>
+     
+     <action-state id="UserInfoRequest">
+        <evaluate expression="UserInfoEndpointLookup" />
+        <!-- Something needs to check TLS server certificate? -->
+        <evaluate expression="'proceed'" />
+        <transition on="proceed" to="CheckUserInfoResponseType" />
+     </action-state>
+     
+     <!-- Make strategy for these conditions 
+     Also, what about a plain JWT? need to extract out the claims for those too -->
+     <decision-state id="CheckUserInfoResponseType">
+        <if test="opensamlProfileRequestContext.getSubcontext('net.shibboleth.idp.authn.context.AuthenticationContext')
+                .getSubcontext('org.opensaml.profile.context.ProfileRequestContext').getInboundMessageContext()
+                .getSubcontext('net.shibboleth.idp.plugin.authn.oidc.rp.context.UserInfoResponseContext')
+                .getUserInfo().isSigned()"
+            then="ValidateSignedUserInfoJWT" />
+        <if test="opensamlProfileRequestContext.getSubcontext('net.shibboleth.idp.authn.context.AuthenticationContext')
+                 .getSubcontext('org.opensaml.profile.context.ProfileRequestContext').getInboundMessageContext()
+                 .getSubcontext('net.shibboleth.idp.plugin.authn.oidc.rp.context.UserInfoResponseContext')
+                 .getUserInfo().isEncrypted()"
+            then="DecryptUserInfoJWT" 
+            else="ValidateUserInfoClaimsSet"/>       
+     </decision-state>
+     
+     <action-state id="ValidateSignedUserInfoJWT">
+        <!-- <evaluate expression="PopulateTokenSignatureSigningParameters" /> -->
+        <evaluate expression="ValidateUserInfoTokenClaims" />
+        <evaluate expression="'proceed'" />
+        <transition on="proceed" to="ValidateUserInfoClaimsSet" />
+    </action-state>
+    
+    <action-state id="DecryptUserInfoJWT">
+        <!--  <evaluate expression="PopulateTokenEncryptionParameters" /> -->
+        <evaluate expression="ValidateUserInfoTokenClaims" />
+        <evaluate expression="'proceed'" />
+        <transition on="proceed" to="ValidateUserInfoClaimsSet" />
+    </action-state>
+    
+    <action-state id="ValidateUserInfoClaimsSet">
+        <evaluate expression="ValidateUserInfoClaims" />
+        <evaluate expression="MergeUserInfoAndIDTokenClaims" />
         <evaluate expression="'proceed'" />
         <transition on="proceed" to="SetPrincipal" />
-    </action-state> -->
-
+    </action-state>
 
     <action-state id="SetPrincipal">
         <evaluate expression="ValidateOIDCAuthentication" />
diff --git a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/service/attribute/registry/oidc-claim-rules.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/service/attribute/registry/oidc-claim-rules.xml
new file mode 100644
index 0000000..9de853b
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/service/attribute/registry/oidc-claim-rules.xml
@@ -0,0 +1,428 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xmlns:util="http://www.springframework.org/schema/util"
+       xmlns:p="http://www.springframework.org/schema/p"
+       xmlns:c="http://www.springframework.org/schema/c"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+                           
+       default-init-method="initialize"
+       default-destroy-method="destroy">
+
+    <!-- https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims -->
+
+    <bean parent="shibboleth.TranscodingRuleLoader">
+    <constructor-arg>
+    <list>
+
+        <!-- Typical inetOrgPerson attributes that map to standard claims. -->
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">displayName</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">name</prop>
+                    <prop key="displayName.en">Display name</prop>
+                    <prop key="displayName.de">Anzeigename</prop>
+                    <prop key="displayName.fr">Nom</prop>
+                    <prop key="displayName.it">Nome</prop>
+                    <prop key="displayName.ja">表示名</prop>
+                    <prop key="description.en">The name that should appear in white-pages-like applications for this person.</prop>
+                    <prop key="description.de">Anzeigename</prop>
+                    <prop key="description.fr">Nom complet d'affichage</prop>
+                    <prop key="description.it">Nome</prop>
+                    <prop key="description.ja">アプリケーションでの表示に用いられる英字氏名</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">givenName</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">given_name</prop>
+                    <prop key="displayName.en">Given name</prop>
+                    <prop key="displayName.de">Vorname</prop>
+                    <prop key="displayName.fr">Prénom</prop>
+                    <prop key="displayName.it">Nome</prop>
+                    <prop key="displayName.ja">名</prop>
+                    <prop key="description.en">Given name of a person</prop>
+                    <prop key="description.de">Vorname</prop>
+                    <prop key="description.fr">Prénom de l'utilisateur</prop>
+                    <prop key="description.it">Nome</prop>
+                    <prop key="description.ja">氏名(名)の英語表記</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">homePhone</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">phone_number</prop>
+                    <prop key="displayName.en">Private phone number</prop>
+                    <prop key="displayName.de">Telefon Privat</prop>
+                    <prop key="displayName.fr">Teléphone personnel</prop>
+                    <prop key="displayName.it">Numero di telefono privato</prop>
+                    <prop key="displayName.ja">自宅電話番号</prop>
+                    <prop key="description.en">Private phone number</prop>
+                    <prop key="description.de">Private Telefonnummer</prop>
+                    <prop key="description.fr">Numéro de téléphone de domicile de la personne</prop>
+                    <prop key="description.it">Numero di telefono privato</prop>
+                    <prop key="description.ja">自宅の電話番号</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">mail</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">email</prop>
+                    <prop key="displayName.en">E-mail</prop>
+                    <prop key="displayName.de">E-Mail</prop>
+                    <prop key="displayName.fr">Email</prop>
+                    <prop key="displayName.it">E-mail</prop>
+                    <prop key="displayName.ja">メールアドレス</prop>
+                    <prop key="description.en">E-Mail: Preferred address for e-mail to be sent to this person</prop>
+                    <prop key="description.de">E-Mail-Adresse</prop>
+                    <prop key="description.de-ch">E-Mail Adresse</prop>
+                    <prop key="description.fr">Adresse de courrier électronique</prop>
+                    <prop key="description.it">E-Mail: l'indirizzo e-mail preferito dall'utente</prop>
+                    <prop key="description.ja">メールアドレス</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">preferredLanguage</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">locale</prop>
+                    <prop key="displayName.en">Preferred Language</prop>
+                    <prop key="displayName.de">Bevorzugte Sprache</prop>
+                    <prop key="displayName.fr">Langue préférée</prop>
+                    <prop key="displayName.it">Lingua preferita</prop>
+                    <prop key="displayName.ja">希望言語</prop>
+                    <prop key="description.en">Preferred language: Users preferred language (see RFC1766)</prop>
+                    <prop key="description.de">Bevorzugte Sprache (siehe RFC1766)</prop>
+                    <prop key="description.fr">Exemple: fr, de, it, en, ... (voir RFC1766)</prop>
+                    <prop key="description.it">Lingua preferita: la lingua preferita dall'utente (cfr. RFC1766)</prop>
+                    <prop key="description.ja">利用者が希望する言語(RFC1766 を参照)</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">sn</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">family_name</prop>
+                    <prop key="displayName.en">Surname</prop>
+                    <prop key="displayName.de">Nachname</prop>
+                    <prop key="displayName.fr">Nom de famille</prop>
+                    <prop key="displayName.it">Cognome</prop>
+                    <prop key="displayName.ja">姓</prop>
+                    <prop key="description.en">Surname or family name</prop>
+                    <prop key="description.de">Familienname</prop>
+                    <prop key="description.fr">Nom de famille de l'utilisateur.</prop>
+                    <prop key="description.it">Cognome dell'utilizzatore</prop>
+                    <prop key="description.ja">氏名(姓)の英語表記</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">telephoneNumber</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">phone_number</prop>
+                    <prop key="displayName.en">Business phone number</prop>
+                    <prop key="displayName.de">Telefon Geschäft</prop>
+                    <prop key="displayName.fr">Teléphone professionnel</prop>
+                    <prop key="displayName.it">Numero di telefono dell'ufficio</prop>
+                    <prop key="displayName.ja">所属機関内電話番号</prop>
+                    <prop key="description.en">Business phone number: Office or campus phone number</prop>
+                    <prop key="description.de">Telefonnummer am Arbeitsplatz</prop>
+                    <prop key="description.fr">Teléphone de l'institut, de l'université</prop>
+                    <prop key="description.it">Numero di telefono dell'ufficio</prop>
+                    <prop key="description.ja">所属機関での利用者の電話番号</prop>
+                </props>
+            </property>
+        </bean>
+        
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">uid</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">preferred_username</prop>
+                    <prop key="displayName.en">User ID</prop>
+                    <prop key="displayName.de">Benutzer-ID</prop>
+                    <prop key="displayName.fr">ID utilisateur</prop>
+                    <prop key="displayName.it">ID dell'utente</prop>
+                    <prop key="displayName.ja">ユーザID</prop>
+                    <prop key="description.en">A unique identifier for a person, mainly used for user identification within the user's home organization.</prop>
+                    <prop key="description.de">Eine eindeutige Nummer für eine Person, welche hauptsächlich zur Identifikation innerhalb der Organisation benutzt wird.</prop>
+                    <prop key="description.fr">Identifiant de connexion d'une personnes sur les systèmes informatiques.</prop>
+                    <prop key="description.it">Identificativo unico della persona, usato per l'identificazione dell'utente all'interno della organizzazione di appartenenza.</prop>
+                    <prop key="description.ja">所属機関内で一意の利用者識別子</prop>
+                </props>
+            </property>
+        </bean>
+
+        <!-- eduPerson attributes that map to standard claims. -->
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">eduPersonNickname</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">nickname</prop>
+                    <prop key="displayName.en">Nickname</prop>
+                    <prop key="displayName.de">Kurzname</prop>
+                    <prop key="displayName.de-ch">Übername</prop>
+                    <prop key="displayName.fr">Surnom</prop>
+                    <prop key="displayName.it">Diminutivo</prop>
+                    <prop key="displayName.ja">ニックネーム</prop>
+                    <prop key="description.en">Person's nickname, or the informal name by which they are accustomed to be hailed.</prop>
+                    <prop key="description.de">Kurzname einer Person, oder üblicher Rufname zur Begrüßung.</prop>
+                    <prop key="description.de-ch">Übername einer Person, oder üblicher Rufname zur Begrüssung.</prop>
+                    <prop key="description.fr">Nom personnalisable pour un usage informel.</prop>
+                    <prop key="description.it">Diminutivo della persona, o  soprannome.</prop>
+                    <prop key="description.ja">利用者のニックネームもしくは通称</prop>
+                </props>
+            </property>
+        </bean>
+
+        <!-- A subset of the major eduPerson attributes that have no standard claim mapping. -->
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">eduPersonAssurance</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">eduPersonAssurance</prop>
+                    <prop key="displayName.en">Assurance level</prop>
+                    <prop key="displayName.de">Vertrauensgrad</prop>
+                    <prop key="displayName.fr">Niveau de confiance</prop>
+                    <prop key="displayName.it">Livello di sicurezza</prop>
+                    <prop key="displayName.ja">保証レベル</prop>
+                    <prop key="description.en">Set of URIs that assert compliance with specific standards for identity assurance.</prop>
+                    <prop key="description.de">URIs die eine gewisse Zusicherung für spezifische Standards des Vertrauens beinhalten</prop>
+                    <prop key="description.fr">Un ensemble d'URI qui attestent la conformité selon un standard pour les niveaux d'assurance d'identités</prop>
+                    <prop key="description.it">Un insieme di URI che asseriscono l'osservanza dei livelli di sicurezza richiesti</prop>
+                    <prop key="description.ja">IDの保証レベルに関して特定の基準に準拠していることを示すURI</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">eduPersonEntitlement</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">eduPersonEntitlement</prop>
+                    <prop key="displayName.en">Entitlement</prop>
+                    <prop key="displayName.de">Berechtigung</prop>
+                    <prop key="displayName.fr">Entitlement</prop>
+                    <prop key="displayName.it">Prerogativa</prop>
+                    <prop key="displayName.ja">資格情報</prop>
+                    <prop key="description.en">Member of: URI (either URL or URN) that indicates a set of rights to specific resources based on an agreement across the releavant community</prop>
+                    <prop key="description.de">Zeichenkette, die Rechte für spezifische Ressourcen beschreibt</prop>
+                    <prop key="description.fr">Membre de: URI (soit une URL ou une URN) décrivant un droit spécific d'accès.</prop>
+                    <prop key="description.it">Membro delle seguenti URI (sia URL o URN) che rappresentano diritti specifici d'accesso validi in tutta la communità</prop>
+                    <prop key="description.ja">特定のアプリケーションもしくはコミュニティ内の複数リソースへのアクセス権限を持つことを示すURI(URLもしくはURN)</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">eduPersonPrincipalName</prop>
+                    <prop key="transcoder">OIDCScopedStringTranscoder</prop>
+                    <prop key="oidc.name">eduPersonPrincipalName</prop>
+                    <prop key="displayName.en">Principal name</prop>
+                    <prop key="displayName.de">Persönliche ID</prop>
+                    <prop key="displayName.fr">Principal Name</prop>
+                    <prop key="displayName.it">Principal Name</prop>
+                    <prop key="displayName.ja">プリンシパルID</prop>
+                    <prop key="description.en">A unique identifier for a person, mainly for inter-institutional user identification.</prop>
+                    <prop key="description.de">Eindeutige Benutzeridentifikation</prop>
+                    <prop key="description.de-ch">Eindeutige Benützeridentifikation</prop>
+                    <prop key="description.fr">L'identifiant unique de l'utilisateur</prop>
+                    <prop key="description.it">Un ID personale che identifica chiaramente l'utente in seno alla sua organizzazione</prop>
+                    <prop key="description.ja">フェデレーション内で一意かつ永続的な利用者識別子</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">eduPersonScopedAffiliation</prop>
+                    <prop key="transcoder">OIDCScopedStringTranscoder</prop>
+                    <prop key="oidc.name">eduPersonScopedAffiliation</prop>
+                    <prop key="displayName.en">Scoped affiliation</prop>
+                    <prop key="displayName.de">Zugehörigkeit</prop>
+                    <prop key="displayName.fr">Affiliation</prop>
+                    <prop key="displayName.it">Tipo di membro</prop>
+                    <prop key="displayName.ja">スコープ付き職位</prop>
+                    <prop key="description.en">Specifies the person's affiliation within a particular security domain</prop>
+                    <prop key="description.de">Art der Zugehörigkeit zur Heimatorganisation</prop>
+                    <prop key="description.de-ch">Art der Zugehörigkeit zur Heimorganisation</prop>
+                    <prop key="description.fr">Type d'affiliation dans l'organisation</prop>
+                    <prop key="description.it">Tipo di membro: Tipo di lavoro svolto per l'organizzazione</prop>
+                    <prop key="description.ja">セキュリティドメインのスコープが付いた所属機関における職位</prop>
+                </props>
+            </property>
+        </bean>
+
+        <!-- The remainder are standard OIDC claims, which we map based on the actual claim name. -->
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">address</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.asObject">true</prop>
+                    <prop key="oidc.name">address</prop>
+                    <prop key="displayName.en">Postal address</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">birthdate</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">birthdate</prop>
+                    <prop key="displayName.en">Date of birth</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">email_verified</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">email_verified</prop>
+                    <prop key="oidc.asBoolean">true</prop>
+                    <prop key="displayName.en">E-mail verification status</prop>
+                    <prop key="description.en">Indicates whether e-mail address has been verified by the issuer</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">gender</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">gender</prop>
+                    <prop key="displayName.en">Gender</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">middle_name</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">middle_name</prop>
+                    <prop key="displayName.en">Middle name</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">phone_number_verified</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">phone_number_verified</prop>
+                    <prop key="oidc.asBoolean">true</prop>
+                    <prop key="displayName.en">Phone number verification status</prop>
+                    <prop key="description.en">Indicates whether phone number has been verified by the issuer</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">picture</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">picture</prop>
+                    <prop key="displayName.en">Picture</prop>
+                    <prop key="description.en">URL of personal photo</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">profile</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">profile</prop>
+                    <prop key="displayName.en">Profile page</prop>
+                    <prop key="description.en">URL of personal profile page</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">website</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">website</prop>
+                    <prop key="displayName.en">Web site</prop>
+                    <prop key="description.en">URL to personal web site</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">updated_at</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">updated_at</prop>
+                    <prop key="oidc.asInteger">true</prop>
+                    <prop key="displayName.en">Last update of information</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">zoneinfo</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">zoneinfo</prop>
+                    <prop key="displayName.en">Time zone</prop>
+                </props>
+            </property>
+        </bean>
+
+    </list>
+    </constructor-arg>
+    </bean>
+    
+</beans>
diff --git a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/service/attribute/registry/postconfig.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/service/attribute/registry/postconfig.xml
new file mode 100644
index 0000000..75ffe22
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/service/attribute/registry/postconfig.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+    xmlns:context="http://www.springframework.org/schema/context"
+    xmlns:util="http://www.springframework.org/schema/util"
+    xmlns:p="http://www.springframework.org/schema/p"
+    xmlns:c="http://www.springframework.org/schema/c"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+
+    default-init-method="initialize" default-destroy-method="destroy">
+
+    <!-- Necessary for encoder parsing and claims mapping to function. -->
+    
+    <bean id="OIDCByteTranscoder"
+        class="net.shibboleth.oidc.attribute.transcoding.impl.OIDCByteAttributeTranscoder" />
+
+    <bean id="OIDCStringTranscoder"
+        class="net.shibboleth.oidc.attribute.transcoding.impl.OIDCStringAttributeTranscoder" />
+
+    <bean id="OIDCScopedStringTranscoder"
+        class="net.shibboleth.oidc.attribute.transcoding.impl.OIDCScopedStringAttributeTranscoder" />
+    
+</beans>
diff --git a/idp-oidc-rp-impl/src/main/resources/templates/oidc-request-form-post.vm b/idp-oidc-rp-impl/src/main/resources/templates/oidc-request-form-post.vm
index 8b303e0..e8381a2 100644
--- a/idp-oidc-rp-impl/src/main/resources/templates/oidc-request-form-post.vm
+++ b/idp-oidc-rp-impl/src/main/resources/templates/oidc-request-form-post.vm
@@ -33,7 +33,9 @@
 
             <input type="hidden" name="prompt" value="${prompt}" />#end #if($request)
 
-            <input type="hidden" name="request" value="${request}" />#end 
+            <input type="hidden" name="request" value="${request}" />#end #if($nonce)
+            
+            <input type="hidden" name="nonce" value="${nonce}" />#end
         </div>
         <noscript>
             <div>
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultTokenResponseDecoderTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultTokenResponseDecoderTest.java
index b006417..25587e5 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultTokenResponseDecoderTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultTokenResponseDecoderTest.java
@@ -41,12 +41,12 @@ import net.shibboleth.utilities.java.support.component.ComponentInitializationEx
 public class DefaultTokenResponseDecoderTest extends AbstractOIDCTest {
     
     /** The encoder to test.*/
-    private DefaultTokenResponseDecoder decoder;
+    private DefaultMapResponseDecoder decoder;
     
     @BeforeMethod
     public void setup() throws Exception {
         super.setup();
-        decoder = new DefaultTokenResponseDecoder();
+        decoder = new DefaultMapResponseDecoder();
         decoder.setObjectMapper(new ObjectMapper());
     }
     
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 efe5b44..f2dede9 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
@@ -101,6 +101,16 @@ public abstract class AbstractOIDCTest {
             + "  \"scope\": \"openid\"\n"
             + "}";
     
+    /** Mock response from the UserInfo endpoint.*/
+    @Nonnull @NotEmpty
+    protected final String USERINFO_RESPONSE ="{sub=user-subject-1234531, "
+            + "website=https://openid.net/, "
+            + "zoneinfo=America/Los_Angeles, "
+            + "birthdate=2000-02-03, gender=female, "
+            + "preferred_username=d.tu, "
+            + "given_name=Demo, middle_name=Theresa, locale=en-US, "
+            + "updated_at=1580000000, name=Demo T. User, nickname=Dee, family_name=User}";
+    
     /** Client metadata.*/
     @Nonnull protected final String CLIENT_METADATA = "[\n"
             + "  {\n"
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultClaimMergingStrategyTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultClaimMergingStrategyTest.java
new file mode 100644
index 0000000..1ade5f1
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultClaimMergingStrategyTest.java
@@ -0,0 +1,97 @@
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+
+import javax.annotation.Nonnull;
+
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
+
+import net.minidev.json.JSONObject;
+import net.minidev.json.parser.JSONParser;
+import net.minidev.json.parser.ParseException;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+
+/** Test for the DefaultClaimMergingStrategy.*/
+public class DefaultClaimMergingStrategyTest {
+    
+    /** Mock response from the UserInfo endpoint.*/
+    @Nonnull @NotEmpty
+    protected final String USERINFO_RESPONSE ="{\n"
+            + "  \"sub\": \"jdoe\",\n"
+            + "  \"website\": \"https://openid.net/\",\n"
+            + "  \"zoneinfo\": \"America/Los_Angeles\",\n"
+            + "  \"birthdate\": \"2000-02-03\",\n"
+            + "  \"gender\": \"female\",\n"
+            + "  \"preferred_username\": \"d.tu\",\n"
+            + "  \"given_name\": \"From UserInfo\",\n"
+            + "  \"middle_name\": \"From UserInfo\",\n"
+            + "  \"locale\": \"en-US\",\n"
+            + "  \"updated_at\": 1580000000,\n"
+            + "  \"name\": \"Demo T. User\",\n"
+            + "  \"family_name\": \"User\"\n"
+            + "}";
+    
+    @Test
+    public void testMerge() throws ParseException {
+        
+        final JSONParser parser = new JSONParser(JSONParser.MODE_JSON_SIMPLE);
+        final ClaimsSet userInfo = new ClaimsSet((JSONObject)parser.parse(USERINFO_RESPONSE));
+        
+        final JWTClaimsSet idTokenClaims = new JWTClaimsSet.Builder().subject("jdoe")
+                .claim("given_name", "FromIdToken")
+                .claim("nickname", "FromIdToken")
+                .build();
+        
+        final DefaultClaimMergingStrategy strategy = new DefaultClaimMergingStrategy();
+        final ClaimsSet merged = strategy.apply(userInfo, idTokenClaims);
+        
+        // This is a merged claim
+        assertEquals(merged.getClaim("given_name"), "FromIdToken");
+        
+        // This is a claim only in the id_token
+        assertEquals(merged.getClaim("nickname"), "FromIdToken");
+        
+        // This is a claim only in the userInfo response
+        assertEquals(merged.getClaim("middle_name"), "From UserInfo");
+    }
+    
+    @Test
+    public void testMergeNullUserInfo() throws ParseException {
+        
+        final JWTClaimsSet idTokenClaims = new JWTClaimsSet.Builder().subject("jdoe")
+                .claim("given_name", "FromIdToken")
+                .claim("nickname", "FromIdToken")
+                .build();
+        
+        final DefaultClaimMergingStrategy strategy = new DefaultClaimMergingStrategy();
+        final ClaimsSet merged = strategy.apply(null, idTokenClaims);
+        
+
+        assertEquals(merged.getClaim("given_name"), "FromIdToken");        
+        assertEquals(merged.getClaim("nickname"), "FromIdToken");
+        // not in id_token
+        assertNull(merged.getClaim("middle_name"));
+    }
+    
+    @Test
+    public void testMergeNullIdToken() throws ParseException {
+        
+        final JSONParser parser = new JSONParser(JSONParser.MODE_JSON_SIMPLE);
+        final ClaimsSet userInfo = new ClaimsSet((JSONObject)parser.parse(USERINFO_RESPONSE));
+        
+        final DefaultClaimMergingStrategy strategy = new DefaultClaimMergingStrategy();
+        final ClaimsSet merged = strategy.apply(userInfo, null);
+
+        assertEquals(merged.getClaim("given_name"), "From UserInfo");      
+
+        assertEquals(merged.getClaim("middle_name"), "From UserInfo");
+        // not in UserInfo claims
+        assertNull(merged.getClaim("nickname"));
+
+    }
+
+}
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/ExchangeCodeForTokenTest.java
index c2dccf9..48ccf8b 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/ExchangeCodeForTokenTest.java
@@ -18,7 +18,6 @@
 package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertNull;
 
@@ -98,14 +97,14 @@ public class ExchangeCodeForTokenTest extends AbstractOIDCTest {
         // create new client with mock response
         exchangeAction.setHttpClient(httpClient);
         
-        exchangeAction.setTokenRequestEncoderStrategy(prc -> {
+        exchangeAction.setHttpRequestEncoderStrategy(prc -> {
             URI uri;
             try {
                 uri = new URIBuilder().setScheme("https")
                         .setHost("op.example.com")
                         .setPath("/token")
                         .build();
-            } catch (URISyntaxException e) {
+            } catch (final URISyntaxException e) {
                 return null;
             }                 
             
@@ -115,12 +114,12 @@ public class ExchangeCodeForTokenTest extends AbstractOIDCTest {
                     .setCharset(StandardCharset.UTF_8);
             return rb.build();
         });     
-        exchangeAction.setTokenResponseDecoderStrategy(response -> {
-            ObjectMapper mapper = new ObjectMapper();
+        exchangeAction.setHttpResponseDecoderStrategy(response -> {
+            final ObjectMapper mapper = new ObjectMapper();
             try {
                 return mapper.readValue(
                         httpResponse.getEntity().getContent(), new TypeReference<Map<String, Object>>() {});
-            } catch (UnsupportedOperationException | IOException e) {                
+            } catch (final UnsupportedOperationException | IOException e) {                
                 return null;
             }
         });
@@ -145,14 +144,14 @@ public class ExchangeCodeForTokenTest extends AbstractOIDCTest {
         // create new client with mock response
         exchangeAction.setHttpClient(httpClient);
         
-        exchangeAction.setTokenRequestEncoderStrategy(prc -> {
+        exchangeAction.setHttpRequestEncoderStrategy(prc -> {
             URI uri;
             try {
                 uri = new URIBuilder().setScheme("https")
                         .setHost("op.example.com")
                         .setPath("/token")
                         .build();
-            } catch (URISyntaxException e) {
+            } catch (final URISyntaxException e) {
                 return null;
             }                 
             
@@ -162,7 +161,7 @@ public class ExchangeCodeForTokenTest extends AbstractOIDCTest {
                     .setCharset(StandardCharset.UTF_8);
             return rb.build();
         });     
-        exchangeAction.setTokenResponseDecoderStrategy(response -> null);
+        exchangeAction.setHttpResponseDecoderStrategy(response -> null);
         
         exchangeAction.initialize();
 
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
index 3f16ce5..eb12fe0 100644
--- 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
@@ -18,7 +18,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.nimbusds.jose.shaded.json.parser.JSONParser;
 
 import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.idp.plugin.authn.oidc.rp.context.TokenResponseContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
 import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 
@@ -33,7 +33,7 @@ public class ExtractIDTokenFromResponseTest extends AbstractOIDCTest {
         super.setup();
         action = new ExtractIDTokenFromResponse();       
 
-        final TokenResponseContext trc = new TokenResponseContext();
+        final AccessTokenResponseContext trc = new AccessTokenResponseContext();
         prc.getInboundMessageContext().addSubcontext(trc);        
 
         action.setProfileContextLookupStrategy(new ChildContextLookup<>(ProfileRequestContext.class).compose(
@@ -62,7 +62,7 @@ public class ExtractIDTokenFromResponseTest extends AbstractOIDCTest {
         action.initialize();
         final Event event = action.execute(src);
         assertNull(event);
-        assertNotNull(prc.getInboundMessageContext().getSubcontext(TokenResponseContext.class).getIdToken());
+        assertNotNull(prc.getInboundMessageContext().getSubcontext(AccessTokenResponseContext.class).getIdToken());
         
     }
     
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 ca6bd64..a34bad7 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
@@ -17,11 +17,9 @@
 
 package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
+import java.net.InetAddress;
 import java.net.URI;
+import java.net.UnknownHostException;
 import java.time.Instant;
 import java.util.Date;
 import java.util.HashMap;
@@ -30,16 +28,11 @@ import java.util.Map;
 
 import javax.annotation.Nonnull;
 
-import org.apache.http.HttpResponse;
-import org.apache.http.StatusLine;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.ResponseHandler;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.entity.StringEntity;
-import org.apache.http.protocol.HttpContext;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
+import org.apache.http.conn.ssl.TrustAllStrategy;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.ssl.SSLContextBuilder;
 import org.junit.Test;
-import org.mockito.Mockito;
 import org.opensaml.messaging.context.MessageContext;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
@@ -56,14 +49,18 @@ import com.nimbusds.jose.JOSEException;
 import com.nimbusds.jose.JOSEObjectType;
 import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.jose.JWSHeader;
-import com.nimbusds.jose.KeyLengthException;
+import com.nimbusds.jose.crypto.ECDSASigner;
 import com.nimbusds.jose.crypto.MACSigner;
+import com.nimbusds.jose.jwk.Curve;
+import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
 import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.ParseException;
 import com.nimbusds.oauth2.sdk.ResponseMode;
 import com.nimbusds.oauth2.sdk.ResponseType;
 import com.nimbusds.oauth2.sdk.id.ClientID;
 import com.nimbusds.oauth2.sdk.id.State;
+import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
 import com.nimbusds.openid.connect.sdk.AuthenticationResponseParser;
 import com.nimbusds.openid.connect.sdk.Nonce;
 import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
@@ -75,29 +72,40 @@ import net.minidev.json.parser.JSONParser;
 import net.shibboleth.ext.spring.config.IdentifiableBeanPostProcessor;
 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.context.OIDCPeerEntityContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.ResponseTypeAndModeContext;
 import net.shibboleth.idp.plugin.authn.test.flow.AbstractAuthnXmlFlowExecutionTests;
 import net.shibboleth.idp.plugin.authn.test.flow.mock.MockFlowBuilder;
+import net.shibboleth.idp.profile.context.RelyingPartyContext;
 import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
 import net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext;
+import net.shibboleth.oidc.profile.config.OIDCAuthorizationConfiguration;
 import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import okhttp3.tls.HandshakeCertificates;
+import okhttp3.tls.HeldCertificate;
 
 /** Test the OIDC relying party flow.*/
 public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
 
-
+    /**
+     * Example of good provider metadata. Endpoints are localhost to support the 
+     * mock server that is started. 
+     */
     private final String GOOD_PROVIDER_CONFIGURATION_INFO = "{\n"
             + "\"issuer\": \"https://op.example.com\",\n"
-            + "\"authorization_endpoint\": \"https://op.example.com/o/oauth2/v2/auth\",\n"
-            + "\"device_authorization_endpoint\": \"https://op.example.com/device/code\",\n"
-            + "\"token_endpoint\": \"https://op.example.com/token\",\n"
-            + "\"userinfo_endpoint\": \"https://op.example.com/v1/userinfo\",\n"
-            + "\"revocation_endpoint\": \"https://op.example.com/revoke\",\n"
-            + "\"jwks_uri\": \"https://op.example.com/oauth2/v3/certs\",\n"
+            + "\"authorization_endpoint\": \"https://localhost:9918/o/oauth2/v2/auth\",\n"
+            + "\"device_authorization_endpoint\": \"https://localhost:9918/device/code\",\n"
+            + "\"token_endpoint\": \"https://localhost:9918/token\",\n"
+            + "\"userinfo_endpoint\": \"https://localhost:9918/v1/userinfo\",\n"
+            + "\"revocation_endpoint\": \"https://localhost:9918/revoke\",\n"
+            + "\"jwks_uri\": \"https://localhost:9918/oauth2/v3/certs\",\n"
             + "\"response_types_supported\": [\n"
             + "\"code\",\n"
             + "\"token\",\n"
@@ -165,7 +173,24 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
             + "  }"
             + "]";
 
-    
+    /** Mock JSON Object response from the UserInfo endpoint.*/
+    @Nonnull @NotEmpty
+    protected final String USERINFO_RESPONSE ="{\n"
+            + "  \"sub\": \"jdoe\",\n"
+            + "  \"website\": \"https://openid.net/\",\n"
+            + "  \"zoneinfo\": \"America/Los_Angeles\",\n"
+            + "  \"birthdate\": \"2000-02-03\",\n"
+            + "  \"gender\": \"female\",\n"
+            + "  \"preferred_username\": \"d.tu\",\n"
+            + "  \"given_name\": \"Demo\",\n"
+            + "  \"middle_name\": \"Theresa\",\n"
+            + "  \"locale\": \"en-US\",\n"
+            + "  \"updated_at\": 1580000000,\n"
+            + "  \"name\": \"Demo T. User\",\n"
+            + "  \"nickname\": \"Dee\",\n"
+            + "  \"family_name\": \"User\"\n"
+            + "}";
+
     
     /** Path to the flow to be tested.*/
     @Nonnull private static final String FLOW = 
@@ -188,6 +213,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
             "classpath:/flows/authn/conditions/conditions-flow.xml","authn/conditions",
             "classpath:/conf/authn/authn-events-flow.xml","authn.events");
 
+
     /** Constructor.*/
     public OIDCRPFlowTest() {
         super("http://idp.example.org");        
@@ -224,31 +250,11 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         
         
         try {
-            //create Mock HttpClient
-            /*
-             * This is brittle and only works because the HttpGet request for the metadata
-             * is different than that for the type of request to the token endpoint. If
-             * more endpoints are needed, this should be done properly.
-             */
-            final HttpClient httpClient = mock(HttpClient.class);
-            
-            final HttpResponse httpResponse = Mockito.mock(HttpResponse.class);
-            final StatusLine statusLine = Mockito.mock(StatusLine.class);
-            when(httpResponse.getStatusLine()).thenReturn(statusLine);
-            when(statusLine.getStatusCode()).thenReturn(200);
-
-            when(httpClient.
-                    execute(any(HttpGet.class),any(ResponseHandler.class),any(HttpContext.class)))
-                    .thenReturn(OIDCProviderMetadata.parse(GOOD_PROVIDER_CONFIGURATION_INFO));
-            
-            
-            
-            Mockito.when(httpResponse.getEntity()).thenReturn(new StringEntity(createAccessTokenResponseJSON()));
-            Mockito.when(httpClient.execute((HttpUriRequest) Mockito.any(), (HttpContext) Mockito.any()))
-            .thenReturn(httpResponse);
-            
-            addBeanSingleton(builderContext, "shibboleth.InternalHttpClient", httpClient);
-            
+            // Create a HttpClient which turns off hostname verification and trusts all certificates
+            addBeanSingleton(builderContext, "shibboleth.InternalHttpClient", 
+                        HttpClients.custom().setSSLContext(new SSLContextBuilder()
+                                .loadTrustMaterial(null, TrustAllStrategy.INSTANCE).build())
+                                .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).build());
            
         } catch (final Exception e) {
             log.error("Could not mock HTTP response",e);
@@ -262,6 +268,9 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
                 new ClassPathResource("conf/test-relyingparty-resolver-service.xml"));
         
         loadBeanDefinitionsFromXmlFile(builderContext, new ClassPathResource("conf/additional-system-beans.xml"));
+        
+        loadBeanDefinitionsFromXmlFile(builderContext, 
+                new ClassPathResource("attribute/registry/postconfig.xml"));
     }
     
     /**
@@ -272,10 +281,9 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
      * 
      * @return a serialized access token response.
      * 
-     * @throws KeyLengthException on error
-     * @throws JOSEException on error
+     * @throws Exception on error.
      */
-    private String createAccessTokenResponseJSON() throws KeyLengthException, JOSEException {
+    private String createAccessTokenResponseJSON() throws Exception {
         final var header = new JWSHeader.Builder(JWSAlgorithm.HS256)
                 .type(JOSEObjectType.JWT)
                 .build();
@@ -285,8 +293,10 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
                 .subject("jdoe")
                 .claim("nonce", "abadnonce")
                 .claim("azp", "demo_rp")
+                .claim("name","jdoe")
                 .expirationTime(Date.from(Instant.now().plusSeconds(120)))
                 .build();
+        payload.getClaims().forEach((k,v) -> log.debug("{}:{}",k,v));
         final var signedJWT = new SignedJWT(header,payload);
         signedJWT.sign(new MACSigner("Xp2s5v8y/B?E(H+MbQeThWmYq3t6w9z$"));
         final String accessTokenSerialized = "{\n"
@@ -299,6 +309,119 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         return accessTokenSerialized;
     }
     
+    private String createSignedUserInfoJWTResponseJSON(final String issuer, final String audience) 
+            throws JOSEException {
+        
+        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(audience)
+                .subject("jdoe")
+                .claim("preferred_username", "jdoe")
+                .claim("name", "J Doe")
+                .build();
+        
+        final var signedJWT = new SignedJWT(header, payload);
+        signedJWT.sign(new ECDSASigner(key.toECPrivateKey()));
+        return signedJWT.serialize();
+    }
+    
+    /**
+     * Create a running server that mimics responses from an OpenID Connect provider.
+     * Creates a new self-signed certificate.
+     * 
+     * @return the simple server.
+     * 
+     * @throws UnknownHostException on error.
+     */
+    private MockWebServer createSimpleServer() throws UnknownHostException {
+        //start mock server
+        final MockWebServer mockServer = new MockWebServer();
+        final String localhost = InetAddress.getByName("localhost").getCanonicalHostName();
+        final HeldCertificate localhostCertificate = new HeldCertificate.Builder()
+            .addSubjectAlternativeName(localhost)
+            .build();
+        final HandshakeCertificates serverCertificates = new HandshakeCertificates.Builder()
+                .heldCertificate(localhostCertificate)
+                .build();
+        mockServer.useHttps(serverCertificates.sslSocketFactory(), false);      
+        
+        return mockServer;
+    }
+    
+    /**
+     * Create an {@link OIDCPeerEntityContext}.
+     * 
+     * @return the peer entity context.
+     * 
+     * @throws ParseException on error.
+     */
+    private OIDCPeerEntityContext createPeerContext() throws ParseException {
+        final OIDCPeerEntityContext peerCtx = new OIDCPeerEntityContext();
+        final OIDCProviderMetadata providerMetadata = OIDCProviderMetadata.parse(GOOD_PROVIDER_CONFIGURATION_INFO);
+        final OIDCProviderMetadataContext providerMetadataCtx = new OIDCProviderMetadataContext();
+        providerMetadataCtx.setProviderInformation(providerMetadata);
+        peerCtx.addSubcontext(providerMetadataCtx);
+        return peerCtx;
+    }
+    
+    /**
+     * Create an OIDC authentication request.
+     * 
+     * @return the authentication request.
+     */
+    private OIDCAuthenticationRequest createAuthenticationRequest() {
+        final OIDCAuthenticationRequest request = new OIDCAuthenticationRequest(new ClientID("https://op.example.com"));
+        request.setState(new State("8df98fd63a53fa5b5433d6f8754bca5d.65317332"));
+        request.setNonce(new Nonce("abadnonce"));
+        return request;
+    }
+    
+    /**
+     * Create a client metadata context
+     * 
+     * @return the context.
+     * 
+     * @throws Exception on error.
+     */
+    private OIDCMetadataContext createClientMedataContext() throws Exception {
+        final JSONParser parser = new JSONParser(JSONParser.MODE_JSON_SIMPLE);
+        final OIDCMetadataContext metadataContext = new OIDCMetadataContext();
+        metadataContext.setClientInformation(
+                OIDCClientInformation.parse((JSONObject)((JSONArray)parser.parse(CLIENT_METADATA)).get(0)));
+        return metadataContext;
+    }
+    
+    /**
+     * Create a response mode and type context.
+     * 
+     * @return the context.
+     */
+    private ResponseTypeAndModeContext createResponseTypeAndModeContext() {
+        final ResponseTypeAndModeContext respCtx = new ResponseTypeAndModeContext();
+        respCtx.setResponseMode(ResponseMode.QUERY);
+        respCtx.setResponseType(ResponseType.CODE);
+        return respCtx;
+    }
+    
+    /** 
+     * Create an authentication response.
+     * 
+     * @return the OIDC authentication response.
+     * 
+     * @throws Exception on error.
+     */
+    private AuthenticationResponse createAuthenticationResponse() throws Exception {
+        return AuthenticationResponseParser.parse(
+                new URI("/idp/profile/Authn/OIDC/RP/callback"
+                        + "?state=8df98fd63a53fa5b5433d6f8754bca5d.65317332&code=z8C2DCp6sn0D9aGbEqlrFesdPVRXPtDX"));
+    }
+    
     
     @Override
     @Nonnull protected ProfileRequestContext buildProfileRequestContext(@Nonnull final String flowId,
@@ -330,12 +453,14 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
 
         final FlowExecution flowExecution = getFlowExecutionFactory().createFlowExecution(getFlowDefinition());  
         flowExecution.getConversationScope()
-                    .put("opensamlProfileRequestContext", buildProfileRequestContext("authn/OIDCRelyingParty",false,true));
+                    .put("opensamlProfileRequestContext", 
+                            buildProfileRequestContext("authn/OIDCRelyingParty",false,true));
         updateFlowExecution(flowExecution);
         flowExecution.start(inputMap, externalContext);    
         assertCurrentStateEquals("AuthRequest");
     }
     
+    
     /** 
      * Test the flow from the external authorization request to the end of the flow.
      * 
@@ -359,6 +484,18 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
                 "idp.authn.oidc.rp.proxyIssuer","https://op.example.com");
         
         setMockProperties(mockProperties);
+        
+        final MockWebServer mockOPServer = createSimpleServer();
+        // First is token exchange
+        mockOPServer.enqueue(new MockResponse().setResponseCode(200)
+                .setHeader("content-type", "application/json")
+                .setBody(createAccessTokenResponseJSON()));
+        // Second is userInfo
+        mockOPServer.enqueue(new MockResponse().setResponseCode(200)
+                .setHeader("content-type", "application/json")
+                .setBody(USERINFO_RESPONSE));
+        mockOPServer.start(9918);
+        
 
         final FlowExecutionImpl flowExecution = (FlowExecutionImpl)getFlowExecutionFactory()
                 .createFlowExecution(getFlowDefinition());
@@ -367,44 +504,115 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         
         // create a nested PRC under the authentication context
         final ProfileRequestContext nestPrc = (ProfileRequestContext) 
-                prc.getSubcontext(AuthenticationContext.class).addSubcontext(new ProfileRequestContext(), true);       
+                prc.getSubcontext(AuthenticationContext.class).addSubcontext(new ProfileRequestContext(), true);  
+        
+        // Add under nest PRC
+        final RelyingPartyContext partyContext = new RelyingPartyContext();
+        final OIDCAuthorizationConfiguration partyConfig = new OIDCAuthorizationConfiguration();        
+        partyContext.setProfileConfig(partyConfig);
+        nestPrc.addSubcontext(partyContext);
        
-        final MessageContext outMsgCtx = new MessageContext();
-        final OIDCAuthenticationRequest request = new OIDCAuthenticationRequest(new ClientID("https://op.example.com"));
-        request.setState(new State("8df98fd63a53fa5b5433d6f8754bca5d.65317332"));
-        request.setNonce(new Nonce("abadnonce"));
-        outMsgCtx.setMessage(request);
+        // Setup outbound context
+        final MessageContext outMsgCtx = new MessageContext();        
+        outMsgCtx.setMessage(createAuthenticationRequest());        
+        outMsgCtx.addSubcontext(createPeerContext());
+        outMsgCtx.addSubcontext(createResponseTypeAndModeContext());
+        outMsgCtx.addSubcontext(createClientMedataContext());
+        nestPrc.setOutboundMessageContext(outMsgCtx);    
+        
+        // Setup inbound context.
+        final MessageContext inMsgCtx = new MessageContext();
+        inMsgCtx.setMessage(createAuthenticationResponse());
+        nestPrc.setInboundMessageContext(inMsgCtx);
         
-        final OIDCPeerEntityContext peerCtx = new OIDCPeerEntityContext();
-        OIDCProviderMetadata providerMetadata = OIDCProviderMetadata.parse(GOOD_PROVIDER_CONFIGURATION_INFO);
-        final OIDCProviderMetadataContext providerMetadataCtx = new OIDCProviderMetadataContext();
-        providerMetadataCtx.setProviderInformation(providerMetadata);
-        peerCtx.addSubcontext(providerMetadataCtx);
+        // Add prc to flow.
+        prc.getSubcontext(AuthenticationContext.class)
+        .addSubcontext(new ExternalAuthenticationContext(new ExternalAuthenticationImpl(false)));
+                        flowExecution.getConversationScope().put("opensamlProfileRequestContext",prc);
+                        
         
-        outMsgCtx.addSubcontext(peerCtx);
-        nestPrc.setOutboundMessageContext(outMsgCtx);
+        updateFlowExecution(flowExecution);
         
-        final ResponseTypeAndModeContext respCtx = new ResponseTypeAndModeContext();
-        respCtx.setResponseMode(ResponseMode.QUERY);
-        respCtx.setResponseType(ResponseType.CODE);
-        nestPrc.getOutboundMessageContext().addSubcontext(respCtx);
+        //set start view and ending event to transition on.
+        externalContext.setEventId("proceed");
+        setCurrentState("AuthRequest");       
+        resumeFlow(externalContext);
         
-        final JSONParser parser = new JSONParser(JSONParser.MODE_JSON_SIMPLE);
-        final OIDCMetadataContext metadataContext = new OIDCMetadataContext();
-        metadataContext.setClientInformation(
-                OIDCClientInformation.parse((JSONObject)((JSONArray)parser.parse(CLIENT_METADATA)).get(0)));
-        nestPrc.getOutboundMessageContext().addSubcontext(metadataContext);
+        mockOPServer.shutdown();
         
+        //assert success conditions
+        assertFlowExecutionEnded();
+        assertNotNull(prc.getSubcontext(AuthenticationContext.class));
+        assertNotNull(prc.getSubcontext(SubjectCanonicalizationContext.class));
+        assertEquals(prc.getSubcontext(SubjectCanonicalizationContext.class).getPrincipalName(),"jdoe");
+      
+       
+    }
+    
+    @Test 
+    public void testAuthnFlowFromAuthorizationCallback_UsingJWTUserInfoResponse() throws Exception {
+        
+        setFlowPath(FLOW);
+        setFlowModelResources(flowResources);
+        setSubflows(subflows);        
+        
+        final Map<String,String> mockProperties = Map.of(
+                "idp.service.clientinfo.failFast","false",
+                "idp.oidc.rp.clientID","clientId",
+                "idp.oidc.rp.clientSecret","secret",
+                "idp.oidc.rp.providerConfigurationDocument","provider_location",
+                "idp.oidc.rp.redirectURI","https://localhost:8443/idp/profile/Authn/OIDC/RP/callback",
+                "idp.oidc.rp.scope","email",
+                "idp.entityID", "http://idp.example.com/",
+                "idp.authn.oidc.rp.proxyIssuer","https://op.example.com");
+        
+        setMockProperties(mockProperties);
+        
+        final MockWebServer mockOPServer = createSimpleServer();
+        // First is token exchange
+        mockOPServer.enqueue(new MockResponse().setResponseCode(200)
+                .setHeader("content-type", "application/json")
+                .setBody(createAccessTokenResponseJSON()));
+        // Second is userInfo
+        mockOPServer.enqueue(new MockResponse().setResponseCode(200)
+                .setHeader("content-type", "application/jwt")
+                .setBody(createSignedUserInfoJWTResponseJSON("https://op.example.com","demo_rp")));
+        mockOPServer.start(9918);
+        
+
+        final FlowExecutionImpl flowExecution = (FlowExecutionImpl)getFlowExecutionFactory()
+                .createFlowExecution(getFlowDefinition());
+        final ProfileRequestContext prc =  buildProfileRequestContext("authn/OIDCRelyingParty", false,false);
+        prc.getSubcontext(AuthenticationContext.class).setAuthenticatingAuthority("http://op.example.com");
+        
+        // create a nested PRC under the authentication context
+        final ProfileRequestContext nestPrc = (ProfileRequestContext) 
+                prc.getSubcontext(AuthenticationContext.class).addSubcontext(new ProfileRequestContext(), true);  
+        
+        // Add under nest PRC
+        final RelyingPartyContext partyContext = new RelyingPartyContext();
+        final OIDCAuthorizationConfiguration partyConfig = new OIDCAuthorizationConfiguration();        
+        partyContext.setProfileConfig(partyConfig);
+        nestPrc.addSubcontext(partyContext);
+       
+        // Setup outbound context
+        final MessageContext outMsgCtx = new MessageContext();        
+        outMsgCtx.setMessage(createAuthenticationRequest());        
+        outMsgCtx.addSubcontext(createPeerContext());
+        outMsgCtx.addSubcontext(createResponseTypeAndModeContext());
+        outMsgCtx.addSubcontext(createClientMedataContext());
+        nestPrc.setOutboundMessageContext(outMsgCtx);    
+        
+        // Setup inbound context.
         final MessageContext inMsgCtx = new MessageContext();
-        inMsgCtx.setMessage(AuthenticationResponseParser.parse(
-                new URI("/idp/profile/Authn/OIDC/RP/callback"
-                        + "?state=8df98fd63a53fa5b5433d6f8754bca5d.65317332&code=z8C2DCp6sn0D9aGbEqlrFesdPVRXPtDX")));
+        inMsgCtx.setMessage(createAuthenticationResponse());
         nestPrc.setInboundMessageContext(inMsgCtx);
         
+        // Add prc to flow.
         prc.getSubcontext(AuthenticationContext.class)
         .addSubcontext(new ExternalAuthenticationContext(new ExternalAuthenticationImpl(false)));
                         flowExecution.getConversationScope().put("opensamlProfileRequestContext",prc);
-        
+                        
         
         updateFlowExecution(flowExecution);
         
@@ -413,11 +621,15 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         setCurrentState("AuthRequest");       
         resumeFlow(externalContext);
         
+        mockOPServer.shutdown();
+        
         //assert success conditions
         assertFlowExecutionEnded();
         assertNotNull(prc.getSubcontext(AuthenticationContext.class));
+        assertNotNull(prc.getSubcontext(SubjectCanonicalizationContext.class));
+        assertEquals(prc.getSubcontext(SubjectCanonicalizationContext.class).getPrincipalName(),"jdoe");
       
-        
+       
     }
     
     
@@ -428,7 +640,8 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
      * @throws Exception on error.
      */
     @Test 
-    public void testAuthnFlowFromAuthorizationCallback_ErrorAuthenticationResponse() throws Exception {
+    public void testAuthnFlowFromAuthorizationCallback_ErrorAuthenticationResponse() 
+            throws Exception {
         
         setFlowPath(FLOW);
         setFlowModelResources(flowResources);
@@ -481,9 +694,8 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         
         //assert success conditions
         assertFlowExecutionEnded();
-        //TODO FIX SUCCESS CONDITIONS
         assertNotNull(prc.getSubcontext(AuthenticationContext.class));
-      
+        assertNull(prc.getSubcontext(SubjectCanonicalizationContext.class));      
         
     }
 }
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
index fcfd3bb..299d5b6 100644
--- 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
@@ -13,7 +13,7 @@ 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.TokenResponseContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
 
 /** Tests for TokenResponseIDTokenLookupStrategy.*/
 public class TokenResponseIDTokenLookupStrategyTest extends AbstractOIDCTest {
@@ -30,7 +30,7 @@ public class TokenResponseIDTokenLookupStrategyTest extends AbstractOIDCTest {
         
         final Map<String, Object> rawResponse = mapper.readValue(
                 new ByteArrayInputStream(ACCESS_TOKEN_RESPONSE.getBytes()), new TypeReference<Map<String, Object>>() {});
-        final TokenResponseContext trc = new TokenResponseContext();
+        final AccessTokenResponseContext trc = new AccessTokenResponseContext();
         trc.setRawTokenResponse(rawResponse);
         prc.getInboundMessageContext().addSubcontext(trc);
         
@@ -57,7 +57,7 @@ public class TokenResponseIDTokenLookupStrategyTest extends AbstractOIDCTest {
     
     @Test
     public void testLookup_NoResponseContext() {
-        prc.getInboundMessageContext().removeSubcontext(TokenResponseContext.class);
+        prc.getInboundMessageContext().removeSubcontext(AccessTokenResponseContext.class);
         final String rawToken = strategy.apply(prc);
         assertNull(rawToken);
        
diff --git a/idp-oidc-rp-impl/src/test/resources/attribute/registry/attribute-registry.xml b/idp-oidc-rp-impl/src/test/resources/attribute/registry/attribute-registry.xml
new file mode 100644
index 0000000..e609e84
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/resources/attribute/registry/attribute-registry.xml
@@ -0,0 +1,442 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xmlns:util="http://www.springframework.org/schema/util"
+       xmlns:p="http://www.springframework.org/schema/p"
+       xmlns:c="http://www.springframework.org/schema/c"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+                           
+       default-init-method="initialize"
+       default-destroy-method="destroy">
+
+    
+        
+    <bean id="OIDCByteTranscoder"
+        class="net.shibboleth.oidc.attribute.transcoding.impl.OIDCByteAttributeTranscoder" />
+
+    <bean id="OIDCStringTranscoder"
+        class="net.shibboleth.oidc.attribute.transcoding.impl.OIDCStringAttributeTranscoder" />
+
+    <bean id="OIDCScopedStringTranscoder"
+        class="net.shibboleth.oidc.attribute.transcoding.impl.OIDCScopedStringAttributeTranscoder" />
+        
+        
+    <!-- https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims -->
+
+    <bean parent="shibboleth.TranscodingRuleLoader">
+    <constructor-arg>
+    <list>
+
+        <!-- Typical inetOrgPerson attributes that map to standard claims. -->
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">displayName</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">name</prop>
+                    <prop key="displayName.en">Display name</prop>
+                    <prop key="displayName.de">Anzeigename</prop>
+                    <prop key="displayName.fr">Nom</prop>
+                    <prop key="displayName.it">Nome</prop>
+                    <prop key="displayName.ja">表示名</prop>
+                    <prop key="description.en">The name that should appear in white-pages-like applications for this person.</prop>
+                    <prop key="description.de">Anzeigename</prop>
+                    <prop key="description.fr">Nom complet d'affichage</prop>
+                    <prop key="description.it">Nome</prop>
+                    <prop key="description.ja">アプリケーションでの表示に用いられる英字氏名</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">givenName</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">given_name</prop>
+                    <prop key="displayName.en">Given name</prop>
+                    <prop key="displayName.de">Vorname</prop>
+                    <prop key="displayName.fr">Prénom</prop>
+                    <prop key="displayName.it">Nome</prop>
+                    <prop key="displayName.ja">名</prop>
+                    <prop key="description.en">Given name of a person</prop>
+                    <prop key="description.de">Vorname</prop>
+                    <prop key="description.fr">Prénom de l'utilisateur</prop>
+                    <prop key="description.it">Nome</prop>
+                    <prop key="description.ja">氏名(名)の英語表記</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">homePhone</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">phone_number</prop>
+                    <prop key="displayName.en">Private phone number</prop>
+                    <prop key="displayName.de">Telefon Privat</prop>
+                    <prop key="displayName.fr">Teléphone personnel</prop>
+                    <prop key="displayName.it">Numero di telefono privato</prop>
+                    <prop key="displayName.ja">自宅電話番号</prop>
+                    <prop key="description.en">Private phone number</prop>
+                    <prop key="description.de">Private Telefonnummer</prop>
+                    <prop key="description.fr">Numéro de téléphone de domicile de la personne</prop>
+                    <prop key="description.it">Numero di telefono privato</prop>
+                    <prop key="description.ja">自宅の電話番号</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">mail</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">email</prop>
+                    <prop key="displayName.en">E-mail</prop>
+                    <prop key="displayName.de">E-Mail</prop>
+                    <prop key="displayName.fr">Email</prop>
+                    <prop key="displayName.it">E-mail</prop>
+                    <prop key="displayName.ja">メールアドレス</prop>
+                    <prop key="description.en">E-Mail: Preferred address for e-mail to be sent to this person</prop>
+                    <prop key="description.de">E-Mail-Adresse</prop>
+                    <prop key="description.de-ch">E-Mail Adresse</prop>
+                    <prop key="description.fr">Adresse de courrier électronique</prop>
+                    <prop key="description.it">E-Mail: l'indirizzo e-mail preferito dall'utente</prop>
+                    <prop key="description.ja">メールアドレス</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">preferredLanguage</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">locale</prop>
+                    <prop key="displayName.en">Preferred Language</prop>
+                    <prop key="displayName.de">Bevorzugte Sprache</prop>
+                    <prop key="displayName.fr">Langue préférée</prop>
+                    <prop key="displayName.it">Lingua preferita</prop>
+                    <prop key="displayName.ja">希望言語</prop>
+                    <prop key="description.en">Preferred language: Users preferred language (see RFC1766)</prop>
+                    <prop key="description.de">Bevorzugte Sprache (siehe RFC1766)</prop>
+                    <prop key="description.fr">Exemple: fr, de, it, en, ... (voir RFC1766)</prop>
+                    <prop key="description.it">Lingua preferita: la lingua preferita dall'utente (cfr. RFC1766)</prop>
+                    <prop key="description.ja">利用者が希望する言語(RFC1766 を参照)</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">sn</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">family_name</prop>
+                    <prop key="displayName.en">Surname</prop>
+                    <prop key="displayName.de">Nachname</prop>
+                    <prop key="displayName.fr">Nom de famille</prop>
+                    <prop key="displayName.it">Cognome</prop>
+                    <prop key="displayName.ja">姓</prop>
+                    <prop key="description.en">Surname or family name</prop>
+                    <prop key="description.de">Familienname</prop>
+                    <prop key="description.fr">Nom de famille de l'utilisateur.</prop>
+                    <prop key="description.it">Cognome dell'utilizzatore</prop>
+                    <prop key="description.ja">氏名(姓)の英語表記</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">telephoneNumber</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">phone_number</prop>
+                    <prop key="displayName.en">Business phone number</prop>
+                    <prop key="displayName.de">Telefon Geschäft</prop>
+                    <prop key="displayName.fr">Teléphone professionnel</prop>
+                    <prop key="displayName.it">Numero di telefono dell'ufficio</prop>
+                    <prop key="displayName.ja">所属機関内電話番号</prop>
+                    <prop key="description.en">Business phone number: Office or campus phone number</prop>
+                    <prop key="description.de">Telefonnummer am Arbeitsplatz</prop>
+                    <prop key="description.fr">Teléphone de l'institut, de l'université</prop>
+                    <prop key="description.it">Numero di telefono dell'ufficio</prop>
+                    <prop key="description.ja">所属機関での利用者の電話番号</prop>
+                </props>
+            </property>
+        </bean>
+        
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">uid</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">preferred_username</prop>
+                    <prop key="displayName.en">User ID</prop>
+                    <prop key="displayName.de">Benutzer-ID</prop>
+                    <prop key="displayName.fr">ID utilisateur</prop>
+                    <prop key="displayName.it">ID dell'utente</prop>
+                    <prop key="displayName.ja">ユーザID</prop>
+                    <prop key="description.en">A unique identifier for a person, mainly used for user identification within the user's home organization.</prop>
+                    <prop key="description.de">Eine eindeutige Nummer für eine Person, welche hauptsächlich zur Identifikation innerhalb der Organisation benutzt wird.</prop>
+                    <prop key="description.fr">Identifiant de connexion d'une personnes sur les systèmes informatiques.</prop>
+                    <prop key="description.it">Identificativo unico della persona, usato per l'identificazione dell'utente all'interno della organizzazione di appartenenza.</prop>
+                    <prop key="description.ja">所属機関内で一意の利用者識別子</prop>
+                </props>
+            </property>
+        </bean>
+
+        <!-- eduPerson attributes that map to standard claims. -->
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">eduPersonNickname</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">nickname</prop>
+                    <prop key="displayName.en">Nickname</prop>
+                    <prop key="displayName.de">Kurzname</prop>
+                    <prop key="displayName.de-ch">Übername</prop>
+                    <prop key="displayName.fr">Surnom</prop>
+                    <prop key="displayName.it">Diminutivo</prop>
+                    <prop key="displayName.ja">ニックネーム</prop>
+                    <prop key="description.en">Person's nickname, or the informal name by which they are accustomed to be hailed.</prop>
+                    <prop key="description.de">Kurzname einer Person, oder üblicher Rufname zur Begrüßung.</prop>
+                    <prop key="description.de-ch">Übername einer Person, oder üblicher Rufname zur Begrüssung.</prop>
+                    <prop key="description.fr">Nom personnalisable pour un usage informel.</prop>
+                    <prop key="description.it">Diminutivo della persona, o  soprannome.</prop>
+                    <prop key="description.ja">利用者のニックネームもしくは通称</prop>
+                </props>
+            </property>
+        </bean>
+
+        <!-- A subset of the major eduPerson attributes that have no standard claim mapping. -->
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">eduPersonAssurance</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">eduPersonAssurance</prop>
+                    <prop key="displayName.en">Assurance level</prop>
+                    <prop key="displayName.de">Vertrauensgrad</prop>
+                    <prop key="displayName.fr">Niveau de confiance</prop>
+                    <prop key="displayName.it">Livello di sicurezza</prop>
+                    <prop key="displayName.ja">保証レベル</prop>
+                    <prop key="description.en">Set of URIs that assert compliance with specific standards for identity assurance.</prop>
+                    <prop key="description.de">URIs die eine gewisse Zusicherung für spezifische Standards des Vertrauens beinhalten</prop>
+                    <prop key="description.fr">Un ensemble d'URI qui attestent la conformité selon un standard pour les niveaux d'assurance d'identités</prop>
+                    <prop key="description.it">Un insieme di URI che asseriscono l'osservanza dei livelli di sicurezza richiesti</prop>
+                    <prop key="description.ja">IDの保証レベルに関して特定の基準に準拠していることを示すURI</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">eduPersonEntitlement</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">eduPersonEntitlement</prop>
+                    <prop key="displayName.en">Entitlement</prop>
+                    <prop key="displayName.de">Berechtigung</prop>
+                    <prop key="displayName.fr">Entitlement</prop>
+                    <prop key="displayName.it">Prerogativa</prop>
+                    <prop key="displayName.ja">資格情報</prop>
+                    <prop key="description.en">Member of: URI (either URL or URN) that indicates a set of rights to specific resources based on an agreement across the releavant community</prop>
+                    <prop key="description.de">Zeichenkette, die Rechte für spezifische Ressourcen beschreibt</prop>
+                    <prop key="description.fr">Membre de: URI (soit une URL ou une URN) décrivant un droit spécific d'accès.</prop>
+                    <prop key="description.it">Membro delle seguenti URI (sia URL o URN) che rappresentano diritti specifici d'accesso validi in tutta la communità</prop>
+                    <prop key="description.ja">特定のアプリケーションもしくはコミュニティ内の複数リソースへのアクセス権限を持つことを示すURI(URLもしくはURN)</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">eduPersonPrincipalName</prop>
+                    <prop key="transcoder">OIDCScopedStringTranscoder</prop>
+                    <prop key="oidc.name">eduPersonPrincipalName</prop>
+                    <prop key="displayName.en">Principal name</prop>
+                    <prop key="displayName.de">Persönliche ID</prop>
+                    <prop key="displayName.fr">Principal Name</prop>
+                    <prop key="displayName.it">Principal Name</prop>
+                    <prop key="displayName.ja">プリンシパルID</prop>
+                    <prop key="description.en">A unique identifier for a person, mainly for inter-institutional user identification.</prop>
+                    <prop key="description.de">Eindeutige Benutzeridentifikation</prop>
+                    <prop key="description.de-ch">Eindeutige Benützeridentifikation</prop>
+                    <prop key="description.fr">L'identifiant unique de l'utilisateur</prop>
+                    <prop key="description.it">Un ID personale che identifica chiaramente l'utente in seno alla sua organizzazione</prop>
+                    <prop key="description.ja">フェデレーション内で一意かつ永続的な利用者識別子</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">eduPersonScopedAffiliation</prop>
+                    <prop key="transcoder">OIDCScopedStringTranscoder</prop>
+                    <prop key="oidc.name">eduPersonScopedAffiliation</prop>
+                    <prop key="displayName.en">Scoped affiliation</prop>
+                    <prop key="displayName.de">Zugehörigkeit</prop>
+                    <prop key="displayName.fr">Affiliation</prop>
+                    <prop key="displayName.it">Tipo di membro</prop>
+                    <prop key="displayName.ja">スコープ付き職位</prop>
+                    <prop key="description.en">Specifies the person's affiliation within a particular security domain</prop>
+                    <prop key="description.de">Art der Zugehörigkeit zur Heimatorganisation</prop>
+                    <prop key="description.de-ch">Art der Zugehörigkeit zur Heimorganisation</prop>
+                    <prop key="description.fr">Type d'affiliation dans l'organisation</prop>
+                    <prop key="description.it">Tipo di membro: Tipo di lavoro svolto per l'organizzazione</prop>
+                    <prop key="description.ja">セキュリティドメインのスコープが付いた所属機関における職位</prop>
+                </props>
+            </property>
+        </bean>
+
+        <!-- The remainder are standard OIDC claims, which we map based on the actual claim name. -->
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">address</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.asObject">true</prop>
+                    <prop key="oidc.name">address</prop>
+                    <prop key="displayName.en">Postal address</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">birthdate</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">birthdate</prop>
+                    <prop key="displayName.en">Date of birth</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">email_verified</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">email_verified</prop>
+                    <prop key="oidc.asBoolean">true</prop>
+                    <prop key="displayName.en">E-mail verification status</prop>
+                    <prop key="description.en">Indicates whether e-mail address has been verified by the issuer</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">gender</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">gender</prop>
+                    <prop key="displayName.en">Gender</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">middle_name</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">middle_name</prop>
+                    <prop key="displayName.en">Middle name</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">phone_number_verified</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">phone_number_verified</prop>
+                    <prop key="oidc.asBoolean">true</prop>
+                    <prop key="displayName.en">Phone number verification status</prop>
+                    <prop key="description.en">Indicates whether phone number has been verified by the issuer</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">picture</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">picture</prop>
+                    <prop key="displayName.en">Picture</prop>
+                    <prop key="description.en">URL of personal photo</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">profile</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">profile</prop>
+                    <prop key="displayName.en">Profile page</prop>
+                    <prop key="description.en">URL of personal profile page</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">website</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">website</prop>
+                    <prop key="displayName.en">Web site</prop>
+                    <prop key="description.en">URL to personal web site</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">updated_at</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">updated_at</prop>
+                    <prop key="oidc.asInteger">true</prop>
+                    <prop key="displayName.en">Last update of information</prop>
+                </props>
+            </property>
+        </bean>
+
+        <bean parent="shibboleth.TranscodingProperties">
+            <property name="properties">
+                <props merge="true">
+                    <prop key="id">zoneinfo</prop>
+                    <prop key="transcoder">OIDCStringTranscoder</prop>
+                    <prop key="oidc.name">zoneinfo</prop>
+                    <prop key="displayName.en">Time zone</prop>
+                </props>
+            </property>
+        </bean>
+
+    </list>
+    </constructor-arg>
+    </bean>
+
+
+   
+</beans>
diff --git a/idp-oidc-rp-impl/src/test/resources/attribute/registry/postconfig.xml b/idp-oidc-rp-impl/src/test/resources/attribute/registry/postconfig.xml
new file mode 100644
index 0000000..29752a5
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/resources/attribute/registry/postconfig.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+    xmlns:context="http://www.springframework.org/schema/context"
+    xmlns:util="http://www.springframework.org/schema/util"
+    xmlns:p="http://www.springframework.org/schema/p"
+    xmlns:c="http://www.springframework.org/schema/c"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+
+    default-init-method="initialize" default-destroy-method="destroy">
+
+    <!-- Test config that pulls together various attribute encoders and registries to allow the flow
+    tests to run -->
+    
+    <bean id="shibboleth.AttributeRegistryService" class="net.shibboleth.ext.spring.service.ReloadableSpringService" 
+            p:failFast="false"
+            p:reloadCheckDelay="PT0S"
+            p:beanPostProcessors-ref="shibboleth.IdentifiableBeanPostProcessor"
+            p:beanFactoryPostProcessors-ref="shibboleth.PropertySourcesPlaceholderConfigurer">
+        <constructor-arg name="claz" value="net.shibboleth.idp.attribute.transcoding.AttributeTranscoderRegistry" />
+        <constructor-arg name="strategy">
+           <bean class="net.shibboleth.idp.attribute.transcoding.impl.AttributeRegistryServiceStrategy" 
+           p:namingRegistry-ref="TestNamingRegistryList"/> <!-- Autowired not working in the test flow, so add manually -->
+        </constructor-arg>
+        <property name="serviceConfigurations">
+            <util:list>
+                <value>attribute/registry/attribute-registry.xml</value> <!-- load itself -->
+            </util:list>
+        </property>
+    </bean>
+    
+    <!-- Add a naming registry manually, as Autowired is not enabled in this context. -->
+    <util:list id="TestNamingRegistryList">
+        <ref bean="OidcNamingFunction"/>    
+    </util:list>
+    
+    <bean id="shibboleth.RegistryNamingFunction" abstract="true"
+        class="net.shibboleth.idp.attribute.transcoding.BasicNamingFunction" />
+    
+    <bean id="OidcNamingFunction" parent="shibboleth.RegistryNamingFunction" c:claz="net.minidev.json.JSONObject">
+        <constructor-arg name="function">
+            <bean class="net.shibboleth.oidc.attribute.transcoding.AbstractOIDCAttributeTranscoder.NamingFunction" />
+        </constructor-arg>    
+    </bean>
+     <!-- done. -->
+    
+    <bean id="shibboleth.TranscodingRuleLoader"
+        class="net.shibboleth.idp.attribute.transcoding.impl.TranscodingRuleLoader" abstract="true" />
+        
+    <bean id="shibboleth.TranscodingRule"
+        class="net.shibboleth.idp.attribute.transcoding.TranscodingRule" abstract="true" />
+        
+    <!-- TODO unsure why we need this if no OIDC specific information? -->
+    <bean id="shibboleth.TranscodingProperties" lazy-init="true"
+            class="org.springframework.beans.factory.config.PropertiesFactoryBean">
+        <property name="properties">
+            <props>
+                <prop key="saml1.encodeType">%{idp.service.attribute.registry.encodeType:true}</prop>
+                <prop key="saml2.encodeType">%{idp.service.attribute.registry.encodeType:true}</prop>
+            </props>
+        </property>
+    </bean>
+
+    
+</beans>
diff --git a/idp-oidc-rp-impl/src/test/resources/logback-test.xml b/idp-oidc-rp-impl/src/test/resources/logback-test.xml
index 28948ad..5a9618d 100644
--- a/idp-oidc-rp-impl/src/test/resources/logback-test.xml
+++ b/idp-oidc-rp-impl/src/test/resources/logback-test.xml
@@ -2,6 +2,7 @@
 
 <configuration>
     <logger name="net.shibboleth" level="DEBUG"/>
+    <logger name="net.shibboleth.idp.plugin.authn" level="TRACE"/>
     <logger name="org.springframework" level="INFO"/>
 
     <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
diff --git a/pom.xml b/pom.xml
index c9959f6..2f965b9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -139,6 +139,18 @@
                 <type>pom</type>
                 <scope>import</scope>
             </dependency>
+             <dependency>
+                <groupId>com.squareup.okhttp3</groupId>
+                <artifactId>mockwebserver</artifactId>
+                <version>4.9.3</version>
+                <scope>test</scope>
+            </dependency>
+            <dependency>
+                <groupId>com.squareup.okhttp3</groupId>
+                <artifactId>okhttp-tls</artifactId>
+                <version>4.9.3</version>
+                <scope>test</scope>
+            </dependency>
         </dependencies>
     </dependencyManagement>
     <build>

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


More information about the commits mailing list