[java-idp-plugin-oidc-rp] branch main updated: Fix contexts, add auth controller callback, add auth_code token exchange

Phil Smart philip.smart at jisc.ac.uk
Mon Jan 31 17:54:12 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=106242b1ea6f2875f0177c96d438c860850bc7bb

The following commit(s) were added to refs/heads/main by this push:
     new 106242b  Fix contexts, add auth controller callback, add auth_code token exchange
106242b is described below

commit 106242b1ea6f2875f0177c96d438c860850bc7bb
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Mon Jan 31 17:54:05 2022 +0000

    Fix contexts, add auth controller callback, add auth_code token exchange
    
     - Cleanup the inbound and outbound context trees
     - Add response_mode support and lookup
     - Add/complete callback to authz controller
     - Start flow from authz callback
     - Validate authentication response
     - Add token request support for auth_code grant.
     - Add supporting encoders/decoders for token request/response
     - Improve flow
     - Cleanup old classes
     - Fix some checkstyle/javadoc
---
 .../plugin/authn/oidc/rp/OIDCProxyException.java   |  54 ++
 .../authn/oidc/rp/context/OIDCAuthnContext.java    |  37 +-
 ...dataContext.java => OIDCPeerEntityContext.java} |  42 +-
 .../rp/context/ResponseTypeAndModeContext.java     |  57 ++
 ...adataContext.java => TokenResponseContext.java} |  45 +-
 .../context/OIDCClientMetadataContext.java}        |   7 +-
 .../context/OIDCProviderMetadataContext.java       |   1 +
 idp-oidc-rp-impl/pom.xml                           |  11 +
 .../idp/plugin/authn/oidc/rp/OIDCRPModule.java     |  17 +
 .../idp/plugin/authn/oidc/rp/OIDCRPPlugin.java     |  17 +
 .../decoding/impl/DefaultTokenResponseDecoder.java | 101 ++++
 .../authn/oidc/rp/decoding/impl/package-info.java  |  21 +
 .../impl/AbstractRequestEncoderFunction.java       | 199 +++++++
 .../encoding/impl/DefaultTokenRequestEncoder.java  | 132 +++++
 .../authn/oidc/rp/encoding/impl/package-info.java  |  21 +
 .../impl/AbstractHttpOIDCAuthenticationAction.java | 111 ++++
 .../AbstractOIDCAuthenticationResponseAction.java  | 204 +++++++
 .../plugin/authn/oidc/rp/impl/AddAuthzRequest.java | 103 +++-
 .../oidc/rp/impl/AuthorizationController.java      | 115 ++--
 .../rp/impl/DefaultResponseModeLookupFunction.java |  62 +++
 .../rp/impl/DefaultResponseTypeLookupFunction.java |  73 +++
 .../authn/oidc/rp/impl/ExchangeCodeForToken.java   | 157 ++++++
 .../oidc/rp/impl/ExtractIDTokenFromResponse.java   | 108 ++++
 .../authn/oidc/rp/impl/GetOIDCTokenResponse.java   | 137 -----
 ...OutboundAuthorizationRequestMessageContext.java |  68 ++-
 .../rp/impl/InitializeRelyingPartyContext.java     |  57 +-
 .../rp/impl/OIDCAuthenticationTimeRequested.java   |  17 +
 .../OIDCContextAudienceClaimLookupStrategy.java    |   3 +-
 .../authn/oidc/rp/impl/OIDCProxySupport.java       |  68 ++-
 .../impl/PopulateResponseTypeAndModeContext.java   | 119 +++++
 .../rp/impl/PrepareOIDCInboundMessageContext.java  | 105 ++++
 .../authn/oidc/rp/impl/SetOIDCInformation.java     | 584 ---------------------
 .../impl/TokenResponseIDTokenLookupStrategy.java   |  54 ++
 .../impl/ValidateAuthenticationResponseResult.java |  88 ++++
 .../ValidateExternalAuthenticationContext.java     |  84 +++
 .../authn/oidc/rp/impl/ValidateIDTokenACR.java     | 103 ----
 .../rp/impl/ValidateIDTokenAuthorizedParty.java    |  89 ----
 .../impl/ValidateOIDCAuthenticationResponse.java   | 123 -----
 .../authn/oidc/rp/impl/ValidateResponseState.java  |  81 +++
 .../DefaultClientIDForIssuerLookupFunction.java    |  59 +++
 .../impl/DefaultIssuerIDLookupFunction.java        |  11 +-
 ...r.java => OIDCClientMetadataLookupHandler.java} |  14 +-
 .../impl/OIDCProviderMetadataLookupHandler.java    |  50 +-
 .../oidc-relying-party-authn-beans.xml             | 128 ++++-
 .../oidc-relying-party-authn-flow.xml              |  79 ++-
 .../impl/DefaultTokenResponseDecoderTest.java      | 109 ++++
 .../impl/DefaultTokenRequestEncoderTest.java       |  61 +++
 .../authn/oidc/rp/impl/AbstractOIDCTest.java       | 177 +++++++
 .../oidc/rp/impl/AuthorizationControllerTest.java  |  42 +-
 .../oidc/rp/impl/ExchangeCodeForTokenTest.java     | 176 +++++++
 .../plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java  | 203 ++++++-
 51 files changed, 3345 insertions(+), 1239 deletions(-)

diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCProxyException.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCProxyException.java
new file mode 100644
index 0000000..c79918a
--- /dev/null
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCProxyException.java
@@ -0,0 +1,54 @@
+package net.shibboleth.idp.plugin.authn.oidc.rp;
+
+import javax.annotation.concurrent.ThreadSafe;
+
+/** 
+ * An exception to signal a general Duo error.
+ */
+ at ThreadSafe
+public class OIDCProxyException extends Exception{
+
+    /** Generated serial UID. */
+    private static final long serialVersionUID = 1360790617215053967L;
+
+    /** Constructor. */
+    public OIDCProxyException() {
+        super();
+        
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param message exception message
+     * @param cause exception to be wrapped by this one
+     */
+    public OIDCProxyException(final String message, final Throwable cause) {
+        super(message, cause);
+        
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param message exception message
+     */
+    public OIDCProxyException(final String message) {
+        super(message);
+        
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param cause exception to be wrapped by this one
+     */
+    public OIDCProxyException(final Throwable cause) {
+        super(cause);
+        
+    }
+
+
+
+
+}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCAuthnContext.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCAuthnContext.java
index 76cb049..bfb945f 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCAuthnContext.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCAuthnContext.java
@@ -1,9 +1,29 @@
+/*
+ * 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.util.function.Function;
+
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import org.opensaml.messaging.context.BaseContext;
+import org.opensaml.messaging.decoder.MessageDecoder;
 import org.opensaml.messaging.handler.MessageHandler;
 import org.opensaml.profile.action.ProfileAction;
 
@@ -20,13 +40,19 @@ public class OIDCAuthnContext extends BaseContext {
     /** Profile action to execute to produce outbound message response. */
     @Nonnull private final ProfileAction encodeMessageAction;
     
+    /** The function to use to obtain a decoder. */
+    @Nonnull private Function<String,MessageDecoder> decoderFactory;
+    
     /**
      * Constructor.
      *
      * @param action message-encoding profile action
+     * @param factory the message descoder factory
      */
-    public OIDCAuthnContext(@Nonnull final ProfileAction action) {
+    public OIDCAuthnContext(@Nonnull final ProfileAction action,
+            @Nonnull final Function<String,MessageDecoder> factory) {
         encodeMessageAction = Constraint.isNotNull(action, "Profile action cannot be null");
+        decoderFactory = Constraint.isNotNull(factory, "MessageDecoder factory cannot be null");
     }
     
     /**
@@ -58,5 +84,14 @@ public class OIDCAuthnContext extends BaseContext {
         outboundMessageHandler = handler;        
         return this;
     }
+    
+    /**
+     * Get the factory function to obtain message decoders.
+     * 
+     * @return factory function
+     */
+    @Nonnull public Function<String,MessageDecoder> getMessageDecoderFactory() {
+        return decoderFactory;
+    }
 
 }
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCMetadataContext.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCPeerEntityContext.java
similarity index 50%
copy from idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCMetadataContext.java
copy to idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCPeerEntityContext.java
index 6bafcbb..86dcc70 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCMetadataContext.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCPeerEntityContext.java
@@ -21,39 +21,37 @@ import javax.annotation.Nullable;
 
 import org.opensaml.messaging.context.BaseContext;
 
-import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
 
 /**
- * Subcontext carrying information on metadata of the relying party. This
- * context appears as a subcontext of the
- * {@link org.opensaml.messaging.context.MessageContext} that carries the actual
- * OIDC request message, in such cases the metadata carried herein applies to
- * the issuer of that message.
+ * Lightweight subcontext that carries information about a OIDC peer entity.
  * 
- * This context is just a placeholder for the final solution. At first phase we
- * use only redirect uris.
+ * <p>
+ * This context will often contain subcontexts, whose data is construed to be scoped to that peer entity.
+ * </p>
  */
-public class OIDCMetadataContext extends BaseContext {
-
-    /** The client information. */
-    @Nullable private OIDCClientInformation clientInformation;
+public final class OIDCPeerEntityContext extends BaseContext {
+    
+    /** The identifier of the OIDC peer entity e.g. issuerId or ClientId. */
+    @Nullable @NotEmpty private String identifer;
     
     /**
-     * Set the client information.
+     * Gets the identifier of the OIDC entity.
      * 
-     * @return The client information.
+     * @return identifier of the OIDC entity, may be null
      */
-    @Nullable
-    public OIDCClientInformation getClientInformation() {
-        return clientInformation;
+    @Nullable @NotEmpty public String getIdentifier() {
+        return identifer;
     }
 
     /**
-     * Set the client information.
+     * Sets the identifier of the OIDC entity e.g. issuerId or ClientId.
      * 
-     * @param information The client information.
+     * @param id the new identifier
      */
-    public void setClientInformation(@Nullable final OIDCClientInformation information) {
-        clientInformation = information;
+    public void setIdentifier(@Nullable final String id) {
+        identifer = StringSupport.trimOrNull(id);
     }
-}
\ No newline at end of file
+
+}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/ResponseTypeAndModeContext.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/ResponseTypeAndModeContext.java
new file mode 100644
index 0000000..b7113a8
--- /dev/null
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/ResponseTypeAndModeContext.java
@@ -0,0 +1,57 @@
+package net.shibboleth.idp.plugin.authn.oidc.rp.context;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+
+import com.nimbusds.oauth2.sdk.ResponseMode;
+import com.nimbusds.oauth2.sdk.ResponseType;
+
+/**
+ * Context hold the response_type and response_mode to be used with the ongoing authentication request.
+ */
+public class ResponseTypeAndModeContext extends BaseContext {
+    
+    /** The chosen response type.*/
+    @Nullable private ResponseType responseType;
+    
+    /** The chosen response mode.*/
+    @Nullable private ResponseMode responseMode;
+    
+    /**
+     * Set the response type to be used with the ongoing authentication request.
+     * 
+     * @param type the response type.
+     */
+    public void setResponseType(@Nullable final ResponseType type) {
+        responseType = type;
+    }
+    
+    /**
+     * Get the response_type to use with this authentication request.
+     * 
+     * @return the response_type.
+     */
+    @Nullable public ResponseType getResponseType() {
+        return responseType;
+    }
+    
+    /**
+     * Set the response mode to be used with the ongoing authentication request.
+     * 
+     * @param mode the response mode.
+     */
+    public void setResponseMode(@Nullable final ResponseMode mode) {
+        responseMode = mode;
+    }
+    
+    /**
+     * Get the response_mode to use with this authentication request.
+     * 
+     * @return the response_mode.
+     */
+    @Nullable public ResponseMode getResponseMode() {
+        return responseMode;
+    }
+
+}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCMetadataContext.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/TokenResponseContext.java
similarity index 50%
copy from idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCMetadataContext.java
copy to idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/TokenResponseContext.java
index 6bafcbb..56a78d8 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCMetadataContext.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/TokenResponseContext.java
@@ -17,43 +17,34 @@
 
 package net.shibboleth.idp.plugin.authn.oidc.rp.context;
 
+import java.util.Map;
+
 import javax.annotation.Nullable;
 
 import org.opensaml.messaging.context.BaseContext;
 
-import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
-
-/**
- * Subcontext carrying information on metadata of the relying party. This
- * context appears as a subcontext of the
- * {@link org.opensaml.messaging.context.MessageContext} that carries the actual
- * OIDC request message, in such cases the metadata carried herein applies to
- * the issuer of that message.
- * 
- * This context is just a placeholder for the final solution. At first phase we
- * use only redirect uris.
- */
-public class OIDCMetadataContext extends BaseContext {
-
-    /** The client information. */
-    @Nullable private OIDCClientInformation clientInformation;
+/** A context to hold an OIDC token request response.*/
+public class TokenResponseContext extends BaseContext {
+    
+    /** The raw token response as a map.*/
+    @Nullable private Map<String, Object> rawTokenResponse;
     
     /**
-     * Set the client information.
+     * Set the raw token response as a map.
      * 
-     * @return The client information.
+     * @param rawResponse the raw response.
      */
-    @Nullable
-    public OIDCClientInformation getClientInformation() {
-        return clientInformation;
+    public void setRawTokenResponse(@Nullable final Map<String, Object> rawResponse) {
+        rawTokenResponse = rawResponse;
     }
-
+    
     /**
-     * Set the client information.
+     * Get the raw token response map.
      * 
-     * @param information The client information.
+     * @return the raw response map.
      */
-    public void setClientInformation(@Nullable final OIDCClientInformation information) {
-        clientInformation = information;
+    @Nullable public Map<String, Object> getRawTokenResponse() {
+        return rawTokenResponse;
     }
-}
\ No newline at end of file
+
+}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCMetadataContext.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/OIDCClientMetadataContext.java
similarity index 89%
rename from idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCMetadataContext.java
rename to idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/OIDCClientMetadataContext.java
index 6bafcbb..f0b9f5b 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCMetadataContext.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/OIDCClientMetadataContext.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package net.shibboleth.idp.plugin.authn.oidc.rp.context;
+package net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context;
 
 import javax.annotation.Nullable;
 
@@ -29,11 +29,8 @@ import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
  * {@link org.opensaml.messaging.context.MessageContext} that carries the actual
  * OIDC request message, in such cases the metadata carried herein applies to
  * the issuer of that message.
- * 
- * This context is just a placeholder for the final solution. At first phase we
- * use only redirect uris.
  */
-public class OIDCMetadataContext extends BaseContext {
+public class OIDCClientMetadataContext extends BaseContext {
 
     /** The client information. */
     @Nullable private OIDCClientInformation clientInformation;
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/OIDCProviderMetadataContext.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/OIDCProviderMetadataContext.java
index 118c312..849edac 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/OIDCProviderMetadataContext.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/OIDCProviderMetadataContext.java
@@ -29,6 +29,7 @@ public class OIDCProviderMetadataContext extends BaseContext {
      *
      * @param metadata the provider configuration information.
      */
+    //TODO not sure this is needed in constructor, make inline with other contexts.
     public OIDCProviderMetadataContext(@Nonnull final OIDCProviderMetadata metadata) {
         providerInformation = Constraint.isNotNull(metadata, "OIDC Provider Metadata can not be null");
     }
diff --git a/idp-oidc-rp-impl/pom.xml b/idp-oidc-rp-impl/pom.xml
index e4ecd8e..a209dfd 100644
--- a/idp-oidc-rp-impl/pom.xml
+++ b/idp-oidc-rp-impl/pom.xml
@@ -128,6 +128,17 @@
             <artifactId>opensaml-messaging-impl</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>${idp.groupId}</groupId>
+            <artifactId>idp-profile-impl</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>net.shibboleth.idp</groupId>
+            <artifactId>idp-profile-api</artifactId>
+            <scope>test</scope>
+            <type>test-jar</type>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCRPModule.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCRPModule.java
index 90397e7..b8c75dc 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCRPModule.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCRPModule.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;
 
 import java.io.IOException;
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCRPPlugin.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCRPPlugin.java
index e24c0dc..a5dd33f 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCRPPlugin.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCRPPlugin.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;
 
 import java.io.IOException;
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/DefaultTokenResponseDecoder.java
new file mode 100644
index 0000000..4950a60
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultTokenResponseDecoder.java
@@ -0,0 +1,101 @@
+/*
+ * 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 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;
+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>> {
+    
+    /** 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.
+     * 
+     * @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
+    @Nullable public Map<String, Object> 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("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);
+        }
+        return null;
+    }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/package-info.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/package-info.java
new file mode 100644
index 0000000..7e769f9
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * This package has decoders for various OIDC/OAuth2 responses.
+ */
+package net.shibboleth.idp.plugin.authn.oidc.rp.decoding.impl;
\ No newline at end of file
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/AbstractRequestEncoderFunction.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/AbstractRequestEncoderFunction.java
new file mode 100644
index 0000000..bf4baa5
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/AbstractRequestEncoderFunction.java
@@ -0,0 +1,199 @@
+/*
+ * 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.encoding.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.http.client.methods.HttpUriRequest;
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCClientMetadataContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCProviderMetadataContext;
+import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
+import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+//TODO is the same as the AbstractOIDCAuthenticationAction.
+/** Abstract request encoder function that pulls out various contexts and request/reponse messages.*/
+public abstract class AbstractRequestEncoderFunction extends AbstractInitializableComponent 
+                                implements Function<ProfileRequestContext, HttpUriRequest> {
+    
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(AbstractRequestEncoderFunction.class);
+    
+    /** Lookup strategy to locate the OP metadata to use.*/
+    @Nonnull private Function<ProfileRequestContext, OIDCProviderMetadataContext> providerMetadataLookupStrategy;
+    
+    /** Lookup strategy to locate the metadata of the client to use to communicate with the OP.*/
+    @Nonnull private Function<ProfileRequestContext,OIDCClientMetadataContext> clientMetadataLookupStrategy;
+
+    /** OIDC authentication response from downstream OP. */
+    @Nullable private AuthenticationSuccessResponse authnResponse;
+    
+    /** OIDC authentication request built by the IdP. */
+    @Nullable private OIDCAuthenticationRequest authnRequest;
+
+    /** OIDC client Metadata context. */
+    @Nullable private OIDCClientMetadataContext clientMetadataContext;
+    
+    /** OIDC Metadata context. */
+    @Nullable private OIDCProviderMetadataContext providerMetadataContext;
+    
+    /** Constructor.*/
+    protected AbstractRequestEncoderFunction() {
+        providerMetadataLookupStrategy = new ChildContextLookup<>(OIDCProviderMetadataContext.class).compose(
+                new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
+                        new OutboundMessageContextLookup()));
+        
+        clientMetadataLookupStrategy = new ChildContextLookup<>(OIDCClientMetadataContext.class).compose(
+                new OutboundMessageContextLookup());
+    }
+    
+    /**
+     * Set the lookup strategy to locate the OpenID providers metadata.
+     * 
+     * @param strategy the strategy.
+     */
+    public void setProviderMetadataLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, OIDCProviderMetadataContext> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        providerMetadataLookupStrategy = 
+                Constraint.isNotNull(strategy,"Provider metadata lookup strategy can not be null");
+    }
+    
+    /**
+     * Set the lookup strategy to locate the OpenID client metadata.
+     * 
+     * @param strategy the strategy.
+     */
+    public void setClientMetadataLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, OIDCClientMetadataContext> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        clientMetadataLookupStrategy = Constraint.isNotNull(strategy,"Client metadata lookup strategy can not be null");
+    }
+
+    /**
+     * Returns the authentication response from the downstream OP.
+     * 
+     * @return the authentication response.
+     */
+    @Nullable public AuthenticationSuccessResponse getAuthenticationResponse() {
+        return authnResponse;
+    }
+    
+    /**
+     * Returns the authentication request build by this IdP.
+     * 
+     * @return the authentication request.
+     */
+    @Nullable public OIDCAuthenticationRequest getAuthenticationRequest() {
+        return authnRequest;
+    }
+
+    /**
+     * Returns the client metadata context.
+     * 
+     * @return The client metadata context.
+     */
+    @Nullable public OIDCClientMetadataContext getClientMetadataContext() {
+        return clientMetadataContext;
+    }
+    
+    /**
+     * Returns the OIDC provider metadata context.
+     * 
+     * @return The provider metadata context.
+     */
+    @Nullable public OIDCProviderMetadataContext getProviderMetadataContext() {
+        return providerMetadataContext;
+    }
+
+    //TODO these come from the outbound message context, so are not general to other actions
+
+    @Override
+    public HttpUriRequest apply(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+
+        final MessageContext inboundMessageCtx = profileRequestContext.getInboundMessageContext();
+        if (inboundMessageCtx == null) {
+            log.error("No inbound message context");
+            return null;
+        }
+        if (inboundMessageCtx.getMessage() == null) {
+            log.error("No inbound message");
+            return null;
+        }
+        
+        if (!(inboundMessageCtx.getMessage() instanceof AuthenticationSuccessResponse)) {
+            log.error("No inbound authentication success response");
+            return null;
+        }
+        authnResponse = (AuthenticationSuccessResponse)inboundMessageCtx.getMessage();
+        
+        final MessageContext outboundMsgContext = profileRequestContext.getOutboundMessageContext();
+        if (outboundMsgContext == null) {
+            log.error("Outbound message context was null");
+            return null;
+        } 
+        if (!(outboundMsgContext.getMessage() instanceof OIDCAuthenticationRequest)) {
+            log.error("Outbound message was not an authentication request");
+            return null;
+        }
+        authnRequest = (OIDCAuthenticationRequest) outboundMsgContext.getMessage();
+        
+        clientMetadataContext = clientMetadataLookupStrategy.apply(profileRequestContext);
+        if (clientMetadataContext == null) {
+            log.error("No client metadata found for relying party");
+            return null;
+        }
+        
+        providerMetadataContext = providerMetadataLookupStrategy.apply(profileRequestContext);
+        if (providerMetadataContext == null) {
+            log.error("No provider metadata found for peer");
+            return null;
+        }
+        
+        return doApply(profileRequestContext);
+
+    }
+
+    /**
+     * Encode a HttpUriRequest from the given context. Implementations should override this mehtod.
+     * 
+     * @param profileRequestContext the profile request context.
+     * 
+     * @return the request to execute.
+     */
+    protected abstract HttpUriRequest doApply(ProfileRequestContext profileRequestContext);
+
+    
+}
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
new file mode 100644
index 0000000..797306f
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultTokenRequestEncoder.java
@@ -0,0 +1,132 @@
+/*
+ * 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.encoding.impl;
+
+import java.net.URI;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+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.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jose.util.Base64;
+import com.nimbusds.jose.util.StandardCharset;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+
+/** A token request encoder that builds an OAuth2.0 Access Token Request for an authorization grant request. */
+//TODO either only applies to authorization_code grant, or can handle more. If specific, must gurantee that.
+public class DefaultTokenRequestEncoder extends AbstractRequestEncoderFunction {
+    
+    /** The HTTPS scheme.*/
+    @Nonnull @NotEmpty private static final String HTTPS = "https";
+    
+    /** Class logger. */
+    @Nonnull
+    private final Logger log = LoggerFactory.getLogger(DefaultTokenRequestEncoder.class);
+    
+    @Override
+    @Nullable public HttpUriRequest doApply(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+        try {
+            // Mandate HTTPS, so construct the URL from that.
+            final URI uri = new URIBuilder().setScheme(HTTPS)
+                    .setHost(getProviderMetadataContext()
+                            .getProviderInformation()
+                            .getTokenEndpointURI().getHost())
+                    .setPath(getProviderMetadataContext()
+                            .getProviderInformation()
+                            .getTokenEndpointURI().getPath())
+                    .build();                 
+            
+            // Add headers and create request.
+            final RequestBuilder rb = RequestBuilder.post().setUri(uri)
+                    .setHeader("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType())
+                    .setCharset(StandardCharset.UTF_8);
+            
+            // Add mandatory fields 
+            addRequiredFields(rb);
+            // Add fields that are required if certain conditions are met. 
+            addRequiredOnConditionFields(rb);            
+            // Build client_secret_basic authentication. TODO support others.
+            buildSecretBasicAuthentication(rb);            
+            
+            final HttpUriRequest request = rb.build();                  
+            log.debug("Token URL '{}'",request);            
+            return request;
+            
+        } catch (final Exception e) {
+            log.warn("Unable to encode token request", e);
+        }
+        return null;
+    }
+    
+    /**
+     * Add all required fields for the given grant_type to the request.
+     * 
+     * @param rb the request builder. 
+     */
+    private void addRequiredFields(@Nonnull final RequestBuilder rb) {
+        
+        rb.addParameter("grant_type", "authorization_code")
+        .addParameter("code",getAuthenticationResponse().getAuthorizationCode().getValue());
+    }
+    
+    
+    /**
+     * Add all fields to the request that are required if a given condition is met.
+     * 
+     * @param rb the request builder. 
+     */
+    private void addRequiredOnConditionFields(@Nonnull final RequestBuilder rb) {
+        
+        // Check redirect_uri was sent in authz request and add if so
+        if (getAuthenticationRequest().getRedirectURI() != null) {
+            rb.addParameter("redirect_uri", getAuthenticationRequest().getRedirectURI().toString());
+        }
+    }
+
+    /** 
+     * Build the client_secret_basic authentication using pre-emptive HTTP Basic auth.
+     * 
+     * @param rb the request builder to use.
+     */
+    //TODO taken from Nimbus ClientSecretBasic. Find best way to apply this.
+    private void buildSecretBasicAuthentication(final RequestBuilder rb) {
+        final StringBuilder sb = new StringBuilder();
+
+        sb.append(URLEncoder.encode(
+                getClientMetadataContext().getClientInformation().getID().getValue(), StandardCharsets.UTF_8));
+        sb.append(':');
+        sb.append(URLEncoder.encode(
+                getClientMetadataContext().getClientInformation().getSecret().getValue(), StandardCharsets.UTF_8));
+
+        final String authHeader =  "Basic " + Base64.encode(sb.toString().getBytes(StandardCharsets.UTF_8));
+        rb.addHeader("Authorization", authHeader);
+        
+    }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/package-info.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/package-info.java
new file mode 100644
index 0000000..f63a3fc
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * This package has encoders for various OIDC/OAuth2 requests.
+ */
+package net.shibboleth.idp.plugin.authn.oidc.rp.encoding.impl;
\ No newline at end of file
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
new file mode 100644
index 0000000..9dad4af
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractHttpOIDCAuthenticationAction.java
@@ -0,0 +1,111 @@
+/*
+ * 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.io.IOException;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+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.security.httpclient.HttpClientSecurityParameters;
+import org.opensaml.security.httpclient.HttpClientSecuritySupport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+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;
+
+/**
+ * An abstract class for actions that want to make synchronous HTTP requests.
+ */
+public class AbstractHttpOIDCAuthenticationAction extends AbstractOIDCAuthenticationAction {
+    
+    /** Class logger.*/
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractHttpOIDCAuthenticationAction.class);
+    
+    /** HttpClient for contacting Duo. */
+    @NonnullAfterInit private HttpClient httpClient;
+
+    /** HTTP client security parameters. */
+    @Nullable private HttpClientSecurityParameters httpClientSecurityParameters; 
+    
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        if (httpClient == null) {
+            throw new ComponentInitializationException("httpClient cannot be null");
+        }
+    }
+
+    /**
+     * Set the {@link HttpClient} to use.
+     * 
+     * @param client client to use
+     */
+    public void setHttpClient(@Nonnull final HttpClient client) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+        httpClient = Constraint.isNotNull(client, "HttpClient cannot be null");
+    }
+    
+    /**
+     * Set the optional client security parameters.
+     * 
+     * @param params the new client security parameters
+     */
+    public void setHttpClientSecurityParameters(@Nullable final HttpClientSecurityParameters params) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+        httpClientSecurityParameters = params;
+    }
+
+
+    /**
+     * 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.
+     * 
+     * @param request the prepared HTTP request
+     * 
+     * @return the response, never {@code null}.
+     * 
+     * @throws IOException if there is an error producing a response
+     */
+    @Nonnull protected HttpResponse executeHttpRequest(@Nonnull final HttpUriRequest request) throws IOException {
+        
+        Constraint.isNotNull(request, "Request can not be null");
+
+        final HttpClientContext clientContext = HttpClientContext.create();
+        HttpClientSecuritySupport.marshalSecurityParameters(clientContext, httpClientSecurityParameters, true);
+        HttpClientSecuritySupport.addDefaultTLSTrustEngineCriteria(clientContext, request);
+        final HttpResponse httpResponse = httpClient.execute(request, clientContext);
+        HttpClientSecuritySupport.checkTLSCredentialEvaluated(clientContext, request.getURI().getScheme());
+      
+       return httpResponse;
+  
+    }
+
+}
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
new file mode 100644
index 0000000..d7b4d9d
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCAuthenticationResponseAction.java
@@ -0,0 +1,204 @@
+/*
+ * 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 org.opensaml.messaging.context.MessageContext;
+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.OutboundMessageContextLookup;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
+import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
+
+import net.shibboleth.idp.authn.AbstractAuthenticationAction;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCClientMetadataContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCProviderMetadataContext;
+import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * 
+ * Abstract class for actions performing actions on {@link AuthenticationResponse} located under
+ * {@link ProfileRequestContext#getOutboundMessageContext()#getM}.
+ * 
+ * FIXME: this is very close to that in the OP AbstractOIDCAuthenticationResponseAction. Maybe consolidate
+ * TODO create one of these for the outbound phase (auth request) and one for the other phases which will 
+ * include the response.
+ */
+abstract class AbstractOIDCAuthenticationAction extends AbstractAuthenticationAction {
+
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(AbstractOIDCAuthenticationAction.class);
+    
+    /** Lookup strategy to locate the OP metadata to use.*/
+    @Nonnull private Function<ProfileRequestContext, OIDCProviderMetadataContext> providerMetadataLookupStrategy;
+    
+    /** Lookup strategy to locate the metadata of the client to use to communicate with the OP.*/
+    @Nonnull private Function<ProfileRequestContext,OIDCClientMetadataContext> clientMetadataLookupStrategy;
+
+    /** OIDC authentication response from downstream OP. */
+    @Nullable private AuthenticationSuccessResponse authnResponse;
+    
+    /** OIDC authentication request built by the IdP. */
+    @Nullable private OIDCAuthenticationRequest authnRequest;
+
+    /** OIDC client Metadata context. */
+    @Nullable private OIDCClientMetadataContext clientMetadataContext;
+    
+    /** OIDC Metadata context. */
+    @Nullable private OIDCProviderMetadataContext providerMetadataContext;
+    
+    /** Constructor.*/
+    public AbstractOIDCAuthenticationAction() {
+        providerMetadataLookupStrategy = new ChildContextLookup<>(OIDCProviderMetadataContext.class).compose(
+                new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
+                        new OutboundMessageContextLookup()));
+        
+        clientMetadataLookupStrategy = new ChildContextLookup<>(OIDCClientMetadataContext.class).compose(
+                new OutboundMessageContextLookup());
+    }
+    
+    /**
+     * Set the lookup strategy to locate the OpenID providers metadata.
+     * 
+     * @param strategy the strategy.
+     */
+    public void setProviderMetadataLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, OIDCProviderMetadataContext> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        providerMetadataLookupStrategy = 
+                Constraint.isNotNull(strategy,"Provider metadata lookup strategy can not be null");
+    }
+    
+    /**
+     * Set the lookup strategy to locate the OpenID client metadata.
+     * 
+     * @param strategy the strategy.
+     */
+    public void setClientMetadataLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, OIDCClientMetadataContext> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        clientMetadataLookupStrategy = Constraint.isNotNull(strategy,"Client metadata lookup strategy can not be null");
+    }
+
+    /**
+     * Returns the authentication response from the downstream OP.
+     * 
+     * @return the authentication response.
+     */
+    @Nullable public AuthenticationSuccessResponse getAuthenticationResponse() {
+        return authnResponse;
+    }
+    
+    /**
+     * Returns the authentication request build by this IdP.
+     * 
+     * @return the authentication request.
+     */
+    @Nullable public OIDCAuthenticationRequest getAuthenticationRequest() {
+        return authnRequest;
+    }
+
+    /**
+     * Returns the client metadata context.
+     * 
+     * @return The client metadata context.
+     */
+    @Nullable public OIDCClientMetadataContext getClientMetadataContext() {
+        return clientMetadataContext;
+    }
+    
+    /**
+     * Returns the OIDC provider metadata context.
+     * 
+     * @return The provider metadata context.
+     */
+    @Nullable public OIDCProviderMetadataContext getProviderMetadataContext() {
+        return providerMetadataContext;
+    }
+
+    //TODO these come from the outbound message context, so are not general to other actions
+
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final AuthenticationContext authenticationContext) {
+
+
+        final MessageContext inboundMessageCtx = profileRequestContext.getInboundMessageContext();
+        if (inboundMessageCtx == null) {
+            log.error("{} No inbound message context", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            return false;
+        }
+        if (inboundMessageCtx.getMessage() == null) {
+            log.error("{} No inbound message", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            return false;
+        }
+        
+        if (!(inboundMessageCtx.getMessage() instanceof AuthenticationSuccessResponse)) {
+            log.error("{} No inbound authentication success response", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            return false;
+        }
+        authnResponse = (AuthenticationSuccessResponse)inboundMessageCtx.getMessage();
+        
+        final MessageContext outboundMsgContext = profileRequestContext.getOutboundMessageContext();
+        if (outboundMsgContext == null) {
+            log.error("{} Outbound message context was null", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            return false;
+        } 
+        if (!(outboundMsgContext.getMessage() instanceof OIDCAuthenticationRequest)) {
+            log.error("{} Outbound message was not an authentication request", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            return false;
+        }
+        authnRequest = (OIDCAuthenticationRequest) outboundMsgContext.getMessage();
+        
+        clientMetadataContext = clientMetadataLookupStrategy.apply(profileRequestContext);
+        if (clientMetadataContext == null) {
+            log.error("{} No client metadata found for relying party", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            return false;
+        }
+        
+        providerMetadataContext = providerMetadataLookupStrategy.apply(profileRequestContext);
+        if (providerMetadataContext == null) {
+            log.error("{} No provider metadata found for peer", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            return false;
+        }
+        return true;
+    }
+
+}
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 4d7511c..871c634 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
@@ -17,32 +17,34 @@
 
 package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
 
-import java.net.URI;
-import java.net.URISyntaxException;
+import java.util.function.Function;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.messaging.context.navigate.ParentContextLookup;
 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.opensaml.profile.context.navigate.OutboundMessageContextLookup;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.nimbusds.oauth2.sdk.ResponseType;
-import com.nimbusds.oauth2.sdk.id.ClientID;
-
 import net.shibboleth.idp.authn.AbstractAuthenticationAction;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCMetadataContext;
+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.oidc.rp.messaging.context.OIDCClientMetadataContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCProviderMetadataContext;
 import net.shibboleth.idp.profile.IdPEventIds;
 import net.shibboleth.idp.profile.context.RelyingPartyContext;
 import net.shibboleth.oidc.profile.config.OIDCAuthorizationConfiguration;
 import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
 
 
 /**
@@ -70,15 +72,75 @@ public class AddAuthzRequest extends AbstractAuthenticationAction {
     @Nullable private OIDCAuthorizationConfiguration profileConfiguration;
     
     /** The metadata belonging to the client registered with the current OpenID Provider.*/
-    @Nullable private OIDCMetadataContext clientMetadata;
+    @Nullable private OIDCClientMetadataContext clientMetadata;
     
     /** The metadata belonging to the OpenID Provider.*/
     @Nullable private OIDCProviderMetadataContext providerMetadata;
     
+    /** The response type and mode context.*/
+    @Nullable private ResponseTypeAndModeContext responseTypeAndModeContext;
+    
+    /** Lookup strategy to locate the OP metadata to use.*/
+    @Nonnull private Function<ProfileRequestContext, OIDCProviderMetadataContext> providerMetadataLookupStrategy;
+    
+    /** Lookup strategy to locate the metadata of the client to use to communicate with the OP.*/
+    @Nonnull private Function<ProfileRequestContext,OIDCClientMetadataContext> clientMetadataLookupStrategy;
+    
+    /** Lookup strategy to locate the response_mode and response_type context.*/
+    @Nonnull private Function<ProfileRequestContext,ResponseTypeAndModeContext> responseTypeAndModeLookupStrategy;
+    
     /** Constructor.*/
     public AddAuthzRequest() {
         // Fool the parent class into looking above instead of below the PRC for the context.
         setAuthenticationContextLookupStrategy(new ParentContextLookup<>(AuthenticationContext.class));
+        
+        providerMetadataLookupStrategy = new ChildContextLookup<>(OIDCProviderMetadataContext.class).compose(
+                new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
+                        new InboundMessageContextLookup()));
+        
+        clientMetadataLookupStrategy = new ChildContextLookup<>(OIDCClientMetadataContext.class).compose(
+                new InboundMessageContextLookup());
+        
+        responseTypeAndModeLookupStrategy = new ChildContextLookup<>(ResponseTypeAndModeContext.class).compose(
+                        new OutboundMessageContextLookup());
+    }
+    
+    /**
+     * Set the lookup strategy to locate the OpenID providers metadata.
+     * 
+     * @param strategy the strategy.
+     */
+    public void setProviderMetadataLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, OIDCProviderMetadataContext> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        providerMetadataLookupStrategy = 
+                Constraint.isNotNull(strategy,"Provider metadata lookup strategy can not be null");
+    }
+    
+    /**
+     * Set the lookup strategy to locate the OpenID client metadata.
+     * 
+     * @param strategy the strategy.
+     */
+    public void setClientMetadataLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, OIDCClientMetadataContext> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        clientMetadataLookupStrategy = Constraint.isNotNull(strategy,"Client metadata lookup strategy can not be null");
+    }
+    
+    /**
+     * Set the lookup strategy to locate the response type and mode context.
+     * 
+     * @param strategy the strategy.
+     */
+    public void setResponseTypeAndModeLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, ResponseTypeAndModeContext> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        responseTypeAndModeLookupStrategy = Constraint.isNotNull(strategy,
+                "ResponseTypeAndMode context lookup strategy can not be null");
     }
     
     /**
@@ -100,6 +162,8 @@ public class AddAuthzRequest extends AbstractAuthenticationAction {
             return false;
         }
         
+        
+        // TODO we need to do something with the rpCtx.
         final RelyingPartyContext rpCtx = profileRequestContext.getSubcontext(RelyingPartyContext.class);
         if (rpCtx != null && rpCtx.getConfiguration() != null &&
                 rpCtx.getProfileConfig() instanceof OIDCAuthorizationConfiguration) {
@@ -122,21 +186,27 @@ public class AddAuthzRequest extends AbstractAuthenticationAction {
             return false;
         }
         
-        final MessageContext inboundMessageCtx = profileRequestContext.getInboundMessageContext();
-        clientMetadata = inboundMessageCtx.getSubcontext(OIDCMetadataContext.class);
+        responseTypeAndModeContext = responseTypeAndModeLookupStrategy.apply(profileRequestContext);
+        if (responseTypeAndModeContext == null) {
+            log.error("{} ResponseTypeContext not found", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
+            return false;
+        }
+
+        clientMetadata = clientMetadataLookupStrategy.apply(profileRequestContext);
         if (clientMetadata == null) {
             log.debug("{} Inbound message context did not contain client metadata", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
             return false;
         }
-        providerMetadata = inboundMessageCtx.getSubcontext(OIDCProviderMetadataContext.class);
+        providerMetadata = providerMetadataLookupStrategy.apply(profileRequestContext);
         if (providerMetadata == null) {
             log.debug("{} Inbound message context did not contain provider metadata", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
             return false;
         }
         
-        outboundMessageCtx.setMessage(null);
+        profileRequestContext.getOutboundMessageContext().setMessage(null);
         
         return true;
     }
@@ -151,10 +221,19 @@ public class AddAuthzRequest extends AbstractAuthenticationAction {
         
         final OIDCAuthenticationRequest request = new OIDCAuthenticationRequest(clientMetadata.getClientInformation().getID());
 
-        request.setResponseType(ResponseType.CODE);
+        request.setResponseType(responseTypeAndModeContext.getResponseType());
+        //TODO spec says response mode not recommended if the default type for response_type. Check here?
+        request.setResponseMode(responseTypeAndModeContext.getResponseMode());
         request.setEndpointURI(providerMetadata.getProviderInformation().getAuthorizationEndpointURI());
         request.setRedirectURI(clientMetadata.getClientInformation().getMetadata().getRedirectionURI());
         
+        //TODO if force-authn
+//        try {
+//            request.setPrompt(Prompt.parse("none"));            
+//        } catch (ParseException e) {
+//            log.error("{} Unable to set prompt", e);
+//        }
+        
         log.debug("{} Built authorization request for endpoint '{}'",getLogPrefix(), request.getEndpointURI());
         profileRequestContext.getOutboundMessageContext().setMessage(request);
         
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 d9a17f6..137789d 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
@@ -27,25 +27,34 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
+import org.opensaml.messaging.context.MessageContext;
 import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.messaging.decoder.MessageDecoder;
+import org.opensaml.messaging.decoder.MessageDecodingException;
 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;
 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;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 
 /**
  * 
@@ -73,6 +82,12 @@ import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
 @RequestMapping("%{shibboleth.authn.OIDC.externalAuthnPath:/Authn/OIDC/RP}")
 public class AuthorizationController {
     
+    /** The name of the Http parameter that stores the authorisation code.*/
+    @Nonnull @NotEmpty public static final String CODE_PARAMETER = "code";
+    
+    /** The name of the Http parameter that stores the state value.*/
+    @Nonnull @NotEmpty public static final String STATE_PARAMETER = "state";    
+    
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(AuthorizationController.class);
     
@@ -106,7 +121,8 @@ public class AuthorizationController {
      */
     @GetMapping("/authz")
     public void authorizationRequest(@Nonnull final HttpServletRequest httpRequest, 
-            @Nonnull final HttpServletResponse httpResponse) throws ServletException, IOException, ExternalAuthenticationException {
+            @Nonnull final HttpServletResponse httpResponse) 
+                    throws ServletException, IOException, ExternalAuthenticationException {
         
         final String key = ExternalAuthentication.startExternalAuthentication(httpRequest);
         final ProfileRequestContext prc = ExternalAuthentication.getProfileRequestContext(key, httpRequest);
@@ -169,42 +185,79 @@ public class AuthorizationController {
      * @param httpRequest the servlet request.
      * @param httpResponse the servlet response.
      * 
-     * @throws ServletException throw if there is an error accepting the authz response.      
+     * @throws ExternalAuthenticationException throw if there is an error accepting the authz response.      
      * @throws IOException throw if there is an error accepting the authz response.
      */
     @GetMapping("/callback")
     public void authorizationCallback(@Nonnull final HttpServletRequest httpRequest,
-            @Nonnull final HttpServletResponse httpResponse) throws ServletException, IOException {
-       
+            @Nonnull final HttpServletResponse httpResponse) throws ExternalAuthenticationException, IOException {
+        
+        // We REQUIRE state to be passed in the request and response in order to resume the flow.        
+        final String state = httpRequest.getParameter(STATE_PARAMETER);
+        
+        log.debug("OIDC Relying Party Proxy recieved callback with state '{}'", state);
+        
+        if (state == null) {
+            throw new ExternalAuthenticationException("OIDC authentication response must contain a 'state' parameter");
+        }
+        
+        final String key;
         try {
-//            final HttpSession session = httpRequest.getSession();
-//            if (session == null) {
-//                throw new ExternalAuthenticationException("No session exists, this URL shouldn't be called directly");
-//            }
-//            final String key = StringSupport.trimOrNull((String) httpRequest.getSession()
-//                    .getAttribute(AuthorizationController.SESSION_ATTR_FLOWKEY));
-//            if (key == null) {
-//                throw new ExternalAuthenticationException(
-//                        "Could not find value for " + AuthorizationController.SESSION_ATTR_FLOWKEY);
-//            }
-//            final OpenIDConnectContext openIDConnectContext =
-//                    (OpenIDConnectContext) httpRequest.getSession()
-//                            .getAttribute(AuthorizationController.SESSION_ATTR_SUCTX);
-//            if (openIDConnectContext == null) {
-//                throw new ExternalAuthenticationException(
-//                        "Could not find value for " + AuthorizationController.SESSION_ATTR_SUCTX);
-//            }
-//            log.trace("Attempting URL {}?{}", httpRequest.getRequestURL(), httpRequest.getQueryString());
-//            try {
-//                openIDConnectContext.setAuthenticationResponseURI(httpRequest);
-//            } catch (final URISyntaxException e) {
-//                throw new ExternalAuthenticationException("Could not parse response URI", e);
-//            }
-            ExternalAuthentication.finishExternalAuthentication("key-wrong", httpRequest, httpResponse);
-        } catch (final ExternalAuthenticationException e) {
-            log.error("Could not finish the external authentication", e);          
-            throw new ServletException("Error finishing the external authentication", e);
+            key = OIDCProxySupport.extractKeyFromState(state);
+        } catch (final OIDCProxyException e) {
+            throw new ExternalAuthenticationException("Flow execution key component could not be found in the "
+                    + "returned state, unable to resume the flow execution",e);
+        }      
+        
+        final ProfileRequestContext prc = ExternalAuthentication.getProfileRequestContext(key, httpRequest);
+        final OIDCAuthnContext oidcContext = oidcContextLookupStrategy.apply(prc);
+        if (oidcContext == null) {
+            log.error("OIDCAuthnContext not found");
+            httpRequest.setAttribute(ExternalAuthentication.AUTHENTICATION_ERROR_KEY, EventIds.INVALID_PROFILE_CTX);
+            ExternalAuthentication.finishExternalAuthentication(key, httpRequest, httpResponse);
+            return;
         }
         
+        final ProfileRequestContext nestedPRC = profileRequestContextLookupStrategy.apply(prc);
+        if (nestedPRC == null) {
+            log.error("Nested ProfileRequestContext not found");
+            httpRequest.setAttribute(ExternalAuthentication.AUTHENTICATION_ERROR_KEY, EventIds.INVALID_PROFILE_CTX);
+            ExternalAuthentication.finishExternalAuthentication(key, httpRequest, httpResponse);
+            return;
+        }
+        // Find which response mode to expect.
+        final ResponseTypeAndModeContext responseCtx = 
+                nestedPRC.getOutboundMessageContext().getSubcontext(ResponseTypeAndModeContext.class);
+        if (responseCtx == null) {
+            log.error("ResponseTypeAndMode context not found, cannot decode incomming request");
+            httpRequest.setAttribute(ExternalAuthentication.AUTHENTICATION_ERROR_KEY, EventIds.INVALID_PROFILE_CTX);
+            ExternalAuthentication.finishExternalAuthentication(key, httpRequest, httpResponse);
+            return;
+        }
+        log.debug("OIDC response_type '{}' and response_mode '{}' requested, decoding incoming request", 
+                responseCtx.getResponseMode(), responseCtx.getResponseType());
+        
+        try {
+            final MessageDecoder decoder = 
+                    oidcContext.getMessageDecoderFactory().apply(responseCtx.getResponseMode().getValue());
+            if (decoder == null) {
+                throw new MessageDecodingException("Unable to obtain MessageDecoder for response_mode: " 
+                        + responseCtx.getResponseMode().getValue());
+            }
+            try {
+                decoder.initialize();
+                decoder.decode();
+                final MessageContext messageContext = decoder.getMessageContext();
+                nestedPRC.setInboundMessageContext(messageContext);
+            } finally {
+                decoder.destroy();
+            }
+        } catch (final MessageDecodingException | ComponentInitializationException e) {
+            log.error("Unable to decode OIDC response", e);
+            httpRequest.setAttribute(ExternalAuthentication.AUTHENTICATION_ERROR_KEY, EventIds.UNABLE_TO_DECODE);
+        }
+        
+        ExternalAuthentication.finishExternalAuthentication(key, httpRequest, httpResponse);
+        
     }
 }
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultResponseModeLookupFunction.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultResponseModeLookupFunction.java
new file mode 100644
index 0000000..ed531df
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultResponseModeLookupFunction.java
@@ -0,0 +1,62 @@
+/*
+ * 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.BiFunction;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.oauth2.sdk.ResponseMode;
+import com.nimbusds.oauth2.sdk.ResponseType;
+
+/**
+ * A lookup function that returns a response_mode compatible with the response_type described.
+ * The response_mode can be overriden by the profile configuration.
+ */
+public class DefaultResponseModeLookupFunction 
+                    implements BiFunction<ProfileRequestContext, ResponseType, ResponseMode> {
+    
+    /** Class logger. */
+    @Nonnull
+    private final Logger log = LoggerFactory.getLogger(DefaultResponseModeLookupFunction.class);
+
+    @Override
+    @Nonnull public ResponseMode apply(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final ResponseType responseType) {
+        
+//        final RelyingPartyContext rpCtx = profileRequestContext.getSubcontext(RelyingPartyContext.class);
+//        OIDCAuthorizationConfiguration profileConfiguration = null;
+//
+//        if (rpCtx != null && rpCtx.getProfileConfig() instanceof OIDCAuthorizationConfiguration) {
+//            profileConfiguration = (OIDCAuthorizationConfiguration) rpCtx.getProfileConfig();
+//            
+//            final OIDCHttpRequestMethod requestMethodFromConfig =
+//                    profileConfiguration.getHttpRequestMethod(profileRequestContext);
+//        }
+        //TODO extract possible response mode from profile config as well. Must be compatible with the response_type
+        final ResponseMode compatibleMode = ResponseMode.resolve(null, responseType);
+        log.debug("Response mode '{}' resolved from response type '{}'", compatibleMode, responseType);
+        return compatibleMode;
+                
+    }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultResponseTypeLookupFunction.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultResponseTypeLookupFunction.java
new file mode 100644
index 0000000..e08eacf
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultResponseTypeLookupFunction.java
@@ -0,0 +1,73 @@
+/*
+ * 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 org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.oauth2.sdk.ResponseType;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCClientMetadataContext;
+
+/**
+ * A lookup function that returns a response_type from the 'first' described in a client's metadata. 
+ * If none are found, the default response type of 'code' is returned.
+ */
+public class DefaultResponseTypeLookupFunction implements Function<ProfileRequestContext, ResponseType> {
+    
+    /** The Default response type if none is selected.*/
+    @Nonnull private static final ResponseType DEFAULT_RESPONSE_TYPE = ResponseType.CODE;
+    
+    /** Class logger. */
+    @Nonnull
+    private final Logger log = LoggerFactory.getLogger(DefaultResponseTypeLookupFunction.class);
+
+    @Override
+    @Nonnull public ResponseType apply(@Nullable final ProfileRequestContext profileRequestContext) {
+        
+        if (profileRequestContext == null) {
+            return DEFAULT_RESPONSE_TYPE;
+        }
+        
+        final MessageContext inboundMessageCtx = profileRequestContext.getInboundMessageContext();
+        final OIDCClientMetadataContext clientMetadataCtx = 
+                inboundMessageCtx.getSubcontext(OIDCClientMetadataContext.class);
+        if (clientMetadataCtx != null &&
+                clientMetadataCtx.getClientInformation() != null &&
+                    clientMetadataCtx.getClientInformation().getOIDCMetadata() != null &&
+                        clientMetadataCtx.getClientInformation().getOIDCMetadata().getResponseTypes() != null) {
+            
+            final ResponseType selectedResponseType = 
+                    clientMetadataCtx.getClientInformation().getOIDCMetadata().getResponseTypes().iterator().next();
+            log.debug("Response_type has been determined from client metadata as '{}'", selectedResponseType);
+            return selectedResponseType;
+        } 
+       
+        log.debug("Client metadata did not contain any configured response_types, "
+                + "returning the default '{}' type", DEFAULT_RESPONSE_TYPE);
+        return DEFAULT_RESPONSE_TYPE;
+    }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExchangeCodeForToken.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExchangeCodeForToken.java
new file mode 100644
index 0000000..2fc953d
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExchangeCodeForToken.java
@@ -0,0 +1,157 @@
+/*
+ * 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.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;
+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.context.TokenResponseContext;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+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 a token response which 
+ * contains an id_token. Once obtained, adds the token to the {@link TokenResponseContext}.
+ * 
+ * FIXME: these conditions
+ * 
+ * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
+ * @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. 
+ */
+public class ExchangeCodeForToken extends AbstractHttpOIDCAuthenticationAction {
+    
+    /** Class logger.*/
+    @Nonnull private final Logger log = LoggerFactory.getLogger(ExchangeCodeForToken.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> 
+            tokenResponseContextLookupStrategy;
+    
+    /** Constructor.*/
+    public ExchangeCodeForToken() {
+        tokenResponseContextLookupStrategy =
+                new ChildContextLookup<>(TokenResponseContext.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) {
+        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) { 
+        
+          log.debug("{} Exchanging auth_code '{}' for id_token from upstream OP '{}'", getLogPrefix(), 
+                  getAuthenticationResponse().getAuthorizationCode(),
+                  authenticationContext.getAuthenticatingAuthority());
+          
+          final TokenResponseContext 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;
+              }
+              responseCtx.setRawTokenResponse(responseObject);
+              log.debug("{}: Token request response '{}'",getLogPrefix(), responseObject);
+
+          } catch (final IOException 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
new file mode 100644
index 0000000..c286ec7
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExtractIDTokenFromResponse.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import java.text.ParseException;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jose.JOSEObject;
+import com.nimbusds.jose.JWEObject;
+import com.nimbusds.jose.JWSObject;
+import com.nimbusds.jose.PlainObject;
+
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.oidc.profile.core.OidcEventIds;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/** Action that extracts and id_token from the inbound message context and sets it onto a TODO context.*/
+public class ExtractIDTokenFromResponse extends AbstractProfileAction {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(ExtractIDTokenFromResponse.class);
+    
+    /** Function that looks up a raw id_token value as a base64 encoded string.*/
+    @NonnullAfterInit private Function<ProfileRequestContext, String> rawIdTokenLookupStrategy;
+    
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        if (rawIdTokenLookupStrategy == null) {
+            throw new ComponentInitializationException("RawIdTokenLookupStrategy cannot be null");
+        }
+    }
+
+    /**
+     * Set the strategy used to lookup a base64 encoded JWT from the profile request context.
+     *
+     * @param strategy the strategy to use.
+     */
+    public void setRawIdTokenLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, String> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        rawIdTokenLookupStrategy = Constraint.isNotNull(strategy,"Raw id_token lookup strategy can not be null");
+    }
+    
+    @Override protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        
+        return true;
+    }
+    
+    @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        
+        final String rawIdTokenValue = rawIdTokenLookupStrategy.apply(profileRequestContext);
+        if (rawIdTokenValue == null) {
+            log.warn("{} Unable to lookup id_token",getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext,OidcEventIds.INVALID_ID_TOKEN);
+            return;
+        }
+        log.trace("{} 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) {
+                final PlainObject plainObject = (PlainObject)joseObject;
+                
+            } else if (joseObject instanceof JWSObject) {
+                final JWSObject jwsObject = (JWSObject)joseObject;
+                
+            } else if (joseObject instanceof JWEObject) {
+                final JWEObject jweObject = (JWEObject)joseObject;
+                
+            }
+            
+        } catch (final ParseException e) {
+            log.warn("{}: Unable to parse id_token",getLogPrefix(), e);
+            ActionSupport.buildEvent(profileRequestContext,OidcEventIds.INVALID_ID_TOKEN);
+            return;
+            
+        }
+        
+    }
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/GetOIDCTokenResponse.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/GetOIDCTokenResponse.java
deleted file mode 100644
index d4b047d..0000000
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/GetOIDCTokenResponse.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
-
-import java.io.IOException;
-
-import javax.annotation.Nonnull;
-
-import net.shibboleth.idp.authn.AbstractExtractionAction;
-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.nimbusds.oauth2.sdk.AuthorizationCode;
-import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
-import com.nimbusds.oauth2.sdk.AuthorizationGrant;
-import com.nimbusds.oauth2.sdk.ParseException;
-import com.nimbusds.oauth2.sdk.SerializeException;
-import com.nimbusds.oauth2.sdk.TokenErrorResponse;
-import com.nimbusds.oauth2.sdk.TokenRequest;
-import com.nimbusds.oauth2.sdk.TokenResponse;
-import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
-import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
-import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
-import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
-import com.nimbusds.openid.connect.sdk.OIDCTokenResponseParser;
-
-/**
- * An action that exchanges the OAuth 2.0 authorization code inside the {@link OpenIDConnectContext} for
- * an id_token from the OpenID Connect Provider's Token Endpoint. The ID Token is placed inside the 
- * {@link OpenIDConnectContext}.
- * 
- * 
- * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class, false) != null</pre>
- * @pre <pre>AuthenticationContext.getSubcontext(OpenIDConnectContext.class, false) != null</pre>
- * @post If getIDToken() !=null the method returns immediately. Otherwise, if the token endpoint returns an 
- * {@link OIDCTokenResponse} whose indicatesSuccess()==true, the token is attached to the {@link OpenIDConnectContext}.
- * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
- * @event {@link AuthnEventIds#NO_CREDENTIALS}
- * @event {@link AuthnEventIds#INVALID_CREDENTIALS}
- * 
- * @since 4.0.0
- */
-public class GetOIDCTokenResponse extends AbstractExtractionAction {
-
-    /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(GetOIDCTokenResponse.class);
-
-    /** {@inheritDoc} */
-    @Override
-    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;
-        }
-        if (oidcCtx.getIDToken() != null) {
-            log.debug("id_token already exists, nothing to fetch from token endpoint");
-            
-            return;
-        }
-        final AuthenticationSuccessResponse response = oidcCtx.getAuthenticationSuccessResponse();
-        
-        if (response == null) {
-            log.info("{} Authentication success response not found in OpenIDConnectContext", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
-           
-            return;
-        }
-        final AuthorizationCode code = response.getAuthorizationCode();
-        //TODO P.S. code could be null, should not be.
-        final AuthorizationGrant codeGrant = new AuthorizationCodeGrant(code, oidcCtx.getRedirectURI());
-        //TODO P.S. neither should be null, both could be (although not after SetOIDCInformation initilises the context)
-        final ClientAuthentication clientAuth = new ClientSecretBasic(oidcCtx.getClientID(), oidcCtx.getClientSecret());
-        
-        log.trace("{} Using the following OIDC token endpoint URI: {}", getLogPrefix(),
-                oidcCtx.getoIDCProviderMetadata().getTokenEndpointURI());
-        
-        final TokenRequest tokenRequest =
-                new TokenRequest(oidcCtx.getoIDCProviderMetadata().getTokenEndpointURI(), clientAuth, codeGrant);
-        final OIDCTokenResponse oidcTokenResponse;
-        try {
-            final TokenResponse tokenResponse = OIDCTokenResponseParser.parse(tokenRequest.toHTTPRequest().send());
-            // TokenResponse can only be TokenErrorResponse or OIDCTokenResponse
-            if (tokenResponse instanceof OIDCTokenResponse) {
-                
-                oidcTokenResponse = (OIDCTokenResponse) tokenResponse;                
-                oidcCtx.setOidcTokenResponse(oidcTokenResponse);
-                
-                if (log.isTraceEnabled() && oidcTokenResponse.getOIDCTokens().getIDToken() != null) {
-                    log.trace("{} Retrieved id_token '{}'",getLogPrefix(),
-                            oidcTokenResponse.getOIDCTokens().getIDToken().getParsedString());
-                }
-                
-            } else {
-                final TokenErrorResponse errorResponse = (TokenErrorResponse) tokenResponse;
-                log.warn("{} Error in retrieving id_token, response error is {}", getLogPrefix(),
-                        errorResponse.getErrorObject());
-                // should map error object OAuth2 Error types to new or existing event ids.
-                ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_CREDENTIALS);
-                return;
-            }
-
-        } catch (final SerializeException | IOException | ParseException e) {
-            log.error("{} token exchange failed", getLogPrefix(), e);
-            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_CREDENTIALS);           
-            return;
-        }
-
-       
-    }
-}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOutboundAuthorizationRequestMessageContext.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOutboundAuthorizationRequestMessageContext.java
index 2296d48..0141ca1 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOutboundAuthorizationRequestMessageContext.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOutboundAuthorizationRequestMessageContext.java
@@ -20,7 +20,9 @@ package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
 import java.util.function.Function;
 
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
+import org.opensaml.messaging.context.BaseContext;
 import org.opensaml.messaging.context.MessageContext;
 import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.action.ActionSupport;
@@ -30,8 +32,11 @@ import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCClientMetadataContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCProviderMetadataContext;
 import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.idp.profile.IdPEventIds;
 import net.shibboleth.idp.profile.context.RelyingPartyContext;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
@@ -41,39 +46,42 @@ public class InitializeOutboundAuthorizationRequestMessageContext extends Abstra
     
     /** Class logger. */
     @Nonnull
-    private final Logger log = LoggerFactory.getLogger(InitializeOutboundAuthorizationRequestMessageContext.class);    
+    private final Logger log = LoggerFactory.getLogger(InitializeOutboundAuthorizationRequestMessageContext.class); 
     
-    /** Strategy function to lookup the {@link OIDCMetadataContext}. */
-    @Nonnull private Function<ProfileRequestContext, OIDCProviderMetadataContext> oidcProviderMetadataCtxLookupStrategy;
+    /** The {@link OIDCPeerEntityContext} to base the outbound context on. */
+    @Nullable private OIDCPeerEntityContext peerEntityCtx;
+        
+    /** Strategy function to lookup the {@link OIDCClientMetadataContext} that represents this client during
+     * communication with the given OIDC peer. */
+    @Nonnull 
+    private Function<ProfileRequestContext, OIDCClientMetadataContext> oidcClientMetadataCtxLookupStrategy;
     
     /**
      * Strategy used to locate the {@link RelyingPartyContext} associated with a given {@link ProfileRequestContext}.
      */
     @Nonnull private Function<ProfileRequestContext, RelyingPartyContext> relyingPartyCtxLookupStrategy;
-    
-    /** The relying party context used for storing the SAML metadata context. */
-    //TODO do we need this one? or just the OIDC provider metadata context? 
-    private RelyingPartyContext relyingPartyCtx;
+
     
     /**
      * Constructor.
      */
     public InitializeOutboundAuthorizationRequestMessageContext() {
-        oidcProviderMetadataCtxLookupStrategy = new ChildContextLookup<>(OIDCProviderMetadataContext.class).compose(
-                new InboundMessageContextLookup());     
+        oidcClientMetadataCtxLookupStrategy = new ChildContextLookup<>(OIDCClientMetadataContext.class).compose(
+                new InboundMessageContextLookup());    
         relyingPartyCtxLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class);
     }
     
     /**
-     * Set the strategy to lookup the {@link oidcProviderMetadataCtxLookupStrategy} from the {@link ProfileRequestContext}.
+     * Set the strategy to lookup the {@link oidcProviderMetadataCtxLookupStrategy} 
+     * from the {@link ProfileRequestContext}.
      * 
      * @param strgy What to set.
      */
-    public void setOIDCProviderMetadataContextLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext, OIDCProviderMetadataContext> strgy) {
+    public void setOIDCClientMetadataContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, OIDCClientMetadataContext> strgy) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
 
-        oidcProviderMetadataCtxLookupStrategy = Constraint.isNotNull(strgy, "Injected Metadata Strategy cannot be null");
+        oidcClientMetadataCtxLookupStrategy = Constraint.isNotNull(strgy, "Injected Metadata Strategy cannot be null");
     }
     
     /**
@@ -98,13 +106,22 @@ public class InitializeOutboundAuthorizationRequestMessageContext extends Abstra
             return false;
         }
         
-        relyingPartyCtx = relyingPartyCtxLookupStrategy.apply(profileRequestContext);
+        final RelyingPartyContext relyingPartyCtx = relyingPartyCtxLookupStrategy.apply(profileRequestContext);
         if (relyingPartyCtx == null) {
             log.error("{} No relying party context", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
             return false;
         }
         
+        final BaseContext identifyingCtx = relyingPartyCtx.getRelyingPartyIdContextTree();
+        if (!(identifyingCtx instanceof OIDCPeerEntityContext)) {
+            log.debug("{} No OIDC peer entity context found via relying party context", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CTX);
+            return false;
+        }
+
+        peerEntityCtx = (OIDCPeerEntityContext) identifyingCtx;
+        
         return true;
         
     }
@@ -115,7 +132,28 @@ public class InitializeOutboundAuthorizationRequestMessageContext extends Abstra
         
         final MessageContext msgCtx = new MessageContext();
         profileRequestContext.setOutboundMessageContext(msgCtx);
-        //msgCtx.addSubcontext(new OIDCAuthorizationRequestContext());
+        
+        final OIDCPeerEntityContext outboundPeerContext = msgCtx.getSubcontext(OIDCPeerEntityContext.class, true);
+        outboundPeerContext.setIdentifier(peerEntityCtx.getIdentifier());
+        
+        final OIDCProviderMetadataContext inboundProviderMetadata = 
+                peerEntityCtx.getSubcontext(OIDCProviderMetadataContext.class);
+        if (inboundProviderMetadata != null) {
+            
+            // Pass a reference here? we do not need to mutate the metadata
+            final OIDCProviderMetadataContext outMetadata = 
+                    new OIDCProviderMetadataContext(inboundProviderMetadata.getProviderInformation());
+            
+            outboundPeerContext.addSubcontext(outMetadata);
+            
+        }
+        final OIDCClientMetadataContext inboundClientMetadata = 
+                oidcClientMetadataCtxLookupStrategy.apply(profileRequestContext);
+        
+        final OIDCClientMetadataContext outboundClientMetadata = new OIDCClientMetadataContext();
+        outboundClientMetadata.setClientInformation(inboundClientMetadata.getClientInformation());
+        msgCtx.addSubcontext(outboundClientMetadata);
+
         log.debug("{} Initialized outbound message context", getLogPrefix());
 
 
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeRelyingPartyContext.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeRelyingPartyContext.java
index 3d1376e..ac3bc54 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeRelyingPartyContext.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeRelyingPartyContext.java
@@ -22,17 +22,18 @@ import java.util.function.Function;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
-import org.opensaml.messaging.context.MessageContext;
 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.opensaml.saml.common.messaging.context.SAMLPeerEntityContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCClientMetadataContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCProviderMetadataContext;
-import net.shibboleth.idp.plugin.authn.oidc.rp.metadata.impl.DefaultIssuerIDLookupFunction;
 import net.shibboleth.idp.profile.AbstractProfileAction;
 import net.shibboleth.idp.profile.IdPEventIds;
 import net.shibboleth.idp.profile.context.RelyingPartyContext;
@@ -57,11 +58,15 @@ public class InitializeRelyingPartyContext extends AbstractProfileAction {
     /** Strategy that will return or create a {@link RelyingPartyContext}. */
     @Nonnull private Function<ProfileRequestContext, RelyingPartyContext> relyingPartyContextCreationStrategy;
     
-    /** Strategy that will return {@link OIDCMetadataContext}. */
-    @Nonnull private Function<ProfileRequestContext, OIDCProviderMetadataContext> oidcProviderMetadataContextLookupStrategy;
+    /** Strategy that will return {@link OIDCClientMetadataContext}. */
+    @Nonnull 
+    private Function<ProfileRequestContext, OIDCProviderMetadataContext> oidcProviderMetadataContextLookupStrategy;
+        
+    /** OIDC peer entity context to populate from. */
+    @Nullable private OIDCPeerEntityContext peerEntityCtx;
     
-    /** Strategy used to obtain the client id value for authorize/token request. */
-    @Nonnull private Function<MessageContext, String> issuerIDLookupStrategy;
+    /** Strategy used to look up the {@link SAMLPeerEntityContext} to draw from. */
+    @Nonnull private Function<ProfileRequestContext,OIDCPeerEntityContext> peerEntityContextLookupStrategy;
     
     /** OIDC issuer id of the downstream OP. */
     @Nullable private String issuerId;
@@ -69,20 +74,27 @@ public class InitializeRelyingPartyContext extends AbstractProfileAction {
     /** Constructor. */
     public InitializeRelyingPartyContext() {
         relyingPartyContextCreationStrategy = new ChildContextLookup<>(RelyingPartyContext.class, true);  
+        
         oidcProviderMetadataContextLookupStrategy = new ChildContextLookup<>(OIDCProviderMetadataContext.class).compose(
-                new InboundMessageContextLookup());   
-        issuerIDLookupStrategy = new DefaultIssuerIDLookupFunction();
+                new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
+                        new InboundMessageContextLookup()));  
+        
+        peerEntityContextLookupStrategy =
+                new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
+                        new InboundMessageContextLookup());
     }
     
     /**
-     * Set the strategy used to locate the issuer id of the request.
+     * Set the strategy used to look up the {@link OIDCPeerEntityContext} to draw from.
      * 
-     * @param strategy lookup strategy
+     * @param strategy strategy used to look up the {@link OIDCPeerEntityContext}
      */
-    public void setIssuerIDLookupStrategy(@Nonnull final Function<MessageContext, String> strategy) {
+    public void setPeerEntityContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext,OIDCPeerEntityContext> strategy) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        issuerIDLookupStrategy =
-                Constraint.isNotNull(strategy, "IssuerID lookup strategy cannot be null");
+
+        peerEntityContextLookupStrategy =
+                Constraint.isNotNull(strategy, "OIDCPeerEntityContext lookup strategy cannot be null");
     }
     
     /**
@@ -111,18 +123,21 @@ public class InitializeRelyingPartyContext extends AbstractProfileAction {
                 Constraint.isNotNull(strategy, "OIDCMetadataContext lookup strategy cannot be null");
     }
     
+    
     @Override
     protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
         if (!super.doPreExecute(profileRequestContext)) {
             log.error("{} pre-execute failed", getLogPrefix());
             return false;
         }
-        issuerId = issuerIDLookupStrategy.apply(profileRequestContext.getInboundMessageContext());
-        if (issuerId == null) {
-            log.error("{} Unable to locate issuer id from the request", getLogPrefix());
+
+        peerEntityCtx = peerEntityContextLookupStrategy.apply(profileRequestContext);
+        if (peerEntityCtx == null) {
+            log.warn("{} Unable to locate OIDCPeerEntityContext", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
             return false;
         }
+        
         return true;
     }
     
@@ -135,11 +150,13 @@ public class InitializeRelyingPartyContext extends AbstractProfileAction {
             ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CTX);
             return;
         }
-        log.debug("Attaching RelyingPartyContext for OP {}", issuerId);
-        rpContext.setRelyingPartyId(issuerId);
-        final OIDCProviderMetadataContext oidcContext = oidcProviderMetadataContextLookupStrategy.apply(profileRequestContext);
+        log.debug("Attaching RelyingPartyContext for OP {}", peerEntityCtx.getIdentifier());
+        rpContext.setRelyingPartyId(peerEntityCtx.getIdentifier());
+        rpContext.setRelyingPartyIdContextTree(peerEntityCtx);
+        final OIDCProviderMetadataContext oidcContext = 
+                oidcProviderMetadataContextLookupStrategy.apply(profileRequestContext);
         if (oidcContext != null && oidcContext.getProviderInformation() != null
-                && issuerId.equals(oidcContext.getProviderInformation().getIssuer().getValue())) {
+                && peerEntityCtx.getIdentifier().equals(oidcContext.getProviderInformation().getIssuer().getValue())) {
             log.debug("{} Setting the OP context to 'verified'", getLogPrefix());
             rpContext.setVerified(true);
         }
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCAuthenticationTimeRequested.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCAuthenticationTimeRequested.java
index 54089e1..f29bd12 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCAuthenticationTimeRequested.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCAuthenticationTimeRequested.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.function.Predicate;
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCContextAudienceClaimLookupStrategy.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCContextAudienceClaimLookupStrategy.java
index ed433e2..c9069cc 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCContextAudienceClaimLookupStrategy.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCContextAudienceClaimLookupStrategy.java
@@ -36,7 +36,8 @@ import net.shibboleth.idp.plugin.authn.oidc.rp.context.OpenIDConnectContext;
  * Returns null if it fails to find the clientID. Used for JWT ID Token audience claims verification.
  */
 @ThreadSafe
-public final class OIDCContextAudienceClaimLookupStrategy implements BiFunction<ProfileRequestContext, JWTClaimsSet, String> {
+public final class OIDCContextAudienceClaimLookupStrategy 
+                implements BiFunction<ProfileRequestContext, JWTClaimsSet, String> {
 
     @Override @Nullable public String apply(@Nonnull final ProfileRequestContext context,
             @Nonnull final JWTClaimsSet cliams) {
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCProxySupport.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCProxySupport.java
index c214c76..c888feb 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCProxySupport.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCProxySupport.java
@@ -1,17 +1,36 @@
+/*
+ * 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.security.SecureRandom;
 
 import javax.annotation.Nonnull;
 
+import org.apache.commons.codec.DecoderException;
 import org.apache.commons.codec.binary.Hex;
 
+import net.shibboleth.idp.plugin.authn.oidc.rp.OIDCProxyException;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 
 /**
  * Support class for OIDC proxy related implementations.
  */
-//TODO Duo has similar support class methods, maybe merge.
+//TODO Duo has similar support class methods, maybe merge into commons.
 public final class OIDCProxySupport {
     
     /** Private constructor.*/
@@ -57,5 +76,52 @@ public final class OIDCProxySupport {
         final String keyHex = Hex.encodeHexString(key.getBytes());
         return nonce+"."+keyHex;
     }
+    
+    /**
+     * Extract the key component from the state. The key is hex encoded and separated from the
+     * nonce value by a dot. 
+     * 
+     * @param state the state which contains both the nonce and the key dot separated.
+     * 
+     * @return the key extracted from the state and hex decoded.
+     * 
+     * @throws OIDCProxyException if the key component can not be found, or hex decoding fails.
+     */
+    @Nonnull static String extractKeyFromState(@Nonnull final String state) throws OIDCProxyException {
+        Constraint.isNotNull(state, "State can not be null");
+        
+        final String[] stateSplit = state.split("\\.");
+        if (stateSplit.length!=2) {
+            throw new OIDCProxyException("State does not contain the key component");
+        }
+        final String hexKey = stateSplit[1];
+        try {
+            //should we check it is sensible?
+            return new String(Hex.decodeHex(hexKey));
+        } catch (final DecoderException e) {
+            throw new OIDCProxyException("Can not hex decode key",e);
+        }
+        
+    }
+
+    /**
+     * Extract the nonce component from the state. The nonce is separated from the key
+     * by a dot, and is assumed to be the first value of the pair.
+     * 
+     * @param state the state which contains both the nonce and the key dot separated.
+     * 
+     * @return the nonce extracted from the state.
+     * 
+     * @throws DuoException if the nonce component can not be found.
+     */
+    @Nonnull static String extractNonceFromState(@Nonnull final String state) throws OIDCProxyException {
+        Constraint.isNotNull(state, "State can not be null");
+
+        final String[] stateSplit = state.split("\\.");
+        if (stateSplit.length!=2) {
+            throw new OIDCProxyException("State does not contain the nonce component");
+        }
+        return stateSplit[0];
+    }
 
 }
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/PopulateResponseTypeAndModeContext.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/PopulateResponseTypeAndModeContext.java
new file mode 100644
index 0000000..b8f4f99
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/PopulateResponseTypeAndModeContext.java
@@ -0,0 +1,119 @@
+/*
+ * 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.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.oauth2.sdk.ResponseMode;
+import com.nimbusds.oauth2.sdk.ResponseType;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.ResponseTypeAndModeContext;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Action that populates the outbound ResponseTypeContext from client metadata or 
+ * profile configuration...
+ * 
+ *  TODO finish
+ */
+public class PopulateResponseTypeAndModeContext extends AbstractProfileAction {
+    
+    /** The Default response type if none is selected.*/
+    @Nonnull private static final ResponseType DEFAULT_RESPONSE_TYPE = ResponseType.CODE;
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(PopulateResponseTypeAndModeContext.class);
+    
+    
+    /** Function to lookup the response type given the profile request context.*/
+    @Nonnull private Function<ProfileRequestContext, ResponseType> responseTypeLookup;
+    
+    /** Function to lookup the response mode given the profile request context.*/
+    @Nonnull private BiFunction<ProfileRequestContext, ResponseType, ResponseMode> responseModeLookup;
+    
+    /** Constructor.*/
+    public PopulateResponseTypeAndModeContext() {
+        responseTypeLookup = new DefaultResponseTypeLookupFunction();
+        responseModeLookup = new DefaultResponseModeLookupFunction();
+    }
+    
+    /**
+     * Set the response_type lookup function.
+     * 
+     * @param lookup the function.
+     */
+    public void setResponseTypeLookup(@Nonnull final Function<ProfileRequestContext, ResponseType> lookup) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+        responseTypeLookup = Constraint.isNotNull(lookup, "Response Type lookup can not be null");
+    }
+    
+    /**
+     * Set the response_mode lookup function. Accepts both a chosen response_mode, and the profile request
+     * context.
+     * 
+     * @param lookup the function.
+     */
+    public void setResponseModeLookup(
+            @Nonnull final BiFunction<ProfileRequestContext, ResponseType, ResponseMode> lookup) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+        responseModeLookup = Constraint.isNotNull(lookup, "Response Mode lookup can not be null");
+    }
+   
+    
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }
+        
+        // Fail fast here in case there is no inbound context to pull information from.
+        if (profileRequestContext.getInboundMessageContext() == null) {
+            log.debug("{} Inbound message context was null", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            return false;
+        }        
+        
+        return true;
+    }
+    
+    /** {@inheritDoc} */
+    @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        
+        final ResponseTypeAndModeContext ctx = new ResponseTypeAndModeContext();
+        ctx.setResponseType(responseTypeLookup.apply(profileRequestContext));
+        ctx.setResponseMode(responseModeLookup.apply(profileRequestContext, ctx.getResponseType()));
+        profileRequestContext.getOutboundMessageContext().addSubcontext(ctx);
+               
+        
+    }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/PrepareOIDCInboundMessageContext.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/PrepareOIDCInboundMessageContext.java
new file mode 100644
index 0000000..9c45c99
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/PrepareOIDCInboundMessageContext.java
@@ -0,0 +1,105 @@
+/*
+ * 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 org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Action that adds an inbound {@link MessageContext} and a {@link OIDCPeerEntityContext} to the
+ * {@link ProfileRequestContext} based on the identity of a relying party, by default from the 
+ * {@link AuthenticationContext#getAuthenticatingAuthority()}.
+ * 
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ */
+public class PrepareOIDCInboundMessageContext extends AbstractProfileAction {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(PrepareOIDCInboundMessageContext.class);
+    
+    /** Strategy to lookup the identifier of the OP/RP to based the inbound context on. */
+    @NonnullAfterInit private Function<ProfileRequestContext,String> identifierLookupStrategy;
+    
+    /** The identifier of the OP/RP to base the inbound context on. */
+    @Nullable private String identifier;
+    
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        if (identifierLookupStrategy == null) {
+            throw new ComponentInitializationException("IdentifierLookupStrategy cannot be null");
+        }
+    }
+    
+    /**
+     * Set the lookup strategy to identify the OP/RP.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setIdentiferLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        identifierLookupStrategy = Constraint.isNotNull(strategy, "Identifier lookup strategy can not be null");
+    }
+
+    @Override protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }        
+        
+        identifier = identifierLookupStrategy.apply(profileRequestContext);
+        if (identifier == null) {       
+            log.warn("{} No identifier returned from lookup function", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            return false;
+        }    
+        return true;
+    }
+    
+    @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+        final MessageContext msgCtx = new MessageContext();
+        profileRequestContext.setInboundMessageContext(msgCtx);
+
+        final OIDCPeerEntityContext peerContext = msgCtx.getSubcontext(OIDCPeerEntityContext.class, true);
+        peerContext.setIdentifier(identifier);
+
+        log.debug("{} Initialized inbound context for message to {}", getLogPrefix(), identifier);
+    }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/SetOIDCInformation.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/SetOIDCInformation.java
deleted file mode 100644
index 021e46a..0000000
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/SetOIDCInformation.java
+++ /dev/null
@@ -1,584 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.security.PrivateKey;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Function;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import net.minidev.json.JSONObject;
-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.messaging.context.navigate.ChildContextLookup;
-import org.opensaml.profile.action.ActionSupport;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.nimbusds.jose.crypto.RSASSASigner;
-import com.nimbusds.jwt.JWT;
-import com.nimbusds.jwt.JWTClaimsSet;
-import com.nimbusds.jwt.PlainJWT;
-import com.nimbusds.oauth2.sdk.ParseException;
-import com.nimbusds.oauth2.sdk.ResponseMode;
-import com.nimbusds.oauth2.sdk.ResponseType;
-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.AuthenticationRequest;
-import com.nimbusds.openid.connect.sdk.Display;
-import com.nimbusds.openid.connect.sdk.Nonce;
-import com.nimbusds.openid.connect.sdk.OIDCScopeValue;
-import com.nimbusds.openid.connect.sdk.Prompt;
-import com.nimbusds.openid.connect.sdk.Prompt.Type;
-import com.nimbusds.openid.connect.sdk.claims.ACR;
-import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
-import com.nimbusds.jose.JWSAlgorithm;
-import com.nimbusds.jose.JWSHeader;
-import com.nimbusds.jose.PlainHeader;
-import com.nimbusds.jwt.SignedJWT;
-
-import net.shibboleth.idp.attribute.IdPAttribute;
-import net.shibboleth.idp.attribute.IdPAttributeValue;
-import net.shibboleth.idp.attribute.StringAttributeValue;
-import net.shibboleth.idp.attribute.context.AttributeContext;
-import net.shibboleth.idp.profile.context.RelyingPartyContext;
-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.ComponentSupport;
-import net.shibboleth.utilities.java.support.logic.Constraint;
-import net.shibboleth.utilities.java.support.primitive.StringSupport;
-
-/**
- *
- * An action that populates the {@link AuthenticationContext} with a freshly built {@link OpenIDConnectContext}.
- *
- * <p>A singleton instance of this class can be created and shared between authentication requests.</p>
- *
- * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
- * @event {@link AuthnEventIds#NO_CREDENTIALS}
- * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class) != null</pre>
- * @post The AuthenticationContext is modified as above.
- *
- * @since 4.0.0
- */
- at SuppressWarnings("rawtypes")
-public class SetOIDCInformation extends AbstractAuthenticationAction {
-
-    /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(SetOIDCInformation.class);
-
-    /** Context to look attributes for. */
-    @Nonnull private Function<ProfileRequestContext, AttributeContext> attributeContextLookupStrategy;
-
-    /** Redirect URI. */
-    @Nonnull private URI redirectURI;
-
-    /** Client Id. */
-    @Nonnull private ClientID clientID;
-
-    /** Client Secret. */
-    @Nonnull private Secret clientSecret;
-
-    /** Response type, default is code flow. */
-    @Nonnull private ResponseType responseType = new ResponseType(ResponseType.Value.CODE);
-
-    /** Scope. Must contain an openid scope.*/
-    @Nonnull private Scope scope = new Scope(OIDCScopeValue.OPENID);
-
-    /**
-     *  OIDC Prompt. Specifies whether the Authorization Server prompts
-     *  the End-User for reauthentication 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;
-
-    /** Private key for signing the request object. */
-    @Nullable private PrivateKey signPrvKey;
-
-    /** Algorithm used for signing the request object. Defaults to the required
-     * singing algorithm of RSA SHA-256*/
-    @Nonnull private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.RS256;
-
-    /** key id for key used for signing the request object. */
-    @Nonnull private String keyID = "id";
-
-    /** Request object claims. */
-    @Nullable private Map<String, String> requestClaims;
-
-    /** OIDC provider metadata. */
-    @Nonnull private OIDCProviderMetadata oIDCProviderMetadata;
-
-    /** Constructor. */
-    public SetOIDCInformation() {
-        attributeContextLookupStrategy =
-                new ChildContextLookup<>(AttributeContext.class).compose(
-                        new ChildContextLookup<>(RelyingPartyContext.class));
-
-    }
-
-    /**
-     * Set the private key used for signing request object.
-     *
-     * @param key signing key
-     */
-    public void setPrivKey(@Nullable final PrivateKey key) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        signPrvKey = key;
-    }
-
-    /**
-     * Set the RSA algorithm used for signing the request object. Default is RS256.
-     *
-     * @param algorithm used for signing, must not be <code>null</code>.
-     */
-    public void setJwsAlgorithm(@Nonnull final JWSAlgorithm algorithm) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        jwsAlgorithm = Constraint.isNotNull(algorithm,"OpenID Connect JWS Request algorithm can not be null");
-
-    }
-
-    /**
-     * Set the key id of the key.
-     *
-     * @param id for the key, must not be <code>null</code>.
-     */
-    public void setKeyID(@Nonnull @NotEmpty final String id) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        keyID =Constraint.isNotNull(StringSupport.trimOrNull(id), "OpenID Connect key ID cannot be null");
-    }
-
-    /**
-     *
-     * If this is set (i.e. not <code>null</code>) request claims will be built as a
-     * JWT Request Object. See OpenID Connect Core section 6.1. If the <code>signPrvKey</code>
-     * is set, the JWT will also be signed.
-     *
-     * <p>The key of the mapping is the name of the requested claim. The value of the requested claim
-     * is either:</p>
-     * <ul>
-     * <li>Null, if the claim is being requested in a default manor</li>
-     * <li><code>{"essential":true}</code> if the value is essential. The default is
-     * <code>{"essential":false}</code></li>
-     * <li>A specific value for the claim. This value must be a valid value for that claim.</li>
-     * <li>A specific set of values for the claim. These values must be valid value for that claim.</li>
-     * </ul>
-     * 
-     * <p>
-     * If used, the OpenID Connect Provider must support it, as specified in the
-     * <code>request_parameter_supported</code> parameter of the Providers discovery metadata.
-     * </p>
-     *
-     * @param claims map of requested claims
-     */
-    public void setRequestClaims(@Nullable final Map<String, String> claims) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        requestClaims = claims;
-    }
-
-    /**
-     * Sets the response type. Default is code.
-     *
-     * @param type space-delimited list of one or more authorization response types. Must not be <code>null</code>.
-     * @throws ParseException if response type cannot be parsed
-     */
-    public void setResponseType(@Nonnull @NotEmpty final String type) throws ParseException {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        Constraint.isNotNull(StringSupport.trimOrNull(type), "OpenID Connect response type cannot be null or empty");
-
-        responseType = ResponseType.parse(type);
-    }
-
-    /**
-     * Setter for Oauth2 client id.
-     *
-     * @param oauth2ClientID Oauth2 Client ID, must not be <code>null</code>.
-     */
-    public void setClientID(@Nonnull @NotEmpty final String oauth2ClientID) {
-
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        Constraint.isNotNull(StringSupport.trimOrNull(oauth2ClientID),
-                "OpenID Connect client ID cannot be null or empty");
-
-        clientID = new ClientID(oauth2ClientID);
-    }
-
-    /**
-     * Setter for OAuth2 Client secret.
-     *
-     * @param oauth2ClientSecret OAuth2 Client Secret, must not be <code>null</code>.
-     */
-    public void setClientSecret(@Nonnull @NotEmpty final String oauth2ClientSecret) {
-
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        Constraint.isNotNull(StringSupport.trimOrNull(oauth2ClientSecret),
-                "OpenID Connect client secret cannot be null or empty");
-
-        clientSecret = new Secret(oauth2ClientSecret);
-    }
-
-    /**
-     * Setter for the OAuth 2.0 redirect URI the OIDC provider will return to. The constructed URI should not be empty.
-     *
-     * @param redirect OAuth2 redirect uri, must not be <code>null</code>.
-     */
-
-    public void setRedirectURI(@Nonnull @NotEmpty final URI redirect) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        Constraint.isNotNull(redirect, "OpenID Connect redirect URI cannot be null");
-        Constraint.isNotEmpty(redirect.toString(), "OpenID Connect redirect URI cannot be empty");
-
-        redirectURI = redirect;
-
-    }
-
-    /**
-     * Setter for OpenId Provider Metadata resource.
-     *
-     * <p>Constructs a URI from the <code>metadataLocation</code> and attempts to connect,
-     * stream, and parse its content into an {@link OIDCProviderMetadata} instance.</p>
-     *
-     * @param metadataLocation OpenId Provider Metadata location, must not be <code>null</code>.
-     *
-     * @throws URISyntaxException if metadataLocation is not a URI
-     * @throws IOException if metadataLocation cannot be read
-     * @throws ParseException if metadataLocation has wrong content
-     */
-    //TODO: not like this? HttpClient, metadata service etc.
-    public void setProviderMetadataLocation(@Nonnull @NotEmpty final String metadataLocation)
-            throws URISyntaxException, IOException, ParseException {
-
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        Constraint.isNotNull(StringSupport.trimOrNull(metadataLocation),
-                "OpenID Connect metadata location cannot be null or empty");
-
-        final URI issuerURI = new URI(metadataLocation);
-        final URL providerConfigurationURL = issuerURI.resolve(".well-known/openid-configuration").toURL();
-        final InputStream stream = providerConfigurationURL.openStream();
-        String providerInfo = null;
-        try (java.util.Scanner s = new java.util.Scanner(stream)) {
-            providerInfo = s.useDelimiter("\\A").hasNext() ? s.next() : "";
-        }
-        oIDCProviderMetadata = OIDCProviderMetadata.parse(providerInfo);
-
-    }
-
-    /**
-     * Setter for OpenId Scope values. New ones are be added to the {@code OIDCScopeValue#OPENID} scope.
-     *
-     * @param oidcScopes OpenID Connect Scope values, can be <code>null</code> and will be ignored.
-     */
-    public void setScope(@Nullable final List<String> oidcScopes) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
-        //As the OPENID scope is always set. New scopes can be null.
-        if (oidcScopes==null) {
-            return;
-        }
-
-        for (final String oidcScope : oidcScopes) {
-            switch (oidcScope.toUpperCase()) {
-                case "ADDRESS":
-                    scope.add(OIDCScopeValue.ADDRESS);
-                    break;
-                case "EMAIL":
-                    scope.add(OIDCScopeValue.EMAIL);
-                    break;
-                case "OFFLINE_ACCESS":
-                    scope.add(OIDCScopeValue.OFFLINE_ACCESS);
-                    break;
-                case "PHONE":
-                    scope.add(OIDCScopeValue.PHONE);
-                    break;
-                case "PROFILE":
-                    scope.add(OIDCScopeValue.PROFILE);
-                    break;
-                default:
-            }
-        }
-
-    }
-
-    /**
-     * Setter for the OpenID Connect Prompt value.
-     *
-     * @param oidcPrompt  OpenID Connect Prompt value.
-     */
-    public void setPrompt(@Nullable final String oidcPrompt) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
-        prompt = new Prompt(oidcPrompt);
-    }
-
-    /**
-     * Setter for the OpenID Connect Authentication Context Class Reference (ACR) values.
-     *
-     * @param oidcAcrs OpenId ACR values
-     */
-    public void setAcr(@Nullable @NonnullElements final List<String> oidcAcrs) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
-        if (null != oidcAcrs) {
-            for (final String oidcAcr : oidcAcrs) {
-                final ACR acr = new ACR(oidcAcr);
-
-                if (acrs == null) {
-                    acrs = new ArrayList<ACR>();
-                }
-                if (acr!=null) {
-                    acrs.add(acr);
-                }
-            }
-        }
-
-    }
-
-    /**
-     * Setter for the OpenID Connect Display value.
-     *
-     * @param oidcDisplay OpenID Connect Display value.
-     */
-    public void setDisplay(@Nullable final String oidcDisplay) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
-        if (null != display) {
-             try {
-                 display = Display.parse(oidcDisplay);
-             } catch (final ParseException e) {
-                 log.error("{} Could not set display value",getLogPrefix(), e);
-             }
-        }
-
-    }
-
-    /**
-     * Set the lookup strategy for the {@link AttributeContext}.
-     *
-     * @param strategy lookup strategy
-     */
-    public void setAttributeContextLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext, AttributeContext> strategy) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
-        attributeContextLookupStrategy =
-                Constraint.isNotNull(strategy, "AttributeContext lookup strategy cannot be null");
-
-    }
-
-    /**
-     * Returns the first {@link StringAttributeValue} value of the {@link IdPAttribute} that has key
-     * <code>name</code> from the {@link OpenIDConnectContext#getResolvedIdPAttributes()}.
-     *
-     * @param oidcCtx context to get attributes from.
-     * @param name of the attribute to find.
-     * @return attribute value if found, null otherwise.
-     */
-    @Nullable private String attributeToString(@Nonnull final OpenIDConnectContext oidcCtx,
-            @Nullable final String name) {
-
-        if (oidcCtx.getResolvedIdPAttributes() == null) {
-            log.warn("Attribute context not available");
-            return null;
-        }
-        final IdPAttribute attribute = oidcCtx.getResolvedIdPAttributes().get(name);
-        if (attribute == null || attribute.getValues().size() == 0) {
-            log.debug("attribute " + name + " not found or has no values");
-            return null;
-        }
-        for (final IdPAttributeValue attrValue : attribute.getValues()) {
-            if (attrValue instanceof StringAttributeValue) {
-                // We set the value
-                return attrValue.getDisplayValue();
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Build the requested claims into an JSONObject, used by
-     * {@link #getRequestObject(OpenIDConnectContext, State)}.
-     *
-     * @param oidcCtx context to extract request claims from.
-     * @return id token as a JSON Object.
-     */
-    @Nonnull private JSONObject buildIDToken(@Nonnull final OpenIDConnectContext oidcCtx) {
-
-        final JSONObject idToken = new JSONObject();
-        for (final Map.Entry<String, String> entry : requestClaims.entrySet()) {
-            final String value = entry.getValue();
-            final String claim = entry.getKey();
-            if (value == null) {
-                // 1. null value
-                log.debug("Setting claim " + claim + " to null");
-                idToken.put(entry.getKey(), value);
-                continue;
-            }
-            log.debug("locating attribute for " + value);
-            final String attrValue = attributeToString(oidcCtx, value);
-            if (attrValue != null) {
-                // 2. attribute value
-                log.debug("Setting claim " + claim + " to value " + attrValue);
-                idToken.put(claim, attrValue);
-                continue;
-            }
-            if ("essential".equals(value)) {
-                // 3. essential value
-                final JSONObject obj = new JSONObject();
-                obj.put("essential", true);
-                log.debug("Setting claim " + claim + " to value " + obj.toJSONString());
-                idToken.put(claim, obj);
-                continue;
-            }
-            // 4. string value
-            log.debug("Setting claim " + claim + " to value " + value);
-            idToken.put(claim, value);
-        }
-
-        return idToken;
-    }
-
-    /**
-     *
-     * Construct a JWT claims Request Object (see OpenID Connect core 1.0 section 6) iff claims
-     * are requested i.e. <code>requestClaims</code> is not <code>null</code>.
-     *
-     * <p>If the signing key is present, adds also state, iat claims and then signs it.</p>
-     *
-     * <p>Must be called as a last step before constructing the request.</p>
-     *
-     *
-     * @param oidcCtx context for accessing attributes.
-     * @param state to be added to the request object.
-     * @return the request object, or null if one is not constructed.
-     *
-     * @throws Exception if attribute context is not available or parsing/signing fails.
-     */
-    @Nullable private JWT getRequestObject(@Nonnull final OpenIDConnectContext oidcCtx, @Nonnull final State state)
-            throws Exception {
-
-
-        if (requestClaims == null || requestClaims.size() == 0) {
-            return null;
-        }
-        final JSONObject request = new JSONObject();
-        request.put("client_id", clientID.getValue());
-        request.put("response_type", responseType.toString());
-        if (signPrvKey != null) {
-            request.put("iss", clientID.getValue());
-            request.put("aud", oIDCProviderMetadata.getIssuer().getValue());
-            // If we sign we add also iat and state.
-            request.put("state", state.getValue());
-            request.put("iat", TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()));
-        }
-        // Build the id token as instructed.
-        final JSONObject idToken = buildIDToken(oidcCtx);
-        final JSONObject claims = new JSONObject();
-        claims.put("id_token", idToken);
-        request.put("claims", claims);
-        log.debug("Request object without signature "+getLogPrefix() + request.toJSONString());
-        final JWTClaimsSet claimsRequest = JWTClaimsSet.parse(request);
-        JWT requestObject = null;
-        if (signPrvKey != null) {
-            requestObject = new SignedJWT(new JWSHeader.Builder(jwsAlgorithm).keyID(keyID).build(), claimsRequest);
-            ((SignedJWT) requestObject).sign(new RSASSASigner(signPrvKey));
-            log.debug("created request object: " + requestObject.getParsedString());
-        } else {
-            requestObject = new PlainJWT(new PlainHeader(), claimsRequest);
-        }
-        return requestObject;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final AuthenticationContext authenticationContext) {
-
-
-        final OpenIDConnectContext oidcCtx =
-                authenticationContext.getSubcontext(OpenIDConnectContext.class, true);
-
-        // Initialize the context, if request is passive we override default prompt value
-        final Prompt ovrPrompt = authenticationContext.isPassive() ? new Prompt(Type.NONE) : prompt;
-        oidcCtx.setPrompt(ovrPrompt);
-        oidcCtx.setAcrs(acrs);
-        oidcCtx.setClientID(clientID);
-        oidcCtx.setClientSecret(clientSecret);
-        oidcCtx.setDisplay(display);
-        oidcCtx.setoIDCProviderMetadata(oIDCProviderMetadata);
-        oidcCtx.setRedirectURI(redirectURI);
-        final State state = new State();
-        oidcCtx.setState(state);
-        final Nonce nonce = new Nonce();
-        oidcCtx.setNonce(nonce);
-
-        JWT requestObject = null;
-        try {
-            // must be called as a last step
-            requestObject = getRequestObject(oidcCtx, state);
-        } catch (final Exception e) {
-            // TODO: better error id
-            log.error("{} unable to create request object", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
-
-            return;
-        }
-        if (authenticationContext.isForceAuthn()) {
-            // We set max age to 0 if forcedauth is set
-            // TODO: Currently the underlying library doesn't accept value 0, so
-            // we set it to 1
-            final int maxAge = 1;
-            oidcCtx.setAuthenticationRequestURI(
-                    new AuthenticationRequest.Builder(responseType, scope, clientID, redirectURI)
-                            .endpointURI(oIDCProviderMetadata.getAuthorizationEndpointURI()).display(display)
-                            .acrValues(acrs).requestObject(requestObject).responseMode(ResponseMode.QUERY)
-                            .maxAge(maxAge).prompt(ovrPrompt).state(state).nonce(nonce).build().toURI());
-        } else {
-            oidcCtx.setAuthenticationRequestURI(
-                    new AuthenticationRequest.Builder(responseType, scope, clientID, redirectURI)
-                            .endpointURI(oIDCProviderMetadata.getAuthorizationEndpointURI()).display(display)
-                            .acrValues(acrs).requestObject(requestObject).responseMode(ResponseMode.QUERY)
-                            .prompt(ovrPrompt).state(state).nonce(nonce).build().toURI());
-        }
-
-        return;
-    }
-
-}
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
new file mode 100644
index 0000000..5edd244
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TokenResponseIDTokenLookupStrategy.java
@@ -0,0 +1,54 @@
+/*
+ * 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 org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.TokenResponseContext;
+
+/** Return the base64 encoded id_token from the TokenResponseContext, or null if not found.*/
+public class TokenResponseIDTokenLookupStrategy implements Function<ProfileRequestContext, String> {
+
+    @Override
+    @Nullable public String apply(@Nonnull final ProfileRequestContext profileRequestContext) {
+        
+        final MessageContext inbound = profileRequestContext.getInboundMessageContext();
+        if (inbound == null) {
+            return null;
+        }
+        final TokenResponseContext tokenResponseContext = inbound.getSubcontext(TokenResponseContext.class);
+        if (tokenResponseContext == null) {
+            return null;
+        }
+        if (tokenResponseContext.getRawTokenResponse() != null) {
+            final Object idTokenObject =  tokenResponseContext.getRawTokenResponse().get("id_token");
+            if (idTokenObject instanceof String) {
+                return (String) idTokenObject;
+            }
+        }
+        return null;
+        
+    }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateAuthenticationResponseResult.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateAuthenticationResponseResult.java
new file mode 100644
index 0000000..72e5cce
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateAuthenticationResponseResult.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 javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.openid.connect.sdk.AuthenticationErrorResponse;
+import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
+
+import net.shibboleth.idp.authn.AbstractAuthenticationAction;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+
+/**
+ * An action that checks the inbound authentication response indicated success, as opposed to an error.
+ *  
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds.INVALID_MSG_CTX}
+ * @event {@link EventIds.MESSAGE_PROC_ERROR}
+ */
+public class ValidateAuthenticationResponseResult extends AbstractAuthenticationAction {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateAuthenticationResponseResult.class);
+
+    /** Context containing the result to examine. */
+    @Nullable private AuthenticationResponse authenticationResponse;
+    
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final AuthenticationContext authenticationContext) {        
+    
+        final MessageContext inMsgContext = profileRequestContext.getInboundMessageContext();
+        if (inMsgContext == null) {
+            log.debug("{} Inbound message context was null", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            return false;
+        }
+       
+        if (inMsgContext.getMessage() instanceof AuthenticationResponse) {
+            authenticationResponse = (AuthenticationResponse) inMsgContext.getMessage();
+        } else {
+            log.debug("{} Inbound message was not an authentication response", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            return false;
+        }        
+        return true;
+    }
+
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final AuthenticationContext authenticationContext) {
+       
+        if (!authenticationResponse.indicatesSuccess()) {
+            final AuthenticationErrorResponse error = authenticationResponse.toErrorResponse();
+            log.error("{} OIDC Authentication Response contained an error from upstream OP '{}' : {}", 
+                    getLogPrefix(), authenticationContext.getAuthenticatingAuthority(), error.getErrorObject());
+            // TODO: maybe more specific error, and or an error branch in the flow e.g. maybe a UI element?
+            ActionSupport.buildEvent(profileRequestContext, EventIds.MESSAGE_PROC_ERROR);
+        } else {
+            log.debug("{} Downstream OP signalled a successful response", getLogPrefix());
+        }
+       
+    }
+    
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateExternalAuthenticationContext.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateExternalAuthenticationContext.java
new file mode 100644
index 0000000..fbee66d
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateExternalAuthenticationContext.java
@@ -0,0 +1,84 @@
+/*
+ * 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 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.authn.context.ExternalAuthenticationContext;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An action that checks for an {@link ExternalAuthenticationContext} for a signaled event via the
+ * {@link ExternalAuthenticationContext#getAuthnError()} method.
+ *  
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link AuthnEventIds#INVALID_AUTHN_CTX}
+ * @event various
+ */
+public class ValidateExternalAuthenticationContext extends AbstractAuthenticationAction {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateExternalAuthenticationContext.class);
+
+    /** Context containing the result to examine. */
+    @Nullable private ExternalAuthenticationContext extContext;
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final AuthenticationContext authenticationContext) {
+        
+        if (!super.doPreExecute(profileRequestContext, authenticationContext)) {
+            return false;
+        }
+        
+        extContext = authenticationContext.getSubcontext(ExternalAuthenticationContext.class);
+        if (extContext == null) {
+            log.debug("{} No ExternalAuthenticationContext available within authentication context", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_AUTHN_CTX);
+            return false;
+        }
+        
+        return true;
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final AuthenticationContext authenticationContext) {
+
+        if (extContext.getAuthnError() != null) {
+            log.warn("{} OIDC Proxy authentication attempt signaled an error: {}", getLogPrefix(),
+                    extContext.getAuthnError());
+            ActionSupport.buildEvent(profileRequestContext, extContext.getAuthnError());
+        } else {
+            log.trace("{} Continuing with OIDC Proxy authentication attempt", getLogPrefix());
+        }
+       
+    }
+    
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenACR.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenACR.java
deleted file mode 100644
index f64e36e..0000000
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenACR.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
-
-import java.text.ParseException;
-import java.util.List;
-
-import javax.annotation.Nonnull;
-
-import org.opensaml.profile.action.ActionSupport;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.nimbusds.openid.connect.sdk.claims.ACR;
-
-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 net.shibboleth.utilities.java.support.primitive.StringSupport;
-
-/**
- * An action that verifies the Authentication Context Class Reference (ACR) values contained within the 
- * id_token matches those requested.
- * 
- * <p>ACR claims are optional</p>
- * 
- * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class, false) != null</pre>
- * @pre <pre>AuthenticationContext.getSubcontext(OpenIDConnectContext.class, false) != null</pre>
- * @event {@link net.shibboleth.idp.authn.AuthnEventIds#NO_CREDENTIALS}
- * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
- * 
- * @since 4.0.0
- */
-//TODO P.S should some of these functions be delegated to Nimbus IDTokenValidator? 
-public class ValidateIDTokenACR extends AbstractAuthenticationAction {
-
-    /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateIDTokenACR.class);
-
-    /** {@inheritDoc} */
-    @Override
-    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;
-        }
-
-        
-        final List<ACR> acrs = oidcCtx.getAcrs();
-        if (acrs != null && !acrs.isEmpty()) {
-            if (log.isTraceEnabled()) {
-                for (int i = 0; i < acrs.size(); i++) {
-                    log.trace("{} ACR index {} is {}", getLogPrefix(), i, acrs.get(i));
-                }
-            }
-            final String acr;
-            try {
-                //TODO P.S. this could be null.
-                acr = oidcCtx.getIDToken().getJWTClaimsSet().getStringClaim("acr");
-            } catch (final ParseException e) {
-                log.error("{} Error parsing id token", getLogPrefix());
-                ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);                
-                return;
-            }
-            if (StringSupport.trimOrNull(acr) == null) {
-                log.error("{} acr requested but not received", getLogPrefix());
-                ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);                
-                return;
-            }
-            if (!acrs.contains(new ACR(acr))) {
-                log.error("{} acr received does not match requested:" + acr, getLogPrefix());
-                ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);                
-                return;
-            }
-        }
-        
-        return;
-    }
-}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenAuthorizedParty.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenAuthorizedParty.java
deleted file mode 100644
index 1f2cbb3..0000000
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenAuthorizedParty.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
-
-import java.text.ParseException;
-
-import javax.annotation.Nonnull;
-
-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;
-
-/**
- * An action that verifies the Authorized Party (azp) of an id_token. That is, the party to which
- * the id_token was issued, or the presenter of the id_token. As the IdP will always be responsible
- * for presenting the id_token, this value should always be the same client_id as the sole audience (aud).
- * As such, azp is not strictly required, but is checked if present. 
- * 
- * <p>The authorized party is one or more case sensitive Strings or URIs.</p>
- * 
- * <p>See section 3.1.3.7 of the OpenID Connect core 1.0</p>
- * 
- * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class, false) != null</pre>
- * @pre <pre>AuthenticationContext.getSubcontext(OpenIDConnectContext.class, false) != null</pre>
- * @pre <pre>OpenIDConnectContext.getIDToken() != null</pre>
- * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
- * @event {@link AuthnEventIds#NO_CREDENTIALS}
- * 
- * @since 4.0.0
- */
-//Issues of semantics exist here e.g. https://bitbucket.org/openid/connect/issues/973/
-//TODO the code does not match the spec?
-public class ValidateIDTokenAuthorizedParty extends AbstractAuthenticationAction {
-
-    /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateIDTokenAuthorizedParty.class);
-
-    /** {@inheritDoc} */
-    @Override
-    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;
-        }
-
-        try {
-            if (oidcCtx.getIDToken().getJWTClaimsSet().getAudience().size() > 1) {
-                final String azp = oidcCtx.getIDToken().getJWTClaimsSet().getStringClaim("azp");
-                if (!oidcCtx.getClientID().getValue().equals(azp)) {
-                    log.error("{} multiple audiences, client is not the azp", getLogPrefix());
-                    ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);                   
-                    return;
-                }
-            }
-        } catch (final ParseException e) {
-            log.error("{} Error parsing id token", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);            
-            return;
-        }       
-        return;
-    }
-
-}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthenticationResponse.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthenticationResponse.java
deleted file mode 100644
index 01c9603..0000000
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthenticationResponse.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
-
-import javax.annotation.Nonnull;
-
-import net.shibboleth.idp.authn.AbstractExtractionAction;
-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.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.oauth2.sdk.ParseException;
-import com.nimbusds.oauth2.sdk.id.State;
-import com.nimbusds.openid.connect.sdk.AuthenticationErrorResponse;
-import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
-import com.nimbusds.openid.connect.sdk.AuthenticationResponseParser;
-import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
-
-/**
- * An action that extracts the id_token from the query parameters of a successful response from
- * an OpenID Connect provider. The id_token is attached to the {@link OpenIDConnectContext}.  
- *  
- * <p>Also checks the state returned by the provider matches that which was sent to the provider</p>
- * 
- * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class, false) != null</pre>
- * @pre <pre>AuthenticationContext.getSubcontext(OpenIDConnectContext.class, false) != null</pre>
- * @pre <pre>OpenIdConnectContext.getAuthenticationResponseURI() != null</pre>
- * @post If {@link AuthenticationResponse#indicatesSuccess()} == true, the extracted id_token
- * is extracted and placed into the existing {@link OpenIDConnectContext}.
- * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
- * @event {@link AuthnEventIds#NO_CREDENTIALS}
- * 
- * @since 4.0.0
- */
-public class ValidateOIDCAuthenticationResponse extends AbstractExtractionAction {
-
-    /*
-     * A DRAFT PROTO CLASS!! NOT TO BE USED YET.
-     * 
-     * FINAL GOAL IS TO MOVE FROM CURRENT OIDC TO MORE WEBFLOW LIKE IMPLEMENTATION.
-     */
-
-    /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateOIDCAuthenticationResponse.class);
-
-    /** {@inheritDoc} */
-    @Override
-    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final AuthenticationContext authenticationContext) {
-        
-
-        final OpenIDConnectContext oidcCtx =
-                authenticationContext.getSubcontext(OpenIDConnectContext.class);
-        if (oidcCtx == null) {
-            log.info("{} Unable to find oidc context", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);            
-            return;
-        }
-
-        if (oidcCtx.getAuthenticationResponseURI() == null) {
-            log.info("{} Response URI not set", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);            
-            return;
-        }
-        log.debug("Validating OIDC response {}", oidcCtx.getAuthenticationResponseURI().toString());
-        AuthenticationResponse response = null;
-        try {
-            response = AuthenticationResponseParser.parse(oidcCtx.getAuthenticationResponseURI());
-        } catch (final ParseException e) {
-            log.info("{} Response parsing failed", getLogPrefix(),e);
-            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);            
-            return;
-        }
-        if (!response.indicatesSuccess()) {
-            
-            final AuthenticationErrorResponse errorResponse = (AuthenticationErrorResponse) response;
-            String error = errorResponse.getErrorObject().getCode();
-            final String errorDescription = errorResponse.getErrorObject().getDescription();
-            if (StringSupport.trimOrNull(errorDescription) != null) {
-                error += " : " + errorDescription;
-            }
-           
-            log.info("{} response indicated error: {}", getLogPrefix(), error);
-            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);            
-            return;
-        }
-        final AuthenticationSuccessResponse successResponse = (AuthenticationSuccessResponse) response;
-        // implicit and hybrid flows return id token in response.
-        oidcCtx.setIDToken(successResponse.getIDToken());
-        final State state = oidcCtx.getState();
-        if (state == null || !state.equals(successResponse.getState())) {
-            log.info("{} OIDC flow state mismatch:", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
-            
-        }
-
-        oidcCtx.setAuthenticationSuccessResponse(successResponse);
-        
-        return;
-    }
-
-}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateResponseState.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateResponseState.java
new file mode 100644
index 0000000..1af7cf3
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateResponseState.java
@@ -0,0 +1,81 @@
+/*
+ * 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 javax.annotation.Nonnull;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+
+/**
+ * Authentication action that validates the authentication response state parameter 
+ * (which is <b>required</b> in the Proxy flow) matches that in the authentication request.
+ * 
+ * <p>Note, the state parameter could be an encoded format with more than one component. Even if so,
+ * the entire state value between request and response is matched. There is no need to decompose it.</p>
+ * 
+ * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
+ * @event {@link AuthnEventIds#NO_CREDENTIALS}
+ * FIXME: condiditions
+ * @pre <pre>
+ *      ProfileRequestContext.getSubcontext(AuthenticationContext.class, false) != null
+ *      </pre>
+ * 
+ * @pre <pre>
+ *      AuthenticationContext.getSubcontext(DuoOIDCAuthenticationContext.class, false) != null
+ *      </pre>
+ * 
+ */
+public class ValidateResponseState extends AbstractOIDCAuthenticationAction {    
+    
+    /** Class logger. */    
+    @Nonnull @NotEmpty private final Logger log = LoggerFactory.getLogger(ValidateResponseState.class);          
+    
+    /** {@inheritDoc} */
+    @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final AuthenticationContext authenticationContext) {
+        
+        log.trace("{} Proxy authentication request state '{}' was returned in the response as '{}'",
+                getLogPrefix(),getAuthenticationRequest().getState(),getAuthenticationResponse().getState());
+        
+        if (getAuthenticationRequest().getState() == null || getAuthenticationResponse().getState() == null) {
+            log.error("{} The state parameter was not present in either the request or response, "
+                    + "state is mandatory for proxy OIDC requests",getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+            return;
+        }
+        if (!getAuthenticationRequest().getState().equals(getAuthenticationResponse().getState())) {
+            log.error("{} Request state did not match response state, has it been tampered with!",
+                    getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+            return;
+        } 
+        // else state is fine.
+        log.debug("{} OIDC request and response state match, continuing",getLogPrefix());       
+        
+    }
+    
+
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/DefaultClientIDForIssuerLookupFunction.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/DefaultClientIDForIssuerLookupFunction.java
new file mode 100644
index 0000000..7ff6061
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/DefaultClientIDForIssuerLookupFunction.java
@@ -0,0 +1,59 @@
+package net.shibboleth.idp.plugin.authn.oidc.rp.metadata.impl;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.ContextDataLookupFunction;
+
+import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.oauth2.sdk.id.Issuer;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCProviderMetadataContext;
+import net.shibboleth.utilities.java.support.annotation.ParameterName;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+
+
+/**
+ * Strategy to pull out the ID of the OIDC Client registered with to talk to the discovered/configured OP.
+ */
+public class DefaultClientIDForIssuerLookupFunction implements ContextDataLookupFunction<MessageContext, ClientID> {
+    
+    /** A mapping between an OP Issuer and an RP Client.*/
+    @Nonnull @NonnullElements private final Map<Issuer, ClientID> issuerToClientMap;
+    
+    /**
+     * 
+     * Constructor.
+     *
+     * @param map the issuer to client lookup map.
+     */
+    public DefaultClientIDForIssuerLookupFunction(
+            @Nullable @ParameterName(name = "issuerToClientMap") final Map<String, String> map) {
+        if (map == null) {
+            issuerToClientMap = Collections.emptyMap();
+        } else {
+            issuerToClientMap = new HashMap<>(map.size());
+            for (Map.Entry<String, String> entry : map.entrySet()) {
+                issuerToClientMap.put(new Issuer(entry.getKey()), new ClientID(entry.getValue()));
+            }            
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Nullable public ClientID apply(@Nullable final MessageContext input) {
+        if (input == null) {
+            return null;
+        }
+        final OIDCProviderMetadataContext provider = input.getSubcontext(OIDCProviderMetadataContext.class);
+        if (provider == null || provider.getProviderInformation() == null) {
+            return null;
+        }
+        return issuerToClientMap.get(provider.getProviderInformation().getIssuer());
+    }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/DefaultIssuerIDLookupFunction.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/DefaultIssuerIDLookupFunction.java
index 8791c87..4e9bed0 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/DefaultIssuerIDLookupFunction.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/DefaultIssuerIDLookupFunction.java
@@ -5,13 +5,12 @@ import javax.annotation.concurrent.ThreadSafe;
 
 import org.opensaml.messaging.context.MessageContext;
 import org.opensaml.messaging.context.navigate.ContextDataLookupFunction;
-import org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
 
 /**
- * Strategy to pull out the ID of the OIDC proxy issuer/provider.
+ * Strategy to pull out the ID of the OIDC proxy issuer/provider from the {@link OIDCPeerEntityContext}.
  */
-//TODO is this where the OP ID should be? confusing
-//TODO should resolve a Nimbus issuer?
 @ThreadSafe
 public class DefaultIssuerIDLookupFunction implements ContextDataLookupFunction<MessageContext, String> {
 
@@ -21,10 +20,10 @@ public class DefaultIssuerIDLookupFunction implements ContextDataLookupFunction<
             return null;
         }
         
-        final SAMLPeerEntityContext context = input.getSubcontext(SAMLPeerEntityContext.class);
+        final OIDCPeerEntityContext context = input.getSubcontext(OIDCPeerEntityContext.class);
         if (context == null) {
             return null;
         }
-        return context.getEntityId();
+        return context.getIdentifier();
     }
 }
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/OIDCMetadataLookupHandler.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/OIDCClientMetadataLookupHandler.java
similarity index 90%
rename from idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/OIDCMetadataLookupHandler.java
rename to idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/OIDCClientMetadataLookupHandler.java
index aff7fdc..fcbccc7 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/OIDCMetadataLookupHandler.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/OIDCClientMetadataLookupHandler.java
@@ -30,7 +30,7 @@ import org.slf4j.LoggerFactory;
 import com.nimbusds.oauth2.sdk.id.Issuer;
 import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
 
-import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCMetadataContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCClientMetadataContext;
 import net.shibboleth.oidc.metadata.ClientInformationResolver;
 import net.shibboleth.oidc.metadata.criterion.IssuerIDCriterion;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
@@ -42,12 +42,12 @@ import net.shibboleth.utilities.java.support.resolver.ResolverException;
 
 /**
  * Handler for inbound OIDC protocol messages that attempts to locate OIDC metadata for a RP, and attaches it with a
- * {@link OIDCMetadataContext} as a child of a pre-existing instance of {@link MessageContext}.
+ * {@link OIDCClientMetadataContext} as a child of a pre-existing instance of {@link MessageContext}.
  */
-public class OIDCMetadataLookupHandler extends AbstractMessageHandler {
+public class OIDCClientMetadataLookupHandler extends AbstractMessageHandler {
 
     /** Logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(OIDCMetadataLookupHandler.class);
+    @Nonnull private final Logger log = LoggerFactory.getLogger(OIDCClientMetadataLookupHandler.class);
 
     /** Resolver used to look up OIDC client information. */
     @NonnullAfterInit private ClientInformationResolver clientResolver;
@@ -59,7 +59,7 @@ public class OIDCMetadataLookupHandler extends AbstractMessageHandler {
     /**
      * Constructor.
      */
-    public OIDCMetadataLookupHandler() {
+    public OIDCClientMetadataLookupHandler() {
         issuerLookupStrategy = new DefaultIssuerIDLookupFunction();
     }
 
@@ -116,13 +116,13 @@ public class OIDCMetadataLookupHandler extends AbstractMessageHandler {
                 return;
             }
             log.debug("{} Resolved client information for client '{}'", getLogPrefix(), clientInformation.getID());
-            final OIDCMetadataContext oidcCtx = new OIDCMetadataContext();
+            final OIDCClientMetadataContext oidcCtx = new OIDCClientMetadataContext();
             oidcCtx.setClientInformation(clientInformation);
             messageContext.addSubcontext(oidcCtx);
             // Based on that info we know 1) client is valid 2) we know valid
             // redirect uris
             log.debug("{} {} added to MessageContext as child of {}", getLogPrefix(),
-                    OIDCMetadataContext.class.getName(), messageContext.getClass().getName());
+                    OIDCClientMetadataContext.class.getName(), messageContext.getClass().getName());
         } catch (final ResolverException e) {
             log.error("{} ResolverException thrown during client information lookup", getLogPrefix(), e);
         }
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/OIDCProviderMetadataLookupHandler.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/OIDCProviderMetadataLookupHandler.java
index bf413c2..bee1189 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/OIDCProviderMetadataLookupHandler.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/OIDCProviderMetadataLookupHandler.java
@@ -4,7 +4,9 @@ import java.util.function.Function;
 
 import javax.annotation.Nonnull;
 
+import org.opensaml.messaging.context.BaseContext;
 import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.messaging.handler.AbstractMessageHandler;
 import org.opensaml.messaging.handler.MessageHandlerException;
 import org.slf4j.Logger;
@@ -13,7 +15,10 @@ import org.slf4j.LoggerFactory;
 import com.nimbusds.oauth2.sdk.id.Issuer;
 import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
 
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCClientMetadataContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCProviderMetadataContext;
+import net.shibboleth.idp.profile.context.RelyingPartyContext;
 import net.shibboleth.oidc.metadata.ProviderMetadataResolver;
 import net.shibboleth.oidc.metadata.criterion.IssuerIDCriterion;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
@@ -26,10 +31,9 @@ import net.shibboleth.utilities.java.support.resolver.ResolverException;
 
 /**
  * Handler for inbound OIDC protocol messages that attempts to locate OIDC metadata for a OP (issuer), and attaches it with a
- * {@link OIDCMetadataContext} as a child of a pre-existing instance of {@link MessageContext}.
+ * {@link OIDCClientMetadataContext} as a child of a pre-existing instance of {@link MessageContext}.
  */
-//TODO should this be conflated with the OIDCMetadataLookupHandler service
-//TODO is commons the correct place to put this?
+//TODO This might need a way to set which base class to add the metadata too.
 public class OIDCProviderMetadataLookupHandler extends AbstractMessageHandler {
     
     /** Logger. */
@@ -37,11 +41,26 @@ public class OIDCProviderMetadataLookupHandler extends AbstractMessageHandler {
     
     /** Resolver used to look up OIDC provider information. */
     @NonnullAfterInit private ProviderMetadataResolver providerResolver;
-    
+        
     /** Strategy used to obtain the issuer id value for the inbound message context. */
-    @NonnullAfterInit  private Function<MessageContext,String> issuerIDLookupStrategy;
+    @NonnullAfterInit private Function<MessageContext,String> issuerIDLookupStrategy;
+    
+    /** Strategy to resolve the context class to add the resolved metadata too.*/
+    @Nonnull private Function<MessageContext,? extends BaseContext> contextClassLookupStrategy;
+    
+    /** Constructor.*/
+    public OIDCProviderMetadataLookupHandler() {
+        contextClassLookupStrategy = new ChildContextLookup<>(OIDCPeerEntityContext.class);
+    }
+    
+    public void setContextClassLookupStrategy(
+            @Nonnull final Function<MessageContext, ? extends BaseContext> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+        contextClassLookupStrategy = 
+                Constraint.isNotNull(strategy, "Context class lookup strategy can not be null");
+    }
     
-   
     /**
      * Set the strategy used to locate the client id of the request.
      * 
@@ -49,10 +68,12 @@ public class OIDCProviderMetadataLookupHandler extends AbstractMessageHandler {
      */
     public void setIssuerIDLookupStrategy(@Nonnull final Function<MessageContext, String> strategy) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
         issuerIDLookupStrategy =
                 Constraint.isNotNull(strategy, "IssuerIDLookupStrategy lookup strategy cannot be null");
     }
     
+    
     /**
     * Set the {@link ProviderMetadataResolver} to use.
     * 
@@ -60,6 +81,7 @@ public class OIDCProviderMetadataLookupHandler extends AbstractMessageHandler {
     */
    public void setProviderMetadataResolver(@Nonnull final ProviderMetadataResolver resolver) {
        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+       ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
 
        providerResolver = Constraint.isNotNull(resolver, "IssuerMetadataResolver cannot be null");
    }
@@ -81,10 +103,20 @@ public class OIDCProviderMetadataLookupHandler extends AbstractMessageHandler {
     protected void doInvoke(final MessageContext messageContext) throws MessageHandlerException {
         ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
         
+        final BaseContext entityCtx = contextClassLookupStrategy.apply(messageContext);
+
+        if (entityCtx == null) {
+            log.info("{} OIDC entity context class '{}' missing", getLogPrefix(),
+                    OIDCPeerEntityContext.class);
+            return;
+        }
+        
         // Resolve issuer id from inbound message
-        //TODO should resolve an Issuer directly here?
         final String issuerId = issuerIDLookupStrategy.apply(messageContext);
-        
+        if (issuerId == null) {
+            log.warn("{} No issuer returned from lookup strategy", getLogPrefix());
+            return;
+        }
         final IssuerIDCriterion issuerCriterion = new IssuerIDCriterion(new Issuer(issuerId));
         final CriteriaSet criteria = new CriteriaSet(issuerCriterion);
         try {
@@ -94,7 +126,7 @@ public class OIDCProviderMetadataLookupHandler extends AbstractMessageHandler {
                 return;
             }
             log.debug("{} Found provider metadata for '{}'", getLogPrefix(), issuerId);
-            messageContext.addSubcontext(new OIDCProviderMetadataContext(issuerMetadata));
+            entityCtx.addSubcontext(new OIDCProviderMetadataContext(issuerMetadata));
         } catch (final ResolverException e) {
             log.error("{} ResolverException thrown during provider metadata lookup", getLogPrefix(), e);
         }
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 4cb940c..11a5944 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
@@ -58,10 +58,10 @@
         p:fieldExtractors="#{getObject('shibboleth.FlowStartAuditExtractors') ?: getObject('shibboleth.DefaultFlowStartAuditExtractors')}" />
    
    
-   <bean id="PrepareInboundMessageContext"
-            class="net.shibboleth.idp.saml.session.impl.PrepareInboundMessageContext" scope="prototype"
+   <bean id="PrepareOIDCInboundMessageContext"
+            class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.PrepareOIDCInboundMessageContext" scope="prototype"
             p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext">
-        <property name="relyingPartyLookupStrategy">
+        <property name="identiferLookupStrategy">
            <bean parent="shibboleth.Functions.Compose" c:f-ref="ParentAuthenticiationContextLookup">
                <constructor-arg name="g">
                    <bean parent="shibboleth.Functions.Expression" c:expression="#input.getAuthenticatingAuthority()" />
@@ -87,7 +87,7 @@
     <bean id="OIDCClientMetadataLookup" parent="NestedWebFlowMessageHandlerAdaptor"
         scope="prototype" c:executionDirection="INBOUND">
         <constructor-arg name="messageHandler">
-            <bean class="net.shibboleth.idp.plugin.authn.oidc.rp.metadata.impl.OIDCMetadataLookupHandler" scope="prototype">
+            <bean class="net.shibboleth.idp.plugin.authn.oidc.rp.metadata.impl.OIDCClientMetadataLookupHandler" scope="prototype">
                 <property name="clientInformationResolver">
                     <ref bean="shibboleth.oidc.rp.ClientInformationResolver" />
                 </property>
@@ -104,8 +104,7 @@
 
     <bean id="InitializeRelyingPartyContext"
         class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.InitializeRelyingPartyContext" scope="prototype"
-        p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
-        p:issuerIDLookupStrategy-ref="shibboleth.oidc.rp.IssuerIDLookupStrategy" />
+        p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext" />
     
         
     <bean id="InitializeOutboundMessageContext"
@@ -122,9 +121,14 @@
         class="net.shibboleth.idp.profile.impl.SelectProfileConfiguration" scope="prototype"
         p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext" />
         
+    <bean id="PopulateResponseTypeAndModeContext" scope="prototype"
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.PopulateResponseTypeAndModeContext"
+        p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"/>
+        
     <bean id="AddAuthzRequest" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.AddAuthzRequest"
-        p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"/>
+        p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"/>        
+    
         
      <!-- Message Encoder factory is a prototype to allow reuse of the encoders -->
     <bean id="messageEncoderFactory"
@@ -133,22 +137,22 @@
 
     <!-- List must itself be a prototype so new encoders are created per request -->
     <util:list id="shibboleth.authn.oidc.rp.AuthenticationRequestEncoders" scope="prototype">
-        <ref bean="OIDCAuthnRedirectRequestEncoder" />
-        <ref bean="OIDCAuthnPostRequestEncoder" />
+        <ref bean="HTTPRedirectAuthnRequestEncoder" />
+        <ref bean="HTTPPostAuthnRequestEncoder" />
     </util:list>
 
-    <bean id="OIDCAuthnRedirectRequestEncoder"
-        class="net.shibboleth.oidc.profile.encoder.impl.HTTPRedirectAuthnEncoder" init-method="" scope="prototype"
+    <bean id="HTTPRedirectAuthnRequestEncoder"
+        class="net.shibboleth.oidc.profile.encoder.impl.HTTPRedirectAuthnRequestEncoder" init-method="" scope="prototype"
         p:httpServletResponse-ref="shibboleth.HttpServletResponse" />
 
-    <bean id="OIDCAuthnPostRequestEncoder" class="net.shibboleth.oidc.profile.encoder.impl.HTTPPostAuthnEncoder"
+    <bean id="HTTPPostAuthnRequestEncoder" class="net.shibboleth.oidc.profile.encoder.impl.HTTPPostAuthnRequestEncoder"
         init-method="" scope="prototype" p:velocityEngine-ref="shibboleth.VelocityEngine"
         p:httpServletResponse-ref="shibboleth.HttpServletResponse" />
 
 
     <bean id="EncodeMessage" class="org.opensaml.profile.action.impl.EncodeMessage" scope="prototype"
-        p:messageEncoderFactory-ref="messageEncoderFactory" p:httpServletResponse-ref="shibboleth.HttpServletResponse" />
-
+        p:messageEncoderFactory-ref="messageEncoderFactory" p:httpServletResponse-ref="shibboleth.HttpServletResponse"/>
+        
     <!-- TODO: Place holder for message handlers -->
     <bean id="PreEncodeMessageHandler" class="org.opensaml.messaging.handler.impl.BasicMessageHandlerChain" scope="prototype">
         <property name="handlers">
@@ -158,9 +162,97 @@
         </property>
     </bean>
     
+    <!-- Message Decoding -->
+    <bean id="messageDecoderFactory" class="net.shibboleth.idp.saml.profile.impl.SpringAwareMessageDecoderFactory">
+        <property name="beanMappings">
+            <map>
+                <entry key="query" value="OIDCRedirectAuthnResponseDecoder" />
+                <entry key="form_post" value="OIDCPostAuthnResponseDecoder" />
+            </map>
+        </property>
+    </bean>
+    
+    <bean id="OIDCRedirectAuthnResponseDecoder"
+        class="net.shibboleth.oidc.profile.decoding.impl.HTTPRedirectAuthnResponseDecoder" init-method="" scope="prototype"
+        p:httpServletRequest-ref="shibboleth.HttpServletRequest" />
+    
+    <bean id="OIDCPostAuthnResponseDecoder"
+        class="net.shibboleth.oidc.profile.decoding.impl.HTTPPostAuthnResponseDecoder" init-method="" scope="prototype"
+        p:httpServletRequest-ref="shibboleth.HttpServletRequest" />
     
     
     
+    <!-- After authentication response -->
+    
+    <bean id="ValidateExternalAuthenticationContext" scope="prototype"
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateExternalAuthenticationContext" />        
+     
+    <bean id="ValidateAuthenticationResponseResult" scope="prototype"
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateAuthenticationResponseResult" 
+        p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
+        p:authenticationContextLookupStrategy-ref="ParentAuthenticiationContextLookup"/>
+   
+    <bean id="ValidateResponseStateMatchesRequest" scope="prototype"
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateResponseState"
+        p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext" 
+        p:authenticationContextLookupStrategy-ref="ParentAuthenticiationContextLookup"/>
+        
+        
+    <!-- CODE flow beans -->
+    
+    <bean id="ExchangeCodeForToken" scope="prototype"
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ExchangeCodeForToken" 
+        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: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')}"/>   
+        
+     <bean id="ExtractIDTokenFromTokenResponse" scope="prototype"
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ExtractIDTokenFromResponse"
+        p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
+        p:rawIdTokenLookupStrategy-ref="TokenResponseIDTokenLookupStrategy"/>
+    
+    <bean id="TokenResponseIDTokenLookupStrategy" 
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.TokenResponseIDTokenLookupStrategy"/>
+    
+    <!-- could these be singletons? --> 
+    <bean id="shibboleth.authn.oidc.rp.DefaultTokenResponseDecoder" scope="prototype" 
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.decoding.impl.DefaultTokenResponseDecoder"
+        p:objectMapper-ref="shibboleth.authn.oidc.rp.JSONObjectMapper"/>
+        
+    <bean id="shibboleth.authn.oidc.rp.DefaultTokenRequestEncoder" scope="prototype" 
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.encoding.impl.DefaultTokenRequestEncoder"/>
+        
+     <!-- Create a default object mapper. Setup should not change once injected -->
+    <bean id="shibboleth.authn.oidc.rp.JSONObjectMapper" class="com.fasterxml.jackson.databind.ObjectMapper" />
+
+    <bean class="org.springframework.beans.factory.config.MethodInvokingBean"
+        p:targetObject-ref="shibboleth.authn.oidc.rp.JSONObjectMapper" p:targetMethod="setSerializationInclusion">
+        <property name="arguments">
+            <util:constant static-field="com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL" />
+        </property>
+    </bean>
+
+    <bean class="org.springframework.beans.factory.config.MethodInvokingBean"
+        p:targetObject-ref="shibboleth.authn.oidc.rp.JSONObjectMapper" p:targetMethod="registerModule">
+        <property name="arguments">
+            <bean class="com.fasterxml.jackson.datatype.jsr310.JavaTimeModule" />
+        </property>
+    </bean>
+
+    <bean class="org.springframework.beans.factory.config.MethodInvokingBean"
+        p:targetObject-ref="shibboleth.authn.oidc.rp.JSONObjectMapper" p:targetMethod="setDateFormat">
+        <property name="arguments">
+            <bean class="java.text.SimpleDateFormat" c:_0="yyyy-MM-dd'T'HH:mm:ss.SSSZZ" />
+        </property>
+    </bean>   
+    
+    
+    <!-- Process token -->
+    
+   
     
     <!-- OLD STUFF -->
     
@@ -188,17 +280,11 @@
         <property name="responseType" value="id_token token"/>
     </bean> -->
 
-    <bean id="ValidateOIDCAuthenticationResponse"
-        class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateOIDCAuthenticationResponse"
-        p:httpServletRequest-ref="shibboleth.HttpServletRequest" />
-
     <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="GetOIDCTokenResponse" class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.GetOIDCTokenResponse"
-        p:httpServletRequest-ref="shibboleth.HttpServletRequest" />
         
     <bean id="ValidateTokenClaims" scope="prototype"
 	   class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateTokenClaims"
@@ -266,7 +352,7 @@
         class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.OIDCAuthenticationTimeRequested"/>
     
     <bean id="shibboleth.authn.oidc.rp.jwt.DefaultNonceActivationCondition"
-        class="net.shibboleth.oidc.security.jwt.claims.impl.NonceValidationActiviationCondition"/> 
+        class="net.shibboleth.oidc.security.jwt.claims.impl.NonceValidationActivationCondition"/> 
     
     <bean id="shibboleth.authn.oidc.rp.jwt.DefaultIssuerLookupStrategy"
         class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.OIDCIssuerClaimLookupStrategy"/>
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 1935724..ebcf8e6 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
@@ -20,13 +20,9 @@
         <!-- Nest new PRC inside AC -->
         <evaluate expression="InitializeProxyProfileRequestContext" />        
         <evaluate expression="FlowStartPopulateAuditContext" />
+        <!-- Init inbound msg context and create OP Peer -->
+        <evaluate expression="PrepareOIDCInboundMessageContext" />
         
-        <!-- this is nice to get the rp out of the authenticating auth and setup a msg context
-        but do we need it for a SAMLPeerEntityContext? -->
-        <evaluate expression="PrepareInboundMessageContext" />
-        
-        <!--  <evaluate expression="SAMLProtocolAndRole" />  maybe we need an OIDC role selector here, to say this is an
-        OP over the normal RP -->
         <evaluate expression="OIDCProviderMetadataLookup" />             
         <evaluate expression="InitializeRelyingPartyContext" />
         <evaluate expression="SelectRelyingPartyConfiguration" />
@@ -36,14 +32,13 @@
         <!--  <evaluate expression="PostLookupPopulateAuditContext" /> -->
         <evaluate expression="InitializeOutboundMessageContext" />
         <evaluate expression="SelectProfileConfiguration" />
+        <evaluate expression="PopulateResponseTypeAndModeContext"/>
         <evaluate expression="AddAuthzRequest"/>
         <!-- <evaluate expression="PostRequestPopulateAuditContext" />
         <evaluate expression="WriteAuditLog" /> -->
                 
         <!-- 
-        <evaluate expression="InitializeMessageChannelSecurityContext" />
-        <evaluate expression="PopulateBindingAndEndpointContexts" />
-        <evaluate expression="PopulateRequestSignatureSigningParameters" />        
+        <evaluate expression="InitializeMessageChannelSecurityContext" />  
          -->
         <evaluate expression="'proceed'" />
         
@@ -56,30 +51,80 @@
         <on-render>
             <evaluate
                 expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).addSubcontext(new net.shibboleth.idp.authn.context.ExternalAuthenticationContext(new net.shibboleth.idp.authn.impl.ExternalAuthenticationImpl(false)), true).setFlowExecutionUrl(flowExecutionUrl + '&_eventId_proceed=1')" />
-            <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).addSubcontext(new net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCAuthnContext(EncodeMessage), true)" result="flowScope.oidcContext" />
+            <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).addSubcontext(new net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCAuthnContext(EncodeMessage, messageDecoderFactory), true)" result="flowScope.oidcContext" />
             <evaluate expression="flowScope.oidcContext.setOutboundMessageHandler(PreEncodeMessageHandler)" />
           </on-render>
         <transition to="ValidateResponse" />
     </view-state>
 
     <action-state id="ValidateResponse">
-        <evaluate expression="ValidateOIDCAuthenticationResponse" />
+        <evaluate expression="ValidateExternalAuthenticationContext"/>
+        <evaluate expression="ValidateAuthenticationResponseResult"/>
+        <evaluate expression="ValidateResponseStateMatchesRequest"/>
+        <evaluate expression="'proceed'" />
+        <transition on="proceed" to="SwitchOnOIDCFlow" />
+    </action-state>
+    
+    <!-- Switch flow path based on OIDC response_type/flow used. 
+    TODO possible places for an NPE?-->
+    <decision-state id="SwitchOnOIDCFlow">
+        <!-- check a null response_type first, should never get here -->
+        <if test="opensamlProfileRequestContext.getSubcontext('net.shibboleth.idp.authn.context.AuthenticationContext')
+                            .getSubcontext('org.opensaml.profile.context.ProfileRequestContext').getOutboundMessageContext().getSubcontext('net.shibboleth.idp.plugin.authn.oidc.rp.context.ResponseTypeAndModeContext').getResponseType() == null"
+            then="UnsupportedFlow" />
+        <!-- Check for CODE flow -->
+        <if test="opensamlProfileRequestContext.getSubcontext('net.shibboleth.idp.authn.context.AuthenticationContext')
+                            .getSubcontext('org.opensaml.profile.context.ProfileRequestContext').getOutboundMessageContext().getSubcontext('net.shibboleth.idp.plugin.authn.oidc.rp.context.ResponseTypeAndModeContext').getResponseType().impliesCodeFlow()"
+            then="CodeFlow" />
+        <!-- Check for Hybrid flow -->
+        <if test="opensamlProfileRequestContext.getSubcontext('net.shibboleth.idp.authn.context.AuthenticationContext')
+                            .getSubcontext('org.opensaml.profile.context.ProfileRequestContext').getOutboundMessageContext().getSubcontext('net.shibboleth.idp.plugin.authn.oidc.rp.context.ResponseTypeAndModeContext').getResponseType().impliesImplicitFlow()"
+            then="HybridFlow" />
+        <!-- Check for IMPLICIT flow -->
+        <!-- final IF has an else if an unsupported flow is used (should not happen) -->
+        <if test="opensamlProfileRequestContext.getSubcontext('net.shibboleth.idp.authn.context.AuthenticationContext')
+                            .getSubcontext('org.opensaml.profile.context.ProfileRequestContext').getOutboundMessageContext().getSubcontext('net.shibboleth.idp.plugin.authn.oidc.rp.context.ResponseTypeAndModeContext').getResponseType().impliesHybridFlow()"
+            then="ImplicitFlow" else="UnsupportedFlow" /> 
+    </decision-state>
+
+    <action-state id="CodeFlow">
+        <evaluate expression="ExchangeCodeForToken"/> 
+        <evaluate expression="ExtractIDTokenFromTokenResponse"/>       
+        <evaluate expression="'proceed'" />
+        
+        <transition on="proceed" to="ValidateToken" />
+    </action-state>
+    
+     <action-state id="HybridFlow">
+
         <evaluate expression="'proceed'" />
-        <transition on="proceed" to="GetToken" />
+        <transition on="proceed" to="ValidateToken" />
     </action-state>
+    
+    <action-state id="ImplicitFlow">
+
+        <evaluate expression="'proceed'" />
+        <transition on="proceed" to="ValidateToken" />
+    </action-state>
+    
+     <action-state id="UnsupportedFlow">
 
-    <action-state id="GetToken">
-        <evaluate expression="GetOIDCTokenResponse" />
         <evaluate expression="'proceed'" />
-        <transition on="proceed" to="ValidateOIDCTokenResponse" />
+        <transition on="proceed" to="ValidateToken" />
+    </action-state>
+    
+    <action-state id="ValidateToken">
+        
+    
+         <transition on="proceed" to="SetPrincipal" />
     </action-state>
 
-    <action-state id="ValidateOIDCTokenResponse">
+   <!--  <action-state id="ValidateOIDCTokenResponse">
         <evaluate expression="ValidateIDTokenSignature" />
         <evaluate expression="ValidateTokenClaims" />        
         <evaluate expression="'proceed'" />
         <transition on="proceed" to="SetPrincipal" />
-    </action-state>
+    </action-state> -->
 
 
     <action-state id="SetPrincipal">
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
new file mode 100644
index 0000000..57d01e1
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultTokenResponseDecoderTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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 static org.mockito.Mockito.when;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.StatusLine;
+import org.mockito.Mockito;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.impl.AbstractOIDCTest;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/** Tests for the DefaultTokenResponseDecoder.*/
+public class DefaultTokenResponseDecoderTest extends AbstractOIDCTest {
+    
+    /** The encoder to test.*/
+    private DefaultTokenResponseDecoder decoder;
+    
+    @BeforeMethod
+    public void setup() throws Exception {
+        super.setup();
+        decoder = new DefaultTokenResponseDecoder();
+        decoder.setObjectMapper(new ObjectMapper());
+    }
+    
+    
+    @Test
+    public void testEncoder_Success() throws ComponentInitializationException, UnsupportedOperationException, IOException {
+        decoder.initialize();
+        final HttpResponse response = Mockito.mock(HttpResponse.class);
+        final HttpEntity mockHttpEntity = Mockito.mock(HttpEntity.class);
+        final StatusLine mockStatusLine = Mockito.mock(StatusLine.class);
+        when(mockStatusLine.getStatusCode()).thenReturn(200);
+        when(response.getStatusLine()).thenReturn(mockStatusLine);
+        when(response.getEntity()).thenReturn(mockHttpEntity);
+        when(mockHttpEntity.getContent()).thenReturn(new ByteArrayInputStream(ID_TOKEN_RESPONSE.getBytes()));
+        final Map<String, Object> decodedResponse = decoder.apply(response);
+        assertNotNull(decodedResponse);
+        assertNotNull(decodedResponse.get("id_token"));
+        assertNotNull(decodedResponse.get("access_token"));
+        assertNotNull(decodedResponse.get("token_type"));
+        assertNotNull(decodedResponse.get("scope"));
+    }
+    
+    @Test
+    public void testEncoder_NullResponseFields() throws ComponentInitializationException {
+        decoder.initialize();
+        final HttpResponse response = Mockito.mock(HttpResponse.class);
+        
+        final Map<String, Object> decodedResponse = decoder.apply(response);
+        assertNull(decodedResponse);
+        
+    }
+    
+    @Test
+    public void testEncoder_InternalServerErrorResponse() throws ComponentInitializationException {
+        decoder.initialize();
+        final HttpResponse response = Mockito.mock(HttpResponse.class);
+        final StatusLine mockStatusLine = Mockito.mock(StatusLine.class);
+        when(mockStatusLine.getStatusCode()).thenReturn(500);
+        when(response.getStatusLine()).thenReturn(mockStatusLine);
+        
+        final Map<String, Object> decodedResponse = decoder.apply(response);
+        assertNull(decodedResponse);
+        
+    }
+    
+    @Test
+    public void testEncoder_NoMessageEntity() throws ComponentInitializationException {
+        decoder.initialize();
+        final HttpResponse response = Mockito.mock(HttpResponse.class);
+        final StatusLine mockStatusLine = Mockito.mock(StatusLine.class);
+        when(mockStatusLine.getStatusCode()).thenReturn(200);
+        when(response.getStatusLine()).thenReturn(mockStatusLine);
+        when(response.getEntity()).thenReturn(null);
+        
+        final Map<String, Object> decodedResponse = decoder.apply(response);
+        assertNull(decodedResponse);
+        
+    }
+
+}
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultTokenRequestEncoderTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultTokenRequestEncoderTest.java
new file mode 100644
index 0000000..b0b68e8
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultTokenRequestEncoderTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.encoding.impl;
+
+import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertNotNull;
+
+import java.nio.charset.StandardCharsets;
+
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.impl.AbstractOIDCTest;
+
+/** Tests for the DefaultTokenRequestEncoder.*/
+public class DefaultTokenRequestEncoderTest extends AbstractOIDCTest {
+    
+    /** The encoder to test.*/
+    private DefaultTokenRequestEncoder encoder;
+    
+    @BeforeMethod
+    public void setup() throws Exception {
+        super.setup();
+        encoder = new DefaultTokenRequestEncoder();
+    }
+    
+    
+    @Test
+    public void testEncoder_Success() throws Exception {
+        encoder.initialize();
+        final HttpUriRequest request = encoder.apply(prc);
+        assertNotNull(request);
+        assertTrue(request instanceof HttpEntityEnclosingRequest);
+        assertNotNull(request.getFirstHeader("Authorization"));
+        assertNotNull(((HttpEntityEnclosingRequest)request).getEntity().getContent());
+        String content = new String(
+                ((HttpEntityEnclosingRequest)request).getEntity().getContent().readAllBytes(), StandardCharsets.UTF_8);
+        assertTrue("grant_type expected in request", content.contains("grant_type"));
+        assertTrue("authorization_code expected in request", content.contains("authorization_code"));
+        assertTrue("code expected in request", content.contains("code"));
+        assertTrue("redirect_uri expected in request", content.contains("redirect_uri"));
+    }
+
+}
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
new file mode 100644
index 0000000..0c183e7
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java
@@ -0,0 +1,177 @@
+/*
+ * 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.net.URI;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.webflow.execution.RequestContext;
+
+import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.oauth2.sdk.id.State;
+import com.nimbusds.openid.connect.sdk.AuthenticationResponseParser;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+
+import net.minidev.json.JSONArray;
+import net.minidev.json.JSONObject;
+import net.minidev.json.parser.JSONParser;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCClientMetadataContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCProviderMetadataContext;
+import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
+import net.shibboleth.idp.profile.testing.RequestContextBuilder;
+import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+
+/** Abstract class for tests that require context setup appropriate for an OIDC request/response.*/
+public abstract class AbstractOIDCTest {
+    
+    protected final String GOOD_PROVIDER_CONFIGURATION_INFO = 
+            "{\"issuer\":\"https://op.example.com/\","
+            + "\"authorization_endpoint\":\"https://www.certification.openid.net/test/a/"
+            + "test_rp_proxy/authorize\",\"token_endpoint\":\"https://www.certification.openid.net/test/a/test_rp_proxy/token\","
+            + "\"jwks_uri\":\"https://www.certification.openid.net/test/a/test_rp_proxy/jwks\","
+            + "\"userinfo_endpoint\":\"https://www.certification.openid.net/test/a/test_rp_proxy/userinfo\","
+            + "\"registration_endpoint\":\"https://www.certification.openid.net/test/a/test_rp_proxy/register\","
+            + "\"scopes_supported\":[\"openid\",\"phone\",\"profile\",\"email\",\"address\",\"offline_access\"],"
+            + "\"response_types_supported\":[\"code\",\"id_token code\",\"token code id_token\",\"id_token\","
+            + "\"token id_token\",\"token code\",\"token\"],\"response_modes_supported\":[\"query\",\"fragment\","
+            + "\"form_post\"],\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\"],"
+            + "\"token_endpoint_auth_signing_alg_values_supported\":[\"RS256\",\"RS384\",\"RS512\","
+            + "\"PS256\",\"PS384\",\"PS512\",\"ES256\",\"ES256K\",\"ES384\",\"ES512\",\"EdDSA\"],"
+            + "\"grant_types_supported\":[\"authorization_code\",\"implicit\"],\"claims_parameter_supported\":true,"
+            + "\"acr_values_supported\":[\"PASSWORD\"],\"subject_types_supported\":[\"public\",\"pairwise\"],"
+            + "\"claim_types_supported\":[\"normal\",\"aggregated\",\"distributed\"],"
+            + "\"claims_supported\":[\"sub\",\"name\",\"given_name\",\"family_name\","
+            + "\"middle_name\",\"nickname\",\"gender\",\"birthdate\",\"preferred_username\",\"profile\","
+            + "\"website\",\"locale\",\"updated_at\",\"address\",\"zoneinfo\",\"phone_number\",\"phone_number_verified\","
+            + "\"email\",\"email_verified\"],\"id_token_signing_alg_values_supported\":[\"none\",\"RS256\",\"RS384\","
+            + "\"RS512\",\"PS256\",\"PS384\",\"PS512\",\"ES256\",\"ES256K\",\"ES384\",\"ES512\",\"EdDSA\"],"
+            + "\"id_token_encryption_alg_values_supported\":[\"RSA1_5\",\"RSA-OAEP\",\"RSA-OAEP-256\",\"ECDH-ES\","
+            + "\"ECDH-ES+A128KW\",\"ECDH-ES+A192KW\",\"ECDH-ES+A256KW\",\"A128KW\",\"A192KW\",\"A256KW\",\"A128GCMKW\","
+            + "\"A192GCMKW\",\"A256GCMKW\",\"dir\"],\"id_token_encryption_enc_values_supported\":[\"A128CBC-HS256\","
+            + "\"A192CBC-HS384\",\"A256CBC-HS512\",\"A128GCM\",\"A192GCM\",\"A256GCM\"],"
+            + "\"request_object_signing_alg_values_supported\":[\"none\",\"RS256\",\"RS384\",\"RS512\",\"PS256\","
+            + "\"PS384\",\"PS512\",\"ES256\",\"ES256K\",\"ES384\",\"ES512\",\"EdDSA\"],"
+            + "\"request_object_encryption_alg_values_supported\":[\"RSA1_5\",\"RSA-OAEP\",\"RSA-OAEP-256\","
+            + "\"ECDH-ES\",\"ECDH-ES+A128KW\",\"ECDH-ES+A192KW\",\"ECDH-ES+A256KW\",\"A128KW\",\"A192KW\",\"A256KW\","
+            + "\"A128GCMKW\",\"A192GCMKW\",\"A256GCMKW\",\"dir\"],\"request_object_encryption_enc_values_supported\":"
+            + "[\"A128CBC-HS256\",\"A192CBC-HS384\",\"A256CBC-HS512\",\"A128GCM\",\"A192GCM\",\"A256GCM\"],"
+            + "\"userinfo_signing_alg_values_supported\":[\"RS256\",\"RS384\",\"RS512\",\"PS256\",\"PS384\","
+            + "\"PS512\",\"ES256\",\"ES256K\",\"ES384\",\"ES512\",\"EdDSA\"],"
+            + "\"userinfo_encryption_alg_values_supported\":[\"RSA1_5\",\"RSA-OAEP\",\"RSA-OAEP-256\",\"ECDH-ES\","
+            + "\"ECDH-ES+A128KW\",\"ECDH-ES+A192KW\",\"ECDH-ES+A256KW\",\"A128KW\",\"A192KW\",\"A256KW\",\"A128GCMKW\","
+            + "\"A192GCMKW\",\"A256GCMKW\",\"dir\"],\"userinfo_encryption_enc_values_supported\":[\"A128CBC-HS256\","
+            + "\"A192CBC-HS384\",\"A256CBC-HS512\",\"A128GCM\",\"A192GCM\",\"A256GCM\"]}";
+    
+    /** Real response from openid connect tests,*/
+    @Nonnull @NotEmpty
+    protected final String ID_TOKEN_RESPONSE = "{\n"
+            + "  \"access_token\": \"W0y5aDNAzEPNpSzu1cuMG904BZuQFZJUUwG5F3ct0zydZWy1ji\",\n"
+            + "  \"token_type\": \"Bearer\",\n"
+            + "  \"id_token\": \"eyJraWQiOiJiNjliYzcyOS05NDJjLTQzNjItYmM2YS03OWU3MjAwOWY0Yzg"
+            + "iLCJhbGciOiJSUzI1NiJ9.eyJhdF9oYXNoIjoiM0pBZmFibUx4eWVnNjJJM2JiT0RrdyIsInN1YiI"
+            + "6InVzZXItc3ViamVjdC0xMjM0NTMxIiwiYXVkIjoibXl0ZXN0Y2xpZW50IiwiaXNzIjoiaHR0cHM6"
+            + "XC9cL3d3dy5jZXJ0aWZpY2F0aW9uLm9wZW5pZC5uZXRcL3Rlc3RcL2FcL3Rlc3RfcnBfcHJveHlcLy"
+            + "IsImV4cCI6MTY0MzI3NTQyNCwiaWF0IjoxNjQzMjc1MTI0fQ.igU798iSZYUR7Z6_MQQendFCqm9Pf"
+            + "6Xpd4KtnHcPGG5FxtXPiDhlyhP_7ewSIQtzWxQ5wEatixDJWl2oRasivNCJyl6ZNeUG4YuT8S7xlgp"
+            + "UumoycBxrmomS7wgZSola8oZo8BuiRKlWzqLLtFkAChGi5iJEtHfVBe3k3PyQAsWRQdtixkvdxvyLq"
+            + "FLznIaPmMH-OZ82w7sJf4bpalZSAaR5NLpyzLFw-r81IOtOVrpQtnycCw0w_JxAP16GyZXCJc56vCE"
+            + "o8rji7juk3szTOclbsYFrrd9iokWHl0qJa0G1ev5XQLsiUxYxgO1Nw-CAnHtESZ9w7JIfLLX1YlY73g\",\n"
+            + "  \"scope\": \"openid\"\n"
+            + "}";
+    
+    /** Client metadata.*/
+    @Nonnull protected final String CLIENT_METADATA = "[\n"
+            + "  {\n"
+            + "    \"issuer\": \"https://op.example.com\",\n"
+            + "    \"scope\": \"openid info profile email address phone\",\n"
+            + "    \"redirect_uris\": [\n"
+            + "      \"https://192.168.0.150/static\"\n"
+            + "    ],\n"
+            + "    \"client_id\": \"https://rp.example.com\",\n"
+            + "    \"client_secret\": \"secret\",\n"
+            + "    \"response_types\": [\n"
+            + "      \"id_token\",\n"
+            + "      \"id_token token\"\n"
+            + "    ]\n"
+            + "  }"
+            + "]";
+    
+    /** The root profile request context to use.*/
+    protected ProfileRequestContext rootPrc;
+    
+    /** The profile request context to use.*/
+    protected ProfileRequestContext prc;
+    
+    /** The request context to use.*/
+    protected RequestContext src;
+    
+    /** 
+     * Setup the various contexts.
+     * 
+     * @throws Exception on error
+     */
+    public void setup() throws Exception {
+        
+        src = new RequestContextBuilder().buildRequestContext();
+        rootPrc = new WebflowRequestContextProfileRequestContextLookup().apply(src);
+        
+        final AuthenticationContext ac = new AuthenticationContext();
+        ac.setAuthenticatingAuthority("https://op.example.com");
+        rootPrc.addSubcontext(ac);
+        // Add a nested proxy PRC under the authentication context.
+        prc = new ProfileRequestContext();
+        ac.addSubcontext(prc);
+        
+        final MessageContext outMsgCtx = new MessageContext();
+        final OIDCAuthenticationRequest request = new OIDCAuthenticationRequest(new ClientID("https://rp.example.com"));
+        request.setState(new State("8df98fd63a53fa5b5433d6f8754bca5d.65317332"));
+        outMsgCtx.setMessage(request);
+        prc.setOutboundMessageContext(outMsgCtx);      
+
+        final JSONParser parser = new JSONParser(JSONParser.MODE_JSON_SIMPLE);
+        final OIDCClientMetadataContext metadataContext = new OIDCClientMetadataContext();
+        metadataContext.setClientInformation(
+                OIDCClientInformation.parse((JSONObject)((JSONArray)parser.parse(CLIENT_METADATA)).get(0)));
+        outMsgCtx.addSubcontext(metadataContext);
+        
+        final OIDCPeerEntityContext peerEntitCtx = outMsgCtx.getSubcontext(OIDCPeerEntityContext.class,true);
+        final OIDCProviderMetadataContext providerCtx = 
+                new OIDCProviderMetadataContext(
+                        OIDCProviderMetadata.parse((JSONObject)parser.parse(GOOD_PROVIDER_CONFIGURATION_INFO)));
+        peerEntitCtx.addSubcontext(providerCtx);
+        outMsgCtx.addSubcontext(peerEntitCtx);
+        
+        //Set the inbound reponse.
+        
+        final MessageContext inMsgCtx = new MessageContext();
+        inMsgCtx.setMessage(AuthenticationResponseParser.parse(
+                new URI("/idp/profile/Authn/OIDC/RP/callback"
+                        + "?state=8df98fd63a53fa5b5433d6f8754bca5d.65317332&code=z8C2DCp6sn0D9aGbEqlrFesdPVRXPtDX")));    
+
+        prc.setInboundMessageContext(inMsgCtx);
+        prc.setOutboundMessageContext(outMsgCtx);
+    }
+
+}
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationControllerTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationControllerTest.java
index 676c62c..8cf421c 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationControllerTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationControllerTest.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 static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@@ -18,9 +35,10 @@ import javax.servlet.http.HttpServletResponse;
 
 import org.mockito.Mockito;
 import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.decoder.MessageDecodingException;
+import org.opensaml.messaging.decoder.servlet.AbstractHttpServletRequestMessageDecoder;
 import org.opensaml.messaging.encoder.MessageEncoder;
 import org.opensaml.messaging.encoder.MessageEncodingException;
-import org.opensaml.messaging.handler.MessageHandlerException;
 import org.opensaml.profile.action.AbstractProfileAction;
 import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.action.EventIds;
@@ -60,9 +78,8 @@ import net.shibboleth.idp.session.context.SessionContext;
 import net.shibboleth.idp.ui.context.RelyingPartyUIContext;
 import net.shibboleth.oidc.profile.config.OIDCAuthorizationConfiguration.OIDCHttpRequestMethod;
 import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
-import net.shibboleth.oidc.profile.encoder.OIDCMessageEncoder;
+import net.shibboleth.oidc.profile.decoding.OIDCMessageDecoder;
 import net.shibboleth.oidc.profile.encoder.impl.AbstractOIDCMessageEncoder;
-import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.net.HttpServletSupport;
 import net.shibboleth.utilities.java.support.net.URLBuilder;
@@ -134,6 +151,8 @@ public class AuthorizationControllerTest extends AbstractTestNGSpringContextTest
         assertTrue(result.getResponse().getRedirectedUrl().contains("scope"));
     }
     
+    //TODO the callback method
+    
     /**
      * Export the FlowExecutor to the servlet context with the correct set of configured contexts. Mimicking the
      * IdP's configuration of the {@link ServletContextAttributeExporter}.
@@ -184,7 +203,8 @@ public class AuthorizationControllerTest extends AbstractTestNGSpringContextTest
         encoder.setHttpServletResponse(response);
         final MockEncodeMessage encode = new MockEncodeMessage(encoder);
         encode.setHttpServletResponse(response);
-        final OIDCAuthnContext oidcContext = new OIDCAuthnContext(encode);
+        final OIDCAuthnContext oidcContext = new OIDCAuthnContext(encode, 
+                decoder -> new NoOpMockRedirectResponseDecoder());
         
         ac.addSubcontext(oidcContext);
 
@@ -273,6 +293,17 @@ public class AuthorizationControllerTest extends AbstractTestNGSpringContextTest
         }
     }
     
+    private class NoOpMockRedirectResponseDecoder 
+                        extends AbstractHttpServletRequestMessageDecoder implements OIDCMessageDecoder {
+
+        @Override
+        protected void doDecode() throws MessageDecodingException {
+            //do nothing
+            
+        }
+        
+    }
+    
     private class MockRedirectEncoder extends AbstractOIDCMessageEncoder {
 
         @Override
@@ -302,9 +333,6 @@ public class AuthorizationControllerTest extends AbstractTestNGSpringContextTest
             }
             
         }
-
-        
-        
     }
     
 
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
new file mode 100644
index 0000000..21c54ed
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExchangeCodeForTokenTest.java
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+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.methods.HttpUriRequest;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.protocol.HttpContext;
+import org.mockito.Mockito;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.messaging.context.navigate.ParentContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.webflow.execution.Event;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.jose.util.StandardCharset;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
+
+/**
+ * Tests for the {@link ExchangeCodeForToken} action.
+ */
+public class ExchangeCodeForTokenTest extends AbstractOIDCTest {
+    
+    /** The action to test.*/
+    @Nonnull private ExchangeCodeForToken exchangeAction;
+    
+    
+    @BeforeMethod
+    public void setup() throws Exception {    
+        
+        super.setup();
+        
+        exchangeAction = new ExchangeCodeForToken();
+        exchangeAction.setAuthenticationContextLookupStrategy(new ParentContextLookup<>(AuthenticationContext.class));
+        exchangeAction.setProfileContextLookupStrategy(new ChildContextLookup<>(ProfileRequestContext.class).compose(
+                new ChildContextLookup<>(AuthenticationContext.class)
+                .compose(new WebflowRequestContextProfileRequestContextLookup())));        
+    }
+    
+    /** 
+     * Test the token exchange works using a mocked HTTP client and response.
+     * Does not check the signature is valid. Given the amount of mocking, is not
+     * a very thorough test.
+     * 
+     * @throws Exception on error.
+     */
+    @Test
+    public void testTokenExchange() throws Exception {
+
+        final HttpClient httpClient = Mockito.mock(HttpClient.class);
+        final HttpResponse httpResponse = Mockito.mock(HttpResponse.class);
+        final StatusLine statusLine = Mockito.mock(StatusLine.class);
+
+        Mockito.when(httpResponse.getStatusLine()).thenReturn(statusLine);
+
+        Mockito.when(statusLine.getStatusCode()).thenReturn(200);
+        Mockito.when(httpResponse.getEntity()).thenReturn(new StringEntity(ID_TOKEN_RESPONSE));
+        Mockito.when(httpClient.execute((HttpUriRequest) Mockito.any(), (HttpContext) Mockito.any()))
+                .thenReturn(httpResponse);
+
+        // create new client with mock response
+        exchangeAction.setHttpClient(httpClient);
+        
+        exchangeAction.setTokenRequestEncoderStrategy(prc -> {
+            URI uri;
+            try {
+                uri = new URIBuilder().setScheme("https")
+                        .setHost("op.example.com")
+                        .setPath("/token")
+                        .build();
+            } catch (URISyntaxException e) {
+                return null;
+            }                 
+            
+            // Add headers and create request.
+            final RequestBuilder rb = RequestBuilder.post().setUri(uri)
+                    .setHeader("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType())
+                    .setCharset(StandardCharset.UTF_8);
+            return rb.build();
+        });     
+        exchangeAction.setTokenResponseDecoderStrategy(response -> {
+            ObjectMapper mapper = new ObjectMapper();
+            try {
+                return mapper.readValue(
+                        httpResponse.getEntity().getContent(), new TypeReference<Map<String, Object>>() {});
+            } catch (UnsupportedOperationException | IOException e) {                
+                return null;
+            }
+        });
+        
+        exchangeAction.initialize();
+
+        final Event event = exchangeAction.execute(src);
+        //Null is success
+        assertNull(event);
+
+    }
+    
+    @Test
+    public void testNullTokenResponse() throws Exception {
+
+        final HttpClient httpClient = Mockito.mock(HttpClient.class);
+        final HttpResponse httpResponse = Mockito.mock(HttpResponse.class);
+        final StatusLine statusLine = Mockito.mock(StatusLine.class);
+        Mockito.when(httpResponse.getStatusLine()).thenReturn(statusLine);
+        Mockito.when(statusLine.getStatusCode()).thenReturn(200);       
+
+        // create new client with mock response
+        exchangeAction.setHttpClient(httpClient);
+        
+        exchangeAction.setTokenRequestEncoderStrategy(prc -> {
+            URI uri;
+            try {
+                uri = new URIBuilder().setScheme("https")
+                        .setHost("op.example.com")
+                        .setPath("/token")
+                        .build();
+            } catch (URISyntaxException e) {
+                return null;
+            }                 
+            
+            // Add headers and create request.
+            final RequestBuilder rb = RequestBuilder.post().setUri(uri)
+                    .setHeader("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType())
+                    .setCharset(StandardCharset.UTF_8);
+            return rb.build();
+        });     
+        exchangeAction.setTokenResponseDecoderStrategy(response -> null);
+        
+        exchangeAction.initialize();
+
+        final Event event = exchangeAction.execute(src);
+        //Null is success
+        assertNotNull(event);
+        assertEquals("AuthenticationException",event.getId());
+
+    }
+
+}
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java
index ee88be0..9a7c66a 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
@@ -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 static org.mockito.ArgumentMatchers.any;
@@ -5,6 +22,7 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import java.io.IOException;
+import java.net.URI;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -16,6 +34,7 @@ import org.apache.http.client.ResponseHandler;
 import org.apache.http.client.methods.HttpUriRequest;
 import org.apache.http.protocol.HttpContext;
 import org.junit.Test;
+import org.opensaml.messaging.context.MessageContext;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -23,15 +42,33 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder;
 import org.springframework.core.io.ClassPathResource;
 import org.springframework.webflow.core.collection.LocalAttributeMap;
 import org.springframework.webflow.engine.Flow;
+import org.springframework.webflow.engine.impl.FlowExecutionImpl;
 import org.springframework.webflow.execution.FlowExecution;
 import org.springframework.webflow.test.MockFlowBuilderContext;
 
 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.AuthenticationResponseParser;
 import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
 
+import net.minidev.json.JSONArray;
+import net.minidev.json.JSONObject;
+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.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.oidc.rp.messaging.context.OIDCClientMetadataContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCProviderMetadataContext;
 import net.shibboleth.idp.plugin.authn.test.flow.AbstractAuthnXmlFlowExecutionTests;
 import net.shibboleth.idp.plugin.authn.test.flow.mock.MockFlowBuilder;
+import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
 import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
 
@@ -98,6 +135,21 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
             + "]\n"
             + "}";
     
+    @Nonnull private final String CLIENT_METADATA = "[\n"
+            + "  {\n"
+            + "    \"issuer\": \"https://op.example.com\",\n"
+            + "    \"scope\": \"openid info profile email address phone\",\n"
+            + "    \"redirect_uris\": [\n"
+            + "      \"https://192.168.0.150/static\"\n"
+            + "    ],\n"
+            + "    \"client_id\": \"demo_rp\",\n"
+            + "    \"response_types\": [\n"
+            + "      \"id_token\",\n"
+            + "      \"id_token token\"\n"
+            + "    ]\n"
+            + "  }"
+            + "]";
+    
     
     /** Path to the flow to be tested.*/
     @Nonnull private static final String FLOW = 
@@ -181,7 +233,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
     }
 
     @Test
-    public void testFlowToRedirect() {
+    public void testFlowToAuthorizationRedirect() {
         setFlowPath(FLOW);
         setFlowModelResources(flowResources);
         setSubflows(subflows);
@@ -208,4 +260,153 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         flowExecution.start(inputMap, externalContext);    
         assertCurrentStateEquals("AuthRequest");
     }
+    
+    /** 
+     * Test the flow from the external authorization request to the end of the flow.
+     * 
+     * @throws Exception on error.
+     */
+    @Test 
+    public void testAuthnFlowFromAuthorizationCallback() 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 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);       
+       
+        final MessageContext outMsgCtx = new MessageContext();
+        final OIDCAuthenticationRequest request = new OIDCAuthenticationRequest(new ClientID("https://op.example.com"));
+        request.setState(new State("8df98fd63a53fa5b5433d6f8754bca5d.65317332"));
+        outMsgCtx.setMessage(request);
+        
+        final OIDCPeerEntityContext peerCtx = new OIDCPeerEntityContext();
+        OIDCProviderMetadata providerMetadata = OIDCProviderMetadata.parse(GOOD_PROVIDER_CONFIGURATION_INFO);
+        final OIDCProviderMetadataContext providerMetadataCtx = new OIDCProviderMetadataContext(providerMetadata);
+        peerCtx.addSubcontext(providerMetadataCtx);
+        
+        outMsgCtx.addSubcontext(peerCtx);
+        nestPrc.setOutboundMessageContext(outMsgCtx);
+        
+        final ResponseTypeAndModeContext respCtx = new ResponseTypeAndModeContext();
+        respCtx.setResponseMode(ResponseMode.QUERY);
+        respCtx.setResponseType(ResponseType.CODE);
+        nestPrc.getOutboundMessageContext().addSubcontext(respCtx);
+        
+        final JSONParser parser = new JSONParser(JSONParser.MODE_JSON_SIMPLE);
+        final OIDCClientMetadataContext metadataContext = new OIDCClientMetadataContext();
+        metadataContext.setClientInformation(
+                OIDCClientInformation.parse((JSONObject)((JSONArray)parser.parse(CLIENT_METADATA)).get(0)));
+        nestPrc.getOutboundMessageContext().addSubcontext(metadataContext);
+        
+        final MessageContext inMsgCtx = new MessageContext();
+        inMsgCtx.setMessage(AuthenticationResponseParser.parse(
+                new URI("/idp/profile/Authn/OIDC/RP/callback"
+                        + "?state=8df98fd63a53fa5b5433d6f8754bca5d.65317332&code=z8C2DCp6sn0D9aGbEqlrFesdPVRXPtDX")));
+        nestPrc.setInboundMessageContext(inMsgCtx);
+        
+        prc.getSubcontext(AuthenticationContext.class)
+        .addSubcontext(new ExternalAuthenticationContext(new ExternalAuthenticationImpl(false)));
+                        flowExecution.getConversationScope().put("opensamlProfileRequestContext",prc);
+        
+        
+        updateFlowExecution(flowExecution);
+        
+        //set start view and ending event to transition on.
+        externalContext.setEventId("proceed");
+        setCurrentState("AuthRequest");       
+        resumeFlow(externalContext);
+        
+        //assert success conditions
+        assertFlowExecutionEnded();
+        assertNotNull(prc.getSubcontext(AuthenticationContext.class));
+      
+        
+    }
+    
+    
+    /** 
+     * Test the flow from the external authorization request to the end of the flow when an error
+     * is returned from the downstream OP.
+     * 
+     * @throws Exception on error.
+     */
+    @Test 
+    public void testAuthnFlowFromAuthorizationCallback_ErrorAuthenticationResponse() 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 FlowExecutionImpl flowExecution = (FlowExecutionImpl)getFlowExecutionFactory()
+                .createFlowExecution(getFlowDefinition());
+        final ProfileRequestContext prc =  buildProfileRequestContext("authn/OIDCRelyingParty", false,false);
+
+        prc.getSubcontext(AuthenticationContext.class)
+                    .addSubcontext(new ExternalAuthenticationContext(new ExternalAuthenticationImpl(false)));
+        flowExecution.getConversationScope().put("opensamlProfileRequestContext",prc);
+        
+        // create a nested PRC under the authentication context
+        final ProfileRequestContext nestPrc = (ProfileRequestContext) 
+                prc.getSubcontext(AuthenticationContext.class).addSubcontext(new ProfileRequestContext(), true);       
+       
+        final MessageContext outMsgCtx = new MessageContext();
+        final OIDCAuthenticationRequest request = new OIDCAuthenticationRequest(new ClientID("https://op.example.com"));
+        request.setState(new State("8df98fd63a53fa5b5433d6f8754bca5d.65317332"));
+        outMsgCtx.setMessage(request);
+        nestPrc.setOutboundMessageContext(outMsgCtx);
+        
+        final MessageContext inMsgCtx = new MessageContext();
+        inMsgCtx.setMessage(AuthenticationResponseParser.parse(
+                new URI("/idp/profile/Authn/OIDC/RP/callback?"
+                        + "error=login_required&error_description=Login%20required&"
+                        + "state=d0c455126e9078aaf5a8e84c0e1910ad.65317332")));
+        nestPrc.setInboundMessageContext(inMsgCtx);
+        
+        
+        updateFlowExecution(flowExecution);
+        
+        //set start view and ending event to transition on.
+        externalContext.setEventId("proceed");
+        setCurrentState("AuthRequest");       
+        resumeFlow(externalContext);
+        
+        //assert success conditions
+        assertFlowExecutionEnded();
+        //TODO FIX SUCCESS CONDITIONS
+        assertNotNull(prc.getSubcontext(AuthenticationContext.class));
+      
+        
+    }
 }

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


More information about the commits mailing list