[java-idp-plugin-oidc-rp] branch main updated: Add client_id and client_authentication resolvers. Various additions

Phil Smart philip.smart at jisc.ac.uk
Fri Mar 18 11:21:49 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=71753712ba037a555ee0ccb96d2c3005740f34a8

The following commit(s) were added to refs/heads/main by this push:
     new 7175371  Add client_id and client_authentication resolvers. Various additions
7175371 is described below

commit 71753712ba037a555ee0ccb96d2c3005740f34a8
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Mar 18 11:21:43 2022 +0000

    Add client_id and client_authentication resolvers. Various additions
    
     - Improve Token Request encoder. Using some parts of the Nimbus
    TokenRequest API. Supports client_basic_secret and post. JWT type would
    not be too difficult to wire up, not sure of private_key_jwt.
     - Add reloadable client_authentication and client_id resolver services.
    Although this should be part of the profile/sec config rather than
    inside an action.
     - Add redirect URI from request URI generation. Using allowed hosts,
    and an override from the profile config
     - Remove client information metadata usage
     - Tests
---
 idp-oidc-rp-api/pom.xml                            |   7 +-
 .../ClientIDFromOAuth2ClientContextFunction.java   |  71 +++++++
 .../config/navigate/RedirectUriLookupFunction.java |  54 +++++
 .../authn/oidc/rp/context/OAuth2ClientContext.java |  76 +++++++
 .../rp/storage/ClientAuthenticationDetails.java    |  70 +++++++
 .../impl/AbstractRequestEncoderFunction.java       |  74 ++++---
 ...ava => DefaultAuthCodeTokenRequestEncoder.java} |  21 +-
 .../impl/DefaultUserInfoRequestEncoder.java        |  31 ++-
 .../impl/NimbusAuthCodeTokenRequestEncoder.java    |  94 +++++++++
 .../AbstractOIDCAuthenticationResponseAction.java  |  20 +-
 ...uest.java => AddOIDCAuthenticationRequest.java} |  67 +++----
 .../oidc/rp/impl/AuthorizationController.java      | 112 ++++++++++-
 .../impl/DefaultRedirectUriCreationFunction.java   | 222 +++++++++++++++++++++
 .../rp/impl/DefaultResponseTypeLookupFunction.java |   1 +
 ...nitializeOAuth2ClientAuthenticationContext.java | 197 ++++++++++++++++++
 .../rp/impl/InitializeOAuth2ClientContext.java     | 185 +++++++++++++++++
 ...OutboundAuthorizationRequestMessageContext.java |  18 +-
 .../MapBackedMemoryStorageServiceFactoryBean.java  | 119 +++++++++++
 .../impl/OAuth2ClientAuthenticationContainer.java  | 147 ++++++++++++++
 ...lientAuthenticationResolverServiceStrategy.java |  65 ++++++
 .../impl/OAuth2ClientIdentifierContainer.java      | 148 ++++++++++++++
 ...th2ClientIdentifierResolverServiceStrategy.java |  67 +++++++
 ...eloadingOAuth2ClientAuthenticationProvider.java | 107 ++++++++++
 .../ReloadingOAuth2ClientIdentifierProvider.java   | 105 ++++++++++
 .../META-INF/net.shibboleth.idp/postconfig.xml     | 127 ++++++++++--
 .../oidc-relying-party-authn-beans.xml             |  86 ++++----
 .../oidc-relying-party-authn-flow.xml              |   6 +-
 .../authn/clientauthentication-resolver-system.xml |  33 +++
 .../authn/clientidentifier-resolver-system.xml     |  30 +++
 .../authn/providermetadata-resolver-system.xml     |  20 +-
 ...=> DefaultAuthCodeTokenRequestEncoderTest.java} |   6 +-
 .../NimbusAuthCodeTokenRequestEncoderTest.java     | 169 ++++++++++++++++
 .../authn/oidc/rp/impl/AbstractOIDCTest.java       |  12 +-
 .../oidc/rp/impl/AuthorizationControllerTest.java  |  23 ++-
 .../DefaultRedirectUriCreationFunctionTest.java    | 169 ++++++++++++++++
 .../plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java  |  71 +++----
 .../oidc-client-registration-authentication.xml    |  28 +++
 .../authn/oidc-client-registration-clientid.xml    |  21 ++
 .../conf/authn/oidc-providermetadata-resolvers.xml |   2 +-
 pom.xml                                            |  13 --
 40 files changed, 2613 insertions(+), 281 deletions(-)

diff --git a/idp-oidc-rp-api/pom.xml b/idp-oidc-rp-api/pom.xml
index 2a8126f..ace1fa8 100644
--- a/idp-oidc-rp-api/pom.xml
+++ b/idp-oidc-rp-api/pom.xml
@@ -50,7 +50,12 @@
             <groupId>com.nimbusds</groupId>
             <artifactId>oauth2-oidc-sdk</artifactId>
             <scope>provided</scope>
-        </dependency>    
+        </dependency> 
+         <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-annotations</artifactId>
+            <scope>provided</scope>
+        </dependency>   
 	</dependencies>
 	
 </project>
\ No newline at end of file
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ClientIDFromOAuth2ClientContextFunction.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ClientIDFromOAuth2ClientContextFunction.java
new file mode 100644
index 0000000..4edcb4d
--- /dev/null
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ClientIDFromOAuth2ClientContextFunction.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate;
+
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OAuth2ClientContext;
+import net.shibboleth.utilities.java.support.annotation.ParameterName;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * A function that pulls the client_id out of the {@link OAuth2ClientContext}.
+ */
+//TODO could be moved to commons
+ at ThreadSafe
+public class ClientIDFromOAuth2ClientContextFunction 
+                implements BiFunction<ProfileRequestContext, JWTClaimsSet, String> {
+
+    /** Strategy that will return a @link OAuth2ClientContext}. */
+    @Nonnull 
+    private final Function<ProfileRequestContext, OAuth2ClientContext> oauth2ClientContextLookupStrategy;
+
+    /**
+     * 
+     * Constructor.
+     *
+     * @param strgy the strategy used to locate the {@link OAuth2ClientContext}
+     */
+    public ClientIDFromOAuth2ClientContextFunction(
+            @Nonnull @ParameterName(name="oauth2ClientContextLookupStrategy")
+            final Function<ProfileRequestContext, OAuth2ClientContext> strgy) {
+        oauth2ClientContextLookupStrategy = Constraint.isNotNull(strgy, 
+                "OAuth2 client context lookup strategy cannot be null");
+    }
+
+
+    /** {@inheritDoc} */
+    @Override @Nullable
+    public String apply(@Nonnull final ProfileRequestContext prc, @Nullable final JWTClaimsSet claimsSet) {
+        final OAuth2ClientContext clientContext = oauth2ClientContextLookupStrategy.apply(prc);
+        if (clientContext == null) {
+            return null;
+        }
+        return clientContext.getClientId();
+    }
+
+}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/RedirectUriLookupFunction.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/RedirectUriLookupFunction.java
new file mode 100644
index 0000000..fdeece6
--- /dev/null
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/RedirectUriLookupFunction.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.config.navigate;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.profile.config.ProfileConfiguration;
+import net.shibboleth.idp.profile.context.RelyingPartyContext;
+import net.shibboleth.idp.profile.context.navigate.AbstractRelyingPartyLookupFunction;
+import net.shibboleth.oidc.profile.config.OIDCAuthorizationConfiguration;
+
+/**
+ * Pull a redirect_uri from the Relying Party profile config if present. Return {@literal null} otherwise. 
+ */
+public class RedirectUriLookupFunction extends AbstractRelyingPartyLookupFunction<URI> {
+
+    @Override
+    @Nullable public URI apply(@Nullable final ProfileRequestContext input) {
+        final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
+        if (rpc != null) {
+            final ProfileConfiguration pc = rpc.getProfileConfig();
+            if (pc instanceof OIDCAuthorizationConfiguration) {
+                try {
+                    return new URI(((OIDCAuthorizationConfiguration) pc).getRedirectUriOverride(input));
+                } catch (final URISyntaxException e) {
+                    return null;
+                }
+            } 
+        }
+        
+        return null;
+    }
+
+}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OAuth2ClientContext.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OAuth2ClientContext.java
new file mode 100644
index 0000000..a491bbf
--- /dev/null
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OAuth2ClientContext.java
@@ -0,0 +1,76 @@
+package net.shibboleth.idp.plugin.authn.oidc.rp.context;
+
+import java.net.URI;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * A context to store information pertaining to the OAuth2 client (Relying Party) to use in communication
+ * with a OpenId Connect Provider.
+ * 
+ * <p>Typically a subcontext under {@link OIDCPeerEntityContext}, as it relates to the client
+ * associated pairwise with the downstream OP peer.</p>
+ * 
+ * @parent {@link OIDCPeerEntityContext}
+ * @added During an OAuth 2.0 authentication request attempt
+ */
+public class OAuth2ClientContext extends BaseContext {
+    
+    /** The client_id.*/
+    @Nullable private String clientId;
+    
+    
+    /** An redirect URI that should take preference over any automatically computed.*/
+    @Nullable private URI redirectUriOverride;
+    
+   
+    /**
+     * Set the redirect_uri to use in place of any other.
+     * 
+     * @param override the redirect_uri
+     * 
+     * @return this
+     */
+    public OAuth2ClientContext setRedirectUriOverride(@Nullable final URI override) {
+        redirectUriOverride = override;
+        return this;
+    }
+    
+    /**
+     * Get the redirect_uri which should be used in place of any other.
+     * 
+     * @return the redirect_uri
+     */
+    public URI getRedirectUriOverride() {
+        return redirectUriOverride;
+    }
+    
+    /**
+     * Set the client_id.
+     * 
+     * @param id the client_id
+     * 
+     * @return this
+     */
+    public OAuth2ClientContext setClientId(@Nonnull @NotEmpty final String id) {
+        clientId = Constraint.isNotEmpty(id, "ClientID can not be null or empty");
+        return this;
+    }
+    
+    /**
+     * Get the client_id.
+     * 
+     * @return the client_id
+     */
+    public String getClientId() {
+        return clientId;
+    }
+    
+
+}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/storage/ClientAuthenticationDetails.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/storage/ClientAuthenticationDetails.java
new file mode 100644
index 0000000..b8cbdd4
--- /dev/null
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/storage/ClientAuthenticationDetails.java
@@ -0,0 +1,70 @@
+package net.shibboleth.idp.plugin.authn.oidc.rp.storage;
+
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.Immutable;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import net.shibboleth.utilities.java.support.annotation.ParameterName;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/** A bean to hold client_authentication registration details.*/
+ at Immutable
+public class ClientAuthenticationDetails {
+    
+    /** The client secret. */
+    @JsonProperty("client_secret") @Nonnull @NotEmpty private final char[] clientSecret;
+    
+    /** When the client secret expires.*/
+    @JsonProperty("client_secret_expires_at") private final long clientSecretExpiresAt;
+    
+    /** The token endpoint authentication method. */
+    @JsonProperty("token_endpoint_auth_method") @Nonnull @NotEmpty private final String tokenEndpointAuthMethod;
+
+    /**
+     * Constructor.
+     *
+     * @param secret The client secret
+     * @param secretExpiresAt When the client secret expires
+     * @param authMethod
+     */
+    public ClientAuthenticationDetails(@ParameterName(name="clientSecret") @Nonnull final char[] secret, 
+            @ParameterName(name="clientSecretExpiresAt") final long secretExpiresAt,
+            @ParameterName(name="tokenEndpointAuthMethod") @Nonnull @NotEmpty final String authMethod) {
+        super();
+        clientSecret = Constraint.isNotNull(secret, "Secret can not be null");
+        clientSecretExpiresAt = secretExpiresAt;
+        tokenEndpointAuthMethod = Constraint.isNotEmpty(authMethod, "Authentication method can not be null or empty");
+    }
+
+    /**
+     * Get the client_secret. 
+     * 
+     * @return Returns the clientSecret.
+     */
+    @Nonnull public char[] getClientSecret() {
+        return clientSecret;
+    }
+
+    /**
+     * Get when the client secret expires in number of seconds since UNIX EPOCH. 
+     * 
+     * @return Returns the clientSecretExpiresAt.
+     */
+    public long getClientSecretExpiresAt() {
+        return clientSecretExpiresAt;
+    }
+
+    /**
+     * Get the token authentication method.
+     * 
+     * @return Returns the tokenEndpointAuthMethod.
+     */
+    @Nonnull @NotEmpty public String getTokenEndpointAuthMethod() {
+        return tokenEndpointAuthMethod;
+    }
+    
+    
+
+}
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
index 30fd576..d1045b5 100644
--- 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
@@ -32,10 +32,12 @@ import org.slf4j.LoggerFactory;
 
 import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
 
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OAuth2ClientContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
-import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
+import net.shibboleth.oidc.authn.context.OAuth2ClientAuthenticationContext;
 import net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext;
 import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
@@ -46,59 +48,69 @@ public abstract class AbstractRequestEncoderFunction extends AbstractInitializab
                                 implements Function<ProfileRequestContext, HttpUriRequest> {
     
     /** Class logger. */
-    @Nonnull private Logger log = LoggerFactory.getLogger(AbstractRequestEncoderFunction.class);
+    @Nonnull private final 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,OIDCMetadataContext> clientMetadataLookupStrategy;
+    /** The strategy used to lookup or create the {@link OAuth2ClientAuthenticationContext}.*/
+    @Nonnull
+    private Function<ProfileRequestContext, OAuth2ClientAuthenticationContext> 
+                                            oauth2ClientAuthenticationContextLookupStrategy;
 
     /** 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 OIDCMetadataContext clientMetadataContext;
     
     /** OIDC Metadata context. */
     @Nullable private OIDCProviderMetadataContext providerMetadataContext;
     
+    /** 
+     * The context used to store client authentication information for communication with a
+     * downstream OP.
+     */
+    @Nullable private OAuth2ClientAuthenticationContext clientAuthnContext;
+    
     /** Constructor.*/
     protected AbstractRequestEncoderFunction() {
         providerMetadataLookupStrategy = new ChildContextLookup<>(OIDCProviderMetadataContext.class).compose(
                 new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
                         new OutboundMessageContextLookup()));
         
-        clientMetadataLookupStrategy = new ChildContextLookup<>(OIDCMetadataContext.class).compose(
-                new OutboundMessageContextLookup());
+        oauth2ClientAuthenticationContextLookupStrategy = 
+                new ChildContextLookup<>(OAuth2ClientAuthenticationContext.class).compose(
+                new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
+                        new OutboundMessageContextLookup()));
     }
     
     /**
-     * Set the lookup strategy to locate the OpenID providers metadata.
+     * Set the strategy to lookup the {@link OAuth2ClientAuthenticationContext} 
+     * from the {@link ProfileRequestContext}.
      * 
-     * @param strategy the strategy.
+     * @param strgy the strategy.
      */
-    public void setProviderMetadataLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext, OIDCProviderMetadataContext> strategy) {
+    public void setOIDCClientAuthenticationContextContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, OAuth2ClientAuthenticationContext> strgy) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
-        providerMetadataLookupStrategy = 
-                Constraint.isNotNull(strategy,"Provider metadata lookup strategy can not be null");
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+        oauth2ClientAuthenticationContextLookupStrategy = Constraint.isNotNull(strgy, 
+                "OAuth2 client context lookup strategy cannot be null");
     }
     
     /**
-     * Set the lookup strategy to locate the OpenID client metadata.
+     * Set the lookup strategy to locate the OpenID providers metadata.
      * 
      * @param strategy the strategy.
      */
-    public void setClientMetadataLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext, OIDCMetadataContext> strategy) {
+    public void setProviderMetadataLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, OIDCProviderMetadataContext> strategy) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
         
-        clientMetadataLookupStrategy = Constraint.isNotNull(strategy,"Client metadata lookup strategy can not be null");
+        providerMetadataLookupStrategy = 
+                Constraint.isNotNull(strategy,"Provider metadata lookup strategy can not be null");
     }
 
     /**
@@ -120,12 +132,12 @@ public abstract class AbstractRequestEncoderFunction extends AbstractInitializab
     }
 
     /**
-     * Returns the client metadata context.
+     * Get the client authentication context.
      * 
-     * @return The client metadata context.
+     * @return the client context.
      */
-    @Nullable public OIDCMetadataContext getClientMetadataContext() {
-        return clientMetadataContext;
+    @Nullable public OAuth2ClientAuthenticationContext getClientAuthenticationContext() {
+        return clientAuthnContext;
     }
     
     /**
@@ -140,7 +152,7 @@ public abstract class AbstractRequestEncoderFunction extends AbstractInitializab
     //TODO these come from the outbound message context, so are not general to other actions
 
     @Override
-    public HttpUriRequest apply(@Nonnull final ProfileRequestContext profileRequestContext) {
+    @Nullable public HttpUriRequest apply(@Nonnull final ProfileRequestContext profileRequestContext) {
 
 
         final MessageContext inboundMessageCtx = profileRequestContext.getInboundMessageContext();
@@ -170,18 +182,18 @@ public abstract class AbstractRequestEncoderFunction extends AbstractInitializab
         }
         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;
         }
         
+        clientAuthnContext = oauth2ClientAuthenticationContextLookupStrategy.apply(profileRequestContext);
+        if (clientAuthnContext == null) {
+            log.error("No OAuth 2.0 client authentication context found");
+            return null;
+        }
+        
         return doApply(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/DefaultAuthCodeTokenRequestEncoder.java
similarity index 86%
rename from idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultTokenRequestEncoder.java
rename to idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultAuthCodeTokenRequestEncoder.java
index 8d4b0cd..25d5120 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultTokenRequestEncoder.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultAuthCodeTokenRequestEncoder.java
@@ -37,23 +37,24 @@ 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. */
+/** A token request encoder that builds an OAuth2.0 Access Token Request for an authorization code grant request. */
 //TODO either only applies to authorization_code grant, or can handle more. If specific, must gurantee that.
-public class DefaultTokenRequestEncoder extends AbstractRequestEncoderFunction {
+//TODO just remove in favour of the Nimbus version?
+public class DefaultAuthCodeTokenRequestEncoder extends AbstractRequestEncoderFunction {
     
     /** The HTTPS scheme.*/
     @Nonnull @NotEmpty private static final String HTTPS = "https";
     
     /** Class logger. */
     @Nonnull
-    private final Logger log = LoggerFactory.getLogger(DefaultTokenRequestEncoder.class);
+    private final Logger log = LoggerFactory.getLogger(DefaultAuthCodeTokenRequestEncoder.class);
     
     @Override
     @Nullable public HttpUriRequest doApply(@Nonnull final ProfileRequestContext profileRequestContext) {
 
         try {
-            if (getClientMetadataContext().getClientInformation() == null) {
-                log.warn("No client information present to base token request off");
+            if (getClientAuthenticationContext() == null) {
+                log.warn("No client authentication context to base token request off");
                 return null;
             }
             // Mandate HTTPS, so construct the URL from that.
@@ -80,7 +81,7 @@ public class DefaultTokenRequestEncoder extends AbstractRequestEncoderFunction {
             buildSecretBasicAuthentication(rb);            
             
             final HttpUriRequest request = rb.build();                  
-            log.debug("Token URL '{}'",request);            
+            log.debug("Token URL '{}'",request);          
             return request;
             
         } catch (final Exception e) {
@@ -119,15 +120,17 @@ public class DefaultTokenRequestEncoder extends AbstractRequestEncoderFunction {
      * 
      * @param rb the request builder to use.
      */
-    //TODO taken from Nimbus ClientSecretBasic. Find best way to apply this.
+    //TODO taken from Nimbus ClientSecretBasic. Find best way to apply this. Use the authn method here?
     private void buildSecretBasicAuthentication(final RequestBuilder rb) {
         final StringBuilder sb = new StringBuilder();
 
         sb.append(URLEncoder.encode(
-                getClientMetadataContext().getClientInformation().getID().getValue(), StandardCharsets.UTF_8));
+                getClientAuthenticationContext().getClientAuthentication()
+                .getClientID().getValue(), StandardCharsets.UTF_8));
         sb.append(':');
         sb.append(URLEncoder.encode(
-                getClientMetadataContext().getClientInformation().getSecret().getValue(), StandardCharsets.UTF_8));
+                getClientAuthenticationContext().
+                getClientAuthentication().getMethod().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/DefaultUserInfoRequestEncoder.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultUserInfoRequestEncoder.java
index 3c7499f..d0fdb01 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultUserInfoRequestEncoder.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultUserInfoRequestEncoder.java
@@ -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.encoding.impl;
 
 import java.net.URI;
@@ -35,7 +52,7 @@ public class DefaultUserInfoRequestEncoder extends AbstractRequestEncoderFunctio
     private final Logger log = LoggerFactory.getLogger(DefaultUserInfoRequestEncoder.class);
     
     /** Strategy used to look up the {@link AccessTokenResponseContext} to set the parameters for. */
-    @Nonnull private Function<ProfileRequestContext, AccessTokenResponseContext> 
+    @Nonnull private final Function<ProfileRequestContext, AccessTokenResponseContext> 
             tokenResponseContextLookupStrategy;
 
     
@@ -50,11 +67,9 @@ public class DefaultUserInfoRequestEncoder extends AbstractRequestEncoderFunctio
     public HttpUriRequest doApply(@Nonnull final ProfileRequestContext profileRequestContext) {
         
         try {
-            if (getClientMetadataContext().getClientInformation() == null) {
-                log.warn("No client information present to base token request off");
-                return null;
-            }
-            final AccessTokenResponseContext responseCtx = tokenResponseContextLookupStrategy.apply(profileRequestContext);
+
+            final AccessTokenResponseContext responseCtx = 
+                    tokenResponseContextLookupStrategy.apply(profileRequestContext);
             if (responseCtx == null) {
                 log.debug("No TokenResponseContext returned by lookup strategy");
                 return null;
@@ -99,8 +114,8 @@ public class DefaultUserInfoRequestEncoder extends AbstractRequestEncoderFunctio
      * 
      * @throws OIDCRPException if there is an issue adding the bearer token to the Authorization header.
      */
-    private void addBearerToken(@Nonnull final RequestBuilder rb, @Nonnull final AccessTokenResponseContext responseCtx) 
-        throws OIDCRPException {
+    private void addBearerToken(@Nonnull final RequestBuilder rb, 
+            @Nonnull final AccessTokenResponseContext responseCtx) throws OIDCRPException {
         
         // Double check is bearer scheme
         if ("Bearer".equals(responseCtx.getRawTokenResponse().get("token_type"))
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/NimbusAuthCodeTokenRequestEncoder.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/NimbusAuthCodeTokenRequestEncoder.java
new file mode 100644
index 0000000..6f65a3d
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/NimbusAuthCodeTokenRequestEncoder.java
@@ -0,0 +1,94 @@
+/*
+ * 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 javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.methods.RequestBuilder;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jose.util.StandardCharset;
+import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
+import com.nimbusds.oauth2.sdk.AuthorizationGrant;
+import com.nimbusds.oauth2.sdk.TokenRequest;
+import com.nimbusds.oauth2.sdk.http.HTTPRequest;
+
+/** 
+ * A token request encoder that builds an OAuth2.0 Access Token Request for an authorization_code grant 
+ * using the Nimbus token request builders. The Nimbus {@link HTTPRequest} is converted to the 
+ * returned {@link HttpUriRequest}. 
+ * */
+public class NimbusAuthCodeTokenRequestEncoder extends AbstractRequestEncoderFunction {
+    
+    /** Class logger. */
+    @Nonnull
+    private final Logger log = LoggerFactory.getLogger(NimbusAuthCodeTokenRequestEncoder.class);
+    
+    @Override
+    @Nullable public HttpUriRequest doApply(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+        try {
+            if (getClientAuthenticationContext() == null) {
+                log.warn("No client authentication context to base token request off");
+                return null;
+            }
+            final AuthorizationGrant codeGrant = 
+                    new AuthorizationCodeGrant(getAuthenticationResponse().getAuthorizationCode(), 
+                            getAuthenticationRequest().getRedirectURI());
+            
+            final TokenRequest tokenRequest = 
+                    new TokenRequest(getProviderMetadataContext().getProviderInformation().getTokenEndpointURI(),
+                            getClientAuthenticationContext().getClientAuthentication(), codeGrant);
+          
+            return convertHttpRequest(tokenRequest.toHTTPRequest());
+            
+        } catch (final Exception e) {
+            log.warn("Unable to encode token request", e);
+        }
+        return null;
+    }
+    
+    /**
+     * Convert the internally used Nimbus {@link HTTPRequest} to the externally presented {@link HttpUriRequest}.
+     * 
+     * @param request the HTTP request to convert
+     * 
+     * @return the convert HTTP request
+     */
+    private HttpUriRequest convertHttpRequest(@Nonnull final HTTPRequest request) {
+        
+        if (request.getMethod() != HTTPRequest.Method.POST) {
+            // Should never happen as Nimbus should always use POST
+            log.warn("Token Request must use the HTTP POST method, is trying to use '{}'", request.getMethod());
+            return null;
+        }
+        final RequestBuilder rb = RequestBuilder.post().setUri(request.getURI()).setHeader(
+                "Content-Type", request.getEntityContentType().toString())
+                .setCharset(StandardCharset.UTF_8);
+        
+        request.getQueryParameters().forEach((k,v) -> v.stream().forEach(value -> rb.addParameter(k, value)));
+        if (request.getAuthorization() != null && !request.getAuthorization().isEmpty()) {
+            rb.addHeader("Authorization", request.getAuthorization());
+        }
+        return rb.build();    
+    }
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCAuthenticationResponseAction.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCAuthenticationResponseAction.java
index 011597b..b301d15 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCAuthenticationResponseAction.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCAuthenticationResponseAction.java
@@ -55,7 +55,7 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
 abstract class AbstractOIDCAuthenticationAction extends AbstractAuthenticationAction {
 
     /** Class logger. */
-    @Nonnull private Logger log = LoggerFactory.getLogger(AbstractOIDCAuthenticationAction.class);
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractOIDCAuthenticationAction.class);
     
     /** Lookup strategy to locate the OP metadata to use.*/
     @Nonnull private Function<ProfileRequestContext, OIDCProviderMetadataContext> providerMetadataLookupStrategy;
@@ -68,9 +68,6 @@ abstract class AbstractOIDCAuthenticationAction extends AbstractAuthenticationAc
     
     /** OIDC authentication request built by the IdP. */
     @Nullable private OIDCAuthenticationRequest authnRequest;
-
-    /** OIDC client Metadata context. */
-    @Nullable private OIDCMetadataContext clientMetadataContext;
     
     /** OIDC Metadata context. */
     @Nullable private OIDCProviderMetadataContext providerMetadataContext;
@@ -128,14 +125,6 @@ abstract class AbstractOIDCAuthenticationAction extends AbstractAuthenticationAc
         return authnRequest;
     }
 
-    /**
-     * Returns the client metadata context.
-     * 
-     * @return The client metadata context.
-     */
-    @Nullable public OIDCMetadataContext getClientMetadataContext() {
-        return clientMetadataContext;
-    }
     
     /**
      * Returns the OIDC provider metadata context.
@@ -185,13 +174,6 @@ abstract class AbstractOIDCAuthenticationAction extends AbstractAuthenticationAc
         }
         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());
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/AddOIDCAuthenticationRequest.java
similarity index 83%
rename from idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AddAuthzRequest.java
rename to idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AddOIDCAuthenticationRequest.java
index 0665f4b..9ed4ba5 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/AddOIDCAuthenticationRequest.java
@@ -17,7 +17,6 @@
 
 package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
 
-import java.util.List;
 import java.util.function.Function;
 
 import javax.annotation.Nonnull;
@@ -34,15 +33,16 @@ import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.nimbusds.oauth2.sdk.id.ClientID;
 import com.nimbusds.openid.connect.sdk.Nonce;
 
 import net.shibboleth.idp.authn.AbstractAuthenticationAction;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OAuth2ClientContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.ResponseTypeAndModeContext;
 import net.shibboleth.idp.profile.IdPEventIds;
 import net.shibboleth.idp.profile.context.RelyingPartyContext;
-import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
 import net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext;
 import net.shibboleth.oidc.profile.config.OIDCAuthorizationConfiguration;
 import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
@@ -63,37 +63,38 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
  * 
  * @post ProfileRequestContext.getOutboundMessageContext().getMessage() != null
  */
-public class AddAuthzRequest extends AbstractAuthenticationAction {
+public class AddOIDCAuthenticationRequest extends AbstractAuthenticationAction {
     
     /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(AddAuthzRequest.class);    
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddOIDCAuthenticationRequest.class);    
     
     /** Overwrite an existing message? */
     private boolean overwriteExisting;
     
     /** Applicable profile configuration. */
     @Nullable private OIDCAuthorizationConfiguration profileConfiguration;
-    
-    /** The metadata belonging to the client registered with the current OpenID Provider.*/
-    @Nullable private OIDCMetadataContext clientMetadata;
-    
+       
     /** The metadata belonging to the OpenID Provider.*/
     @Nullable private OIDCProviderMetadataContext providerMetadata;
     
     /** The response type and mode context.*/
     @Nullable private ResponseTypeAndModeContext responseTypeAndModeContext;
     
+    /** The strategy used to lookup or create the {@link OAuth2ClientContext} for storing the client_id.*/
+    @Nonnull 
+    private final Function<ProfileRequestContext, OAuth2ClientContext> oauth2ClientContextLookupStrategy;
+    
+    /** The stashed OAuth2 client context.*/
+    @Nullable private OAuth2ClientContext oauth2ClientContext;
+    
     /** 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,OIDCMetadataContext> clientMetadataLookupStrategy;
-    
     /** Lookup strategy to locate the response_mode and response_type context.*/
     @Nonnull private Function<ProfileRequestContext,ResponseTypeAndModeContext> responseTypeAndModeLookupStrategy;
     
     /** Constructor.*/
-    public AddAuthzRequest() {
+    public AddOIDCAuthenticationRequest() {
         // Fool the parent class into looking above instead of below the PRC for the context.
         setAuthenticationContextLookupStrategy(new ParentContextLookup<>(AuthenticationContext.class));
         
@@ -101,11 +102,13 @@ public class AddAuthzRequest extends AbstractAuthenticationAction {
                 new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
                         new InboundMessageContextLookup()));
         
-        clientMetadataLookupStrategy = new ChildContextLookup<>(OIDCMetadataContext.class).compose(
-                new InboundMessageContextLookup());
-        
         responseTypeAndModeLookupStrategy = new ChildContextLookup<>(ResponseTypeAndModeContext.class).compose(
                         new OutboundMessageContextLookup());
+        
+        // Default under OIDCPeerEntityContext in the outbound context (create true).
+        oauth2ClientContextLookupStrategy = new ChildContextLookup<>(OAuth2ClientContext.class, true).compose(
+                new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
+                        new OutboundMessageContextLookup()));
     }
     
     /**
@@ -121,17 +124,6 @@ public class AddAuthzRequest extends AbstractAuthenticationAction {
                 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, OIDCMetadataContext> 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.
@@ -195,13 +187,14 @@ public class AddAuthzRequest extends AbstractAuthenticationAction {
             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);
+        
+        oauth2ClientContext = oauth2ClientContextLookupStrategy.apply(profileRequestContext);
+        if (oauth2ClientContext == null) {
+            log.error("{} OAuth2 client context not found", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
             return false;
         }
+
         providerMetadata = providerMetadataLookupStrategy.apply(profileRequestContext);
         if (providerMetadata == null) {
             log.debug("{} Inbound message context did not contain provider metadata", getLogPrefix());
@@ -223,18 +216,17 @@ public class AddAuthzRequest extends AbstractAuthenticationAction {
         
         
         final OIDCAuthenticationRequest request = 
-                new OIDCAuthenticationRequest(clientMetadata.getClientInformation().getID());
+                new OIDCAuthenticationRequest(new ClientID(oauth2ClientContext.getClientId()));
 
         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());
         
         // Add scopes
-        final List<String> scopes = 
-                clientMetadata.getClientInformation().getMetadata().getScope().toStringList();
-        scopes.forEach(s -> request.getScope().add(s));
+//        final List<String> scopes = 
+//                clientMetadata.getClientInformation().getMetadata().getScope().toStringList();
+//        scopes.forEach(s -> request.getScope().add(s));
         
         //TODO use strategy with injectable secure random implementation?
         request.setNonce(new Nonce(OIDCProxySupport.generateNonce(16)));
@@ -246,7 +238,8 @@ public class AddAuthzRequest extends AbstractAuthenticationAction {
 //            log.error("{} Unable to set prompt", e);
 //        }
         
-        log.debug("{} Built authorization request for endpoint '{}'",getLogPrefix(), request.getEndpointURI());
+        log.debug("{} Built authorization request for endpoint '{}' for client '{}'",getLogPrefix(), 
+                request.getEndpointURI(), oauth2ClientContext.getClientId());
         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 c3d8b43..15a609c 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
@@ -18,6 +18,8 @@
 package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
 
 import java.io.IOException;
+import java.net.URI;
+import java.util.function.BiFunction;
 import java.util.function.Function;
 
 import javax.annotation.Nonnull;
@@ -35,6 +37,7 @@ 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.profile.context.navigate.OutboundMessageContextLookup;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Controller;
@@ -47,11 +50,17 @@ 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.OAuth2ClientContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCAuthnContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
 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.NonnullAfterInit;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+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;
 
 /**
  * 
@@ -76,8 +85,11 @@ import net.shibboleth.utilities.java.support.component.ComponentInitializationEx
  */
 @ThreadSafe
 @Controller
- at RequestMapping("%{shibboleth.authn.OIDC.externalAuthnPath:/Authn/OIDC/RP}")
-public class AuthorizationController {
+ at RequestMapping("%{idp.authn.oidc.rp.externalAuthnPath:/Authn/OIDC/RP}")
+public class AuthorizationController extends AbstractInitializableComponent {
+    
+    /** The final part of the IdP URL path which is called to begin authorization.*/
+    @Nonnull @NotEmpty public static final String AUTHORIZE_PATH_SEGMENT = "/authz";
     
     /** The name of the Http parameter that stores the authorisation code.*/
     @Nonnull @NotEmpty public static final String CODE_PARAMETER = "code";
@@ -89,11 +101,17 @@ public class AuthorizationController {
     @Nonnull private final Logger log = LoggerFactory.getLogger(AuthorizationController.class);
     
     /** Lookup strategy to locate the nested ProfileRequestContext. */
-    @Nonnull private Function<ProfileRequestContext,ProfileRequestContext> profileRequestContextLookupStrategy;    
+    @Nonnull private Function<ProfileRequestContext,ProfileRequestContext> profileRequestContextLookupStrategy;
+    
+    /** Function to create a suitable redirect URI from the given servlet request and profile request context.*/
+    @NonnullAfterInit private BiFunction<HttpServletRequest, ProfileRequestContext, URI> redirectUriCreationStrategy;
     
     /** Lookup strategy to locate the SAML context. */
     @Nonnull private Function<ProfileRequestContext,OIDCAuthnContext> oidcContextLookupStrategy;
     
+    /** The strategy used to lookup or create the {@link OAuth2ClientContext} for storing the client_id.*/
+    @Nonnull private Function<ProfileRequestContext, OAuth2ClientContext> oauth2ClientContextLookupStrategy;
+    
     /** Constructor.*/
     public AuthorizationController() {
         // PRC -> AC -> nested PRC
@@ -103,8 +121,78 @@ public class AuthorizationController {
         // PRC -> AC -> OIDCAuthnContext
         oidcContextLookupStrategy = new ChildContextLookup<>(OIDCAuthnContext.class).compose(
                 new ChildContextLookup<>(AuthenticationContext.class));
+   
+        
+        // Default under OIDCPeerEntityContext in the outbound context (create true).
+        oauth2ClientContextLookupStrategy = new ChildContextLookup<>(OAuth2ClientContext.class, true).compose(
+                new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
+                        new OutboundMessageContextLookup()));
     }
+    
+    /**
+     * Set the strategy to lookup the {@link OAuth2ClientContext} 
+     * from the {@link ProfileRequestContext}.
+     * 
+     * @param strgy the strategy.
+     */
+    public void setOAuth2ClientContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, OAuth2ClientContext> strgy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
 
+        oauth2ClientContextLookupStrategy = Constraint.isNotNull(strgy, 
+                "OAuth2 client context lookup strategy cannot be null");
+    }
+    
+    /**
+     * Set the lookup strategy used to locate the nested {@link ProfileRequestContext}.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setProfileRequestContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext,ProfileRequestContext> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+        
+        profileRequestContextLookupStrategy = Constraint.isNotNull(strategy,
+                "ProfileRequestContext lookup strategy cannot be null");
+    }
+    
+    /**
+     * Set the lookup strategy used to locate the {@link OIDCAuthnContext}.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setOidcAuthnContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext,OIDCAuthnContext> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+        
+        oidcContextLookupStrategy = Constraint.isNotNull(strategy, "OIDCAuthnContext lookup strategy cannot be null");
+    }
+    
+    /**
+     * Set the creation strategy used to compute or lookup a redirect URI.
+     * 
+     * @param strategy the creation strategy
+     */
+    public void setRedirectUriCreationStrategy(
+            @Nonnull final BiFunction<HttpServletRequest, ProfileRequestContext, URI> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+        
+        redirectUriCreationStrategy = 
+                Constraint.isNotNull(strategy, "RedirectURI creation lookup strategy cannot be null");
+    }
+    
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        if (redirectUriCreationStrategy == null) {
+            throw new ComponentInitializationException("redirectUriCreationStrategy cannot be null");
+        }
+    }
 
     /**
      * Begin an authorization request to the configured downstream OP. 
@@ -116,7 +204,7 @@ public class AuthorizationController {
      * @throws IOException throw if there is an error constructing an authz request.
      * @throws ExternalAuthenticationException 
      */
-    @GetMapping("/authz")
+    @GetMapping(AUTHORIZE_PATH_SEGMENT)
     public void authorizationRequest(@Nonnull final HttpServletRequest httpRequest, 
             @Nonnull final HttpServletResponse httpResponse) 
                     throws ServletException, IOException, ExternalAuthenticationException {
@@ -146,6 +234,17 @@ public class AuthorizationController {
             final String state = OIDCProxySupport.generateState(OIDCProxySupport.generateNonce(32), key);
             ((OIDCAuthenticationRequest)nestedPRC.getOutboundMessageContext().getMessage()).setState(new State(state));
             
+            //add redirect URI based on the request URL and any found in the OIDCAuthnContext
+            final URI redirectUri = redirectUriCreationStrategy.apply(httpRequest, nestedPRC);
+            if (redirectUri == null) {
+                log.error("Redirect URI could not be located or created from strategy");
+                httpRequest.setAttribute(ExternalAuthentication.AUTHENTICATION_ERROR_KEY, EventIds.INVALID_PROFILE_CTX);
+                ExternalAuthentication.finishExternalAuthentication(key, httpRequest, httpResponse);
+                return;
+            }
+            log.trace("Created redirect_uri '{}'", redirectUri);
+            ((OIDCAuthenticationRequest)nestedPRC.getOutboundMessageContext().getMessage()).setRedirectURI(redirectUri);
+            
         } else {
             log.error("Outbound Authorization message not found");
             httpRequest.setAttribute(ExternalAuthentication.AUTHENTICATION_ERROR_KEY, EventIds.INVALID_MESSAGE);
@@ -155,7 +254,8 @@ public class AuthorizationController {
         
         try {            
             if (oidcContext.getOutboundMessageHandler() != null) {
-                oidcContext.getOutboundMessageHandler().invoke(nestedPRC.getOutboundMessageContext());            }
+                oidcContext.getOutboundMessageHandler().invoke(nestedPRC.getOutboundMessageContext());            
+            }
             
             oidcContext.getEncodeMessageAction().execute(nestedPRC);
             // Handle error added by the EncodeMessage action. 
@@ -257,4 +357,6 @@ public class AuthorizationController {
         ExternalAuthentication.finishExternalAuthentication(key, httpRequest, httpResponse);
         
     }
+    
+    
 }
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultRedirectUriCreationFunction.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultRedirectUriCreationFunction.java
new file mode 100644
index 0000000..b67d742
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultRedirectUriCreationFunction.java
@@ -0,0 +1,222 @@
+/*
+ * 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 java.net.URISyntaxException;
+import java.util.Collections;
+import java.util.Set;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import javax.annotation.concurrent.ThreadSafe;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.http.client.utils.URIBuilder;
+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 net.shibboleth.idp.plugin.authn.oidc.rp.context.OAuth2ClientContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
+import net.shibboleth.utilities.java.support.annotation.ParameterName;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ *  Constructive, pure, function that returns a redirect_uri from one of (ordered):
+ *  <ol>
+ *      <li>A pre-registered redirect_uri on the {@link OAuth2ClientContext#getRedirectUriOverride()}. 
+ *      Or, if none are pre-registered;
+ *      <li>Derived from the HTTP Servlet request server parameters, checking the origin
+ *          against an allowed set of origins - to prevent Host header injection.
+ *   </ol>
+ *  
+ *  <p>Returns null if one can not be constructed.</p>
+ *  
+ *  <p>Is thread-safe and immutable</p> 
+ */
+ at ThreadSafe
+ at Immutable
+//TODO similar to that used in the Duo plugin.
+public class DefaultRedirectUriCreationFunction 
+                        implements BiFunction<HttpServletRequest, ProfileRequestContext, URI> {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultRedirectUriCreationFunction.class);
+    
+    /** The strategy used to lookup or create the {@link OAuth2ClientContext} for storing the client_id.*/
+    @Nonnull private Function<ProfileRequestContext, OAuth2ClientContext> oauth2ClientContextLookupStrategy;
+    
+    /** The path, excluding the context and servlet paths, to the Duo callback handler.*/
+    @Nonnull @NotEmpty private final String callbackServletPath;
+    
+    /** 
+     * A set of 'allowed' origins that can be used as the scheme, host, and port portion of the redirectURI.
+     * Can be null, if so a redirect_uri must be specified in the context tree.
+     */
+    @Nullable private Set<String> allowedOrigins;
+    
+    /**
+     * Constructor.
+     *
+     * @param callbackPath the path segment relative to the servlet path of the callback endpoint.
+     * @param origins the allowed origins to use if a redirect_uri is computed
+     */
+    public DefaultRedirectUriCreationFunction(
+            @Nonnull @NotEmpty @ParameterName(name="callbackPath") final String callbackPath,
+            @Nonnull @NotEmpty @ParameterName(name="allowedOrigins") @Nullable final Set<String> origins) {
+        
+        callbackServletPath = Constraint.isNotNull(callbackPath,"Duo Call back path can not be null");
+        // Default under OIDCPeerEntityContext in the outbound context (create true) under the nested PRC.
+        oauth2ClientContextLookupStrategy = new ChildContextLookup<>(OAuth2ClientContext.class).compose(
+                new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
+                        new OutboundMessageContextLookup()));
+        
+        if (origins == null) {
+            allowedOrigins = Collections.emptySet();
+        } else {
+            allowedOrigins = Collections.unmodifiableSet(origins);
+        }
+    }
+    
+    /**
+     * Set the allowed origins.
+     * 
+     * @param origins the origins
+     */
+    public void setAllowedOrigins(@Nullable final Set<String> origins) {
+        if (origins == null) {
+            allowedOrigins = Collections.emptySet();
+        }
+        allowedOrigins = Collections.unmodifiableSet(origins);
+    }
+
+    
+    /**
+     * Set the strategy to lookup the {@link OAuth2ClientContext} 
+     * from the {@link ProfileRequestContext}.
+     * 
+     * @param strgy the strategy.
+     */
+    public void setOAuth2ClientContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, OAuth2ClientContext> strgy) {
+
+        oauth2ClientContextLookupStrategy = Constraint.isNotNull(strgy, 
+                "OAuth2 client context lookup strategy cannot be null");
+    }
+
+    @Override
+    @Nullable public URI apply(@Nonnull final HttpServletRequest request, @Nonnull final ProfileRequestContext prc) {
+        
+        final OAuth2ClientContext context = oauth2ClientContextLookupStrategy.apply(prc);
+        if (context == null) {
+            log.warn("Could not locate the OAuth2 Client Context, can not compute redirect_uri");
+            return null;
+        }
+        if (context.getRedirectUriOverride() != null) {
+            return context.getRedirectUriOverride();
+        } 
+        
+        // Should be caught upstream, but dbl check.
+        if (allowedOrigins.isEmpty()) {
+            log.warn("Can not compute redirect_uri if allowed origins is empty");
+            return null;
+        }
+        
+        
+        try {
+            final URI redirectUri = buildURIIgnoreDefaultPorts(request.getScheme(),
+                    request.getServerName(),
+                    request.getServerPort(),
+                    request.getContextPath()+request.getServletPath()+callbackServletPath);
+            
+            final String origin = buildOrigin(redirectUri);
+            if (!allowedOrigins.contains(origin)) {
+                log.warn("The 'origin' of the computed redirect_uri ('{}') is not allowed. If permissible, add it "
+                        + "to the allowed origins property.",origin);
+                return null;
+            }
+            return redirectUri;
+        } catch (final URISyntaxException e) {
+            log.warn("Unable to create redirect_uri for OIDC authentication request", e);
+            return null;
+        }
+    }
+    
+    /**
+     * Builds the 'origin' (see RFC 6454) from given URI. Omits default or unknown ports. 
+     *  
+     * @param uri the URI to build the origin from
+     * @return the origin
+     * @throws URISyntaxException if there is an error getting information from the URI.
+     */
+    @Nonnull private String buildOrigin(@Nonnull final URI uri) throws URISyntaxException {    
+
+        if (uri.getPort() == -1) {
+            //is the default port (or is not defined), do not include
+            return new URI(String.format("%s://%s", uri.getScheme(),uri.getHost())).toString();
+        } else {
+            return new URI(String.format("%s://%s:%s", uri.getScheme(),uri.getHost(),uri.getPort())).toString();
+        }
+
+    }
+    
+    /**
+     * Build a {@link URI} from the given parameters. If the scheme is either
+     * 'http' or 'https' with their respective default port, the port is set to -1.
+     * 
+     * @param scheme the scheme
+     * @param host the hostname
+     * @param port the port
+     * @param path the path
+     * 
+     * @return a fully built URI from the given parameters.
+     * 
+     * @throws URISyntaxException if the URI can not be constructed.
+     */
+    @Nonnull private final URI buildURIIgnoreDefaultPorts(@Nonnull final String scheme, 
+            @Nonnull final String host, @Nonnull final int port, 
+            @Nonnull final String path) throws URISyntaxException {
+        
+        int usedPort = port;
+        if ("http".equalsIgnoreCase(scheme)) {
+            // ignore port iff using the default http port
+            if (port == 80) {
+                usedPort = -1;
+            }
+        } else if ("https".equalsIgnoreCase(scheme)) {
+            // ignore port iff using the default https port
+            if (port == 443) {
+                usedPort = -1;
+            }
+        }
+        return new URIBuilder()
+                .setScheme(scheme)
+                .setHost(host)
+                .setPort(usedPort)
+                .setPath(path)
+                .build();
+    }
+    
+}
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
index f2f5eaf..7536bfa 100644
--- 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
@@ -35,6 +35,7 @@ import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
  * 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.
  */
+//TODO move to checking profile config!
 public class DefaultResponseTypeLookupFunction implements Function<ProfileRequestContext, ResponseType> {
     
     /** The Default response type if none is selected.*/
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOAuth2ClientAuthenticationContext.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOAuth2ClientAuthenticationContext.java
new file mode 100644
index 0000000..9ff116a
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOAuth2ClientAuthenticationContext.java
@@ -0,0 +1,197 @@
+/*
+ * 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.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.oauth2.sdk.auth.ClientAuthentication;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OAuth2ClientContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.idp.profile.IdPEventIds;
+import net.shibboleth.oidc.authn.context.OAuth2ClientAuthenticationContext;
+import net.shibboleth.oidc.metadata.criterion.ClientIDCriterion;
+import net.shibboleth.oidc.security.impl.OAuth2ClientAuthenticationResolver;
+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;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+import net.shibboleth.utilities.java.support.resolver.ResolverException;
+
+/**
+ * An {@link AbstractProfileAction action} that resolves the client authentication method for the chosen 
+ * downstream provider (issuer). 
+ */
+public class InitializeOAuth2ClientAuthenticationContext extends AbstractProfileAction {
+
+    /** Class logger. */
+    @Nonnull
+    private final Logger log = LoggerFactory.getLogger(InitializeOAuth2ClientAuthenticationContext.class);
+    
+    /** The resolver to use to find the client authentication for the given (client_id). */
+    @NonnullAfterInit private OAuth2ClientAuthenticationResolver clientAuthenticationResovler;
+    
+    /** The strategy used to lookup or create the {@link OAuth2ClientContext}.*/
+    @Nonnull private Function<ProfileRequestContext, OAuth2ClientContext> oauth2ClientContextLookupStrategy;
+    
+    /** The strategy used to lookup or create the {@link OAuth2ClientContext} for storing the client authentication.*/
+    @Nonnull private 
+    Function<ProfileRequestContext, OAuth2ClientAuthenticationContext> oauth2ClientAuthenticationContextLookupStrategy;
+    
+    /** The stashed OAuth2 client context.*/
+    @Nullable private OAuth2ClientContext oauth2ClientContext;
+    
+    /** The stashed OAuth2 client authentication context.*/
+    @Nullable private OAuth2ClientAuthenticationContext oauth2ClientAuthenticationContext;
+
+    
+    /** Constructor.*/
+    public InitializeOAuth2ClientAuthenticationContext() {       
+        // Default under OIDCPeerEntityContext in the outbound context (create true).
+        oauth2ClientContextLookupStrategy = new ChildContextLookup<>(OAuth2ClientContext.class).compose(
+                new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
+                        new OutboundMessageContextLookup()));
+        
+        oauth2ClientAuthenticationContextLookupStrategy  = 
+                new ChildContextLookup<>(OAuth2ClientAuthenticationContext.class, true).compose(
+                new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
+                        new OutboundMessageContextLookup()));
+
+    }
+
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        if (clientAuthenticationResovler == null) {
+            throw new ComponentInitializationException("OAuth2 Client Authentication Resovler cannot be null");
+        }
+    }
+
+    /**
+     * Set the OAuth2 client authentication resolver.
+     * 
+     * @param resolver the resolver
+     */
+    public void setClientAuthenticationResolver(@Nonnull final OAuth2ClientAuthenticationResolver resolver) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+        
+        clientAuthenticationResovler = Constraint.isNotNull(resolver, 
+                "OAuth2 Client Authentication Resovler cannot be null");
+    }
+    
+    /**
+     * Set the strategy to lookup the {@link OAuth2ClientContext} 
+     * from the {@link ProfileRequestContext}.
+     * 
+     * @param strgy the strategy.
+     */
+    public void setOAuth2ClientContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, OAuth2ClientContext> strgy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+        oauth2ClientContextLookupStrategy = Constraint.isNotNull(strgy, 
+                "OAuth2 client context lookup strategy cannot be null");
+    }
+    
+    /**
+     * Set the strategy to lookup the {@link OAuth2ClientAuthenticationContext} 
+     * from the {@link ProfileRequestContext}.
+     * 
+     * @param strgy the strategy.
+     */
+    public void setOAuth2ClientAuthenticationContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, OAuth2ClientAuthenticationContext> strgy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+        oauth2ClientAuthenticationContextLookupStrategy = Constraint.isNotNull(strgy, 
+                "OAuth2 client authentication context lookup strategy cannot be null");
+    }
+    
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }
+        
+        oauth2ClientContext = oauth2ClientContextLookupStrategy.apply(profileRequestContext);
+        if (oauth2ClientContext == null) {
+            log.error("{} No OAuth2 client context found or created", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            return false;
+        }
+        oauth2ClientAuthenticationContext = 
+                oauth2ClientAuthenticationContextLookupStrategy.apply(profileRequestContext);
+        if (oauth2ClientAuthenticationContext == null) {
+            log.error("{} No OAuth2 client authentication context found or created", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            return false;
+        }
+        
+        return true;
+        
+    }
+    
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        super.doExecute(profileRequestContext);
+        
+        try {
+            final ClientAuthentication clientAuth = 
+                    clientAuthenticationResovler.resolveSingle(
+                            new CriteriaSet(new ClientIDCriterion(new ClientID(oauth2ClientContext.getClientId()))));
+            
+            if (clientAuth == null) {
+                log.error("{} No client authentication mode found from resolver", getLogPrefix());
+                ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CONFIG);
+                return;
+            }
+            
+            log.trace("{} Found client authentication mode '{}' for client '{}'", 
+                    getLogPrefix(), clientAuth.getMethod(), oauth2ClientContext.getClientId());
+            
+            oauth2ClientAuthenticationContext.setClientAuthentication(clientAuth);           
+            log.debug("{} Initialized OAuth2 Client Authentication Context for client '{}'", 
+                    getLogPrefix(), oauth2ClientContext.getClientId());
+        } catch (final ResolverException e) {
+            log.warn("{} client context could not be initialized", getLogPrefix(), e);
+            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CONFIG);
+            return;
+        }
+    }
+    
+    
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOAuth2ClientContext.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOAuth2ClientContext.java
new file mode 100644
index 0000000..6415634
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOAuth2ClientContext.java
@@ -0,0 +1,185 @@
+/*
+ * 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 java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.RedirectUriLookupFunction;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OAuth2ClientContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.idp.profile.IdPEventIds;
+import net.shibboleth.oidc.metadata.criterion.ClientIDCriterion;
+import net.shibboleth.oidc.security.impl.OAuth2ClientAuthenticationResolver;
+import net.shibboleth.oidc.security.impl.OAuth2ClientIdentifierResolver;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+import net.shibboleth.utilities.java.support.resolver.ResolverException;
+
+/**
+ * An {@link AbstractProfileAction action} that resolves the client identifier for the chosen 
+ * downstream provider (issuer). 
+ */
+public class InitializeOAuth2ClientContext extends AbstractProfileAction {
+
+    /** Class logger. */
+    @Nonnull
+    private final Logger log = LoggerFactory.getLogger(InitializeOAuth2ClientContext.class);
+    
+    /** The resolver to use to find the client identifier (client_id). */
+    @NonnullAfterInit private OAuth2ClientIdentifierResolver clientIdResovler;
+    
+    /** The strategy used to lookup or create the {@link OAuth2ClientContext} for storing the client_id.*/
+    @NonnullAfterInit private Function<ProfileRequestContext, OAuth2ClientContext> oauth2ClientContextLookupStrategy;
+    
+    /** The stashed OAuth2 client context.*/
+    @Nullable private OAuth2ClientContext oauth2ClientContext;
+
+    /** A redirect_uri lookup strategy which can pull out an override redirect_uri from the profile request context.*/
+    @Nonnull private  Function<ProfileRequestContext, URI> redirectUriOverrideLookupStrategy;
+    
+    /** Constructor.*/
+    public InitializeOAuth2ClientContext() {       
+        // Default under OIDCPeerEntityContext in the outbound context (create true).
+        oauth2ClientContextLookupStrategy = new ChildContextLookup<>(OAuth2ClientContext.class, true).compose(
+                new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
+                        new OutboundMessageContextLookup()));
+        
+        redirectUriOverrideLookupStrategy = new RedirectUriLookupFunction();
+    }
+    
+    /**
+     * Set the redirect_uri lookup strategy to locate an explicitly set redirect_uri.
+     * 
+     * @param strategy the strategy.
+     */
+    public void setRedirectUriOverrideLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, URI> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        redirectUriOverrideLookupStrategy = 
+                Constraint.isNotNull(strategy, "Redirect URI lookup strategy can not be null");
+    }
+    
+    
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        if (clientIdResovler == null) {
+            throw new ComponentInitializationException("OAuth2 Client Resovler cannot be null");
+        }
+    }
+    
+    /**
+     * Set the OAuth2 client identifier (client_id) resolver.
+     * 
+     * @param resolver the resolver
+     */
+    public void setClientIdResolver(@Nonnull final OAuth2ClientIdentifierResolver resolver) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+        
+        clientIdResovler = Constraint.isNotNull(resolver, "OAuth2 Client Resovler cannot be null");
+    }
+
+    
+    /**
+     * Set the strategy to lookup the {@link OAuth2ClientContext} 
+     * from the {@link ProfileRequestContext}.
+     * 
+     * @param strgy the strategy.
+     */
+    public void setOAuth2ClientContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, OAuth2ClientContext> strgy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+        oauth2ClientContextLookupStrategy = Constraint.isNotNull(strgy, 
+                "OAuth2 client context lookup strategy cannot be null");
+    }
+    
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }
+        
+        oauth2ClientContext = oauth2ClientContextLookupStrategy.apply(profileRequestContext);
+        if (oauth2ClientContext == null) {
+            log.error("{} No OAuth2 client context found or created", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            return false;
+        }
+        
+        return true;        
+    }
+    
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        super.doExecute(profileRequestContext);
+
+        try {
+            final String clientId = clientIdResovler.resolveSingle(profileRequestContext);
+            if (clientId == null) {
+                log.error("{} No client_id found from resolver", getLogPrefix());
+                ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CONFIG);
+                return;
+            }
+            oauth2ClientContext.setClientId(clientId);
+            
+            final URI redirectUri = redirectUriOverrideLookupStrategy.apply(profileRequestContext);
+            if (redirectUri != null) {
+                log.debug("{} Redirect_uri has been explicitly set as '{}'", getLogPrefix(), redirectUri);
+                oauth2ClientContext.setRedirectUriOverride(redirectUri);
+            }             
+            log.debug("{} Initialized OAuth2 Client Context for client '{}'", getLogPrefix(), clientId);
+        } catch (final ResolverException e) {
+            log.warn("{} client context could not be initialized", getLogPrefix(), e);
+            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CONFIG);
+            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 00e5dc0..30d6522 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
@@ -50,10 +50,7 @@ public class InitializeOutboundAuthorizationRequestMessageContext extends Abstra
     
     /** The {@link OIDCPeerEntityContext} to base the outbound context on. */
     @Nullable private OIDCPeerEntityContext peerEntityCtx;
-    
-    /** The stashed inbound client metadata context.*/
-    @Nullable private OIDCMetadataContext inboundClientMetadata;
-        
+            
     /** Strategy function to lookup the {@link OIDCMetadataContext} that represents this client during
      * communication with the given OIDC peer. */
     @Nonnull 
@@ -125,15 +122,6 @@ public class InitializeOutboundAuthorizationRequestMessageContext extends Abstra
 
         peerEntityCtx = (OIDCPeerEntityContext) identifyingCtx;
         
-        inboundClientMetadata = 
-                oidcClientMetadataCtxLookupStrategy.apply(profileRequestContext);
-        
-        if (inboundClientMetadata == null) {
-            log.debug("{} No OIDC inbound client metadata context", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CTX);
-            return false;
-        }
-        
         return true;
         
     }
@@ -159,10 +147,6 @@ public class InitializeOutboundAuthorizationRequestMessageContext extends Abstra
             
         }
 
-        final OIDCMetadataContext outboundClientMetadata = new OIDCMetadataContext();
-        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/MapBackedMemoryStorageServiceFactoryBean.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/MapBackedMemoryStorageServiceFactoryBean.java
new file mode 100644
index 0000000..10f5328
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/MapBackedMemoryStorageServiceFactoryBean.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.Collections;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+
+import org.opensaml.storage.impl.MemoryStorageService;
+import org.springframework.beans.factory.config.AbstractFactoryBean;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+
+import net.shibboleth.utilities.java.support.annotation.ParameterName;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+
+/** 
+ * A factory that creates and populates a map backed MemoryStorageService from an injected map.
+ * Values which are *not* Strings are serialized into JSON using the supplied object mapper before they are added
+ * to the storage service. Values which are already Strings are just passed in without modification.
+ */
+public class MapBackedMemoryStorageServiceFactoryBean extends AbstractFactoryBean<MemoryStorageService> {
+    
+    /** The Map to inject values into the storage service with.*/
+    @Nonnull private final Map<String, Object> mapToInject;
+    
+    /** The ID to give the created memory service.*/
+    @Nonnull private final String identifier;
+    
+    /** The partition name to use in the injected storage service.*/
+    @Nonnull private final String storageServiceContext;
+    
+    /** JSON object mapper. */
+    @Nonnull @GuardedBy("this") private ObjectMapper objectMapper;
+    
+    /**
+     * 
+     * Constructor.
+     *
+     * @param map the issuer to client_id map to base the storage service off
+     * @param id The ID to give the created memory service
+     * @param context The partition name to use in the injected storage service
+     */
+    public MapBackedMemoryStorageServiceFactoryBean(@ParameterName(name="context") @Nonnull final String context,
+            @ParameterName(name="map") @Nullable final Map<String, Object> map,
+            @ParameterName(name="id") @Nonnull final String id) {
+        if (map == null) {
+            mapToInject = Collections.emptyMap();
+        } else {
+            mapToInject = Collections.unmodifiableMap(map);
+        }
+        identifier = Constraint.isNotEmpty(id, "Memory storage service must have an ID");
+        storageServiceContext = Constraint.isNotEmpty(context, "Storage service context can not be null");
+        setObjectMapper(new ObjectMapper());
+    }
+    
+    /**
+     * Set the JSON object mapper to use to convert the client authentication details to a JSON string.
+     * 
+     * @param mapper the object mapper
+     */
+    public synchronized void setObjectMapper(@Nonnull final ObjectMapper mapper) {
+        objectMapper = Constraint.isNotNull(mapper, "Object mapper can not be null");
+    }
+    
+    /**
+     * Get the JSON object mapper.
+     * 
+     * @return the object mapper
+     */
+    private synchronized ObjectMapper getObjectMapper() {
+        return objectMapper;
+    }
+
+    @Override
+    public Class<?> getObjectType() {
+        return MemoryStorageService.class;
+    }
+
+    @Override
+    protected MemoryStorageService createInstance() throws Exception {
+        final MemoryStorageService service = new MemoryStorageService();
+        service.setId(identifier);
+        service.initialize();
+        // One writer for all records, is safe.
+        final ObjectWriter ow = getObjectMapper().writer();        
+        for (final Map.Entry<String, Object> entry : mapToInject.entrySet()) {
+            String objectAsString = null;
+            if (entry.getValue() instanceof String) {
+                objectAsString = (String) entry.getValue();
+            } else {
+                objectAsString = ow.writeValueAsString(entry.getValue());
+            }
+            service.create(storageServiceContext, entry.getKey(), objectAsString, null);
+        }
+        return service;
+    }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/OAuth2ClientAuthenticationContainer.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/OAuth2ClientAuthenticationContainer.java
new file mode 100644
index 0000000..463c1af
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/OAuth2ClientAuthenticationContainer.java
@@ -0,0 +1,147 @@
+/*
+ * 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.spring.impl;
+
+import javax.annotation.Nonnull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Objects;
+
+import net.shibboleth.ext.spring.service.AbstractServiceableComponent;
+import net.shibboleth.oidc.metadata.ProviderMetadataResolver;
+import net.shibboleth.oidc.security.impl.OAuth2ClientAuthenticationResolver;
+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;
+
+/**
+ * This class is a sortable container of {@link OAuth2ClientAuthenticationResolver}s, wrapped into a serviceable component.
+ */
+public class OAuth2ClientAuthenticationContainer extends AbstractServiceableComponent<OAuth2ClientAuthenticationResolver>
+                                               implements Comparable<OAuth2ClientAuthenticationContainer> {
+
+    /** If we autogenerate a sort key it comes from this count. */
+    private static int sortKeyValue;
+
+    /** Class logger. */
+    private final Logger log = LoggerFactory.getLogger(OAuth2ClientAuthenticationContainer.class);
+
+    /** The embedded resolver. */
+    @NonnullAfterInit
+    private OAuth2ClientAuthenticationResolver resolver;
+
+    /** The key by which we sort the provider. */
+    @NonnullAfterInit
+    private Integer sortKey;
+
+    /**
+     * Set the sort key.
+     * 
+     * @param key what to set
+     */
+    public void setSortKey(final int key) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        sortKey = key;
+    }
+
+    /**
+     * Set the {@link ProviderMetadataResolver} to embed.
+     * 
+     * @param theResolver The {@link ProviderMetadataResolver} to embed.
+     */
+    @Nonnull
+    public void setEmbeddedResolver(@Nonnull final OAuth2ClientAuthenticationResolver theResolver) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        resolver = Constraint.isNotNull(theResolver, "OAuth2ClientAuthenticationResolver cannot be null");
+    }
+
+    /**
+     * Return what we are build around. Used for testing.
+     * 
+     * @return the parameter we got as a constructor
+     */
+    @Nonnull
+    public OAuth2ClientAuthenticationResolver getEmbeddedResolver() {
+        return resolver;
+    }
+
+
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        setId(resolver.getId());
+        super.doInitialize();
+        if (null == resolver) {
+            throw new ComponentInitializationException("OAuth2ClientAuthenticationResolver cannot be null");
+        }
+
+        if (null == sortKey) {
+            synchronized (OAuth2ClientAuthenticationContainer.class) {
+                sortKeyValue++;
+                setSortKey(sortKeyValue);
+            }
+            log.info("Top level OAuth2ClientAuthentication Provider '{}' "
+                    + "did not have a sort key; giving it value '{}'", getId(),
+                    sortKey);
+        }
+    }
+    
+
+    @Override
+    @Nonnull
+    public OAuth2ClientAuthenticationResolver getComponent() {
+        return getEmbeddedResolver();
+    }
+
+    @Override
+    public int compareTo(final OAuth2ClientAuthenticationContainer other) {
+        ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
+        final int result = sortKey.compareTo(other.sortKey);
+        if (result != 0) {
+            return result;
+        }
+        if (equals(other)) {
+            return 0;
+        }
+        return getId().compareTo(other.getId());
+    }
+
+    /**
+     * {@inheritDoc}. We are within a spring context and so equality can be determined by ID, however we also test by
+     * sortKey just in case.
+     */
+    @Override
+    public boolean equals(final Object other) {
+        if (null == other) {
+            return false;
+        }
+        if (!(other instanceof OAuth2ClientAuthenticationContainer)) {
+            return false;
+        }
+        final OAuth2ClientAuthenticationContainer otherRp = (OAuth2ClientAuthenticationContainer) other;
+
+        return Objects.equal(otherRp.sortKey, sortKey) && Objects.equal(getId(), otherRp.getId());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(sortKey, getId());
+    }
+}
\ No newline at end of file
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/OAuth2ClientAuthenticationResolverServiceStrategy.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/OAuth2ClientAuthenticationResolverServiceStrategy.java
new file mode 100644
index 0000000..7b7ffaa
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/OAuth2ClientAuthenticationResolverServiceStrategy.java
@@ -0,0 +1,65 @@
+/*
+ * 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.spring.impl;
+
+import java.util.Collection;
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+
+import org.springframework.beans.factory.BeanCreationException;
+import org.springframework.context.ApplicationContext;
+
+import net.shibboleth.oidc.security.impl.OAuth2ClientAuthenticationResolver;
+import net.shibboleth.utilities.java.support.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.service.ServiceException;
+import net.shibboleth.utilities.java.support.service.ServiceableComponent;
+
+public class OAuth2ClientAuthenticationResolverServiceStrategy extends AbstractIdentifiableInitializableComponent
+        implements Function<ApplicationContext, ServiceableComponent<OAuth2ClientAuthenticationResolver>> {
+
+    @Override
+    public ServiceableComponent<OAuth2ClientAuthenticationResolver> apply(
+            @Nullable final ApplicationContext appContext) {
+        final Collection<OAuth2ClientAuthenticationContainer> resolvers =
+                appContext.getBeansOfType(OAuth2ClientAuthenticationContainer.class).values();
+
+        if (resolvers.isEmpty()) {
+            throw new ServiceException(
+                    "Reload did not produce any bean of type " + OAuth2ClientAuthenticationContainer.class.getName());
+        }
+        if (1 == resolvers.size()) {
+            // done
+            return resolvers.iterator().next();
+        }
+        // initialize so we can sort
+        for (final OAuth2ClientAuthenticationContainer resolver : resolvers) {
+            try {
+                resolver.initialize();
+            } catch (final ComponentInitializationException e) {
+                throw new BeanCreationException("Could not preinitialize " 
+                        + resolver.getId(), e);
+            }
+        }
+
+        throw new BeanCreationException("could not preinitialize the Client Authentication Provider, "
+                + "mulitple Client Authentication Providers not supported");
+    }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/OAuth2ClientIdentifierContainer.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/OAuth2ClientIdentifierContainer.java
new file mode 100644
index 0000000..601200d
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/OAuth2ClientIdentifierContainer.java
@@ -0,0 +1,148 @@
+/*
+ * 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.spring.impl;
+
+import javax.annotation.Nonnull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Objects;
+
+import net.shibboleth.ext.spring.service.AbstractServiceableComponent;
+import net.shibboleth.oidc.metadata.ProviderMetadataResolver;
+import net.shibboleth.oidc.security.impl.OAuth2ClientAuthenticationResolver;
+import net.shibboleth.oidc.security.impl.OAuth2ClientIdentifierResolver;
+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;
+
+/**
+ * This class is a sortable container of {@link OAuth2ClientAuthenticationResolver}s, wrapped into a serviceable component.
+ */
+public class OAuth2ClientIdentifierContainer extends AbstractServiceableComponent<OAuth2ClientIdentifierResolver>
+                                               implements Comparable<OAuth2ClientIdentifierContainer> {
+
+    /** If we autogenerate a sort key it comes from this count. */
+    private static int sortKeyValue;
+
+    /** Class logger. */
+    private final Logger log = LoggerFactory.getLogger(OAuth2ClientIdentifierContainer.class);
+
+    /** The embedded resolver. */
+    @NonnullAfterInit
+    private OAuth2ClientIdentifierResolver resolver;
+
+    /** The key by which we sort the provider. */
+    @NonnullAfterInit
+    private Integer sortKey;
+
+    /**
+     * Set the sort key.
+     * 
+     * @param key what to set
+     */
+    public void setSortKey(final int key) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        sortKey = key;
+    }
+
+    /**
+     * Set the {@link OAuth2ClientIdentifierResolver} to embed.
+     * 
+     * @param theResolver The {@link OAuth2ClientIdentifierResolver} to embed.
+     */
+    @Nonnull
+    public void setEmbeddedResolver(@Nonnull final OAuth2ClientIdentifierResolver theResolver) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        resolver = Constraint.isNotNull(theResolver, "OAuth2ClientAuthenticationResolver cannot be null");
+    }
+
+    /**
+     * Return what we are built around. Used for testing.
+     * 
+     * @return the embedded resolver.
+     */
+    @Nonnull
+    public OAuth2ClientIdentifierResolver getEmbeddedResolver() {
+        return resolver;
+    }
+
+
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        setId(resolver.getId());
+        super.doInitialize();
+        if (null == resolver) {
+            throw new ComponentInitializationException("OAuth2ClientAuthenticationResolver cannot be null");
+        }
+
+        if (null == sortKey) {
+            synchronized (OAuth2ClientIdentifierContainer.class) {
+                sortKeyValue++;
+                setSortKey(sortKeyValue);
+            }
+            log.info("Top level OAuth2ClientAuthentication Provider '{}' "
+                    + "did not have a sort key; giving it value '{}'", getId(),
+                    sortKey);
+        }
+    }
+    
+
+    @Override
+    @Nonnull
+    public OAuth2ClientIdentifierResolver getComponent() {
+        return getEmbeddedResolver();
+    }
+
+    @Override
+    public int compareTo(final OAuth2ClientIdentifierContainer other) {
+        ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
+        final int result = sortKey.compareTo(other.sortKey);
+        if (result != 0) {
+            return result;
+        }
+        if (equals(other)) {
+            return 0;
+        }
+        return getId().compareTo(other.getId());
+    }
+
+    /**
+     * {@inheritDoc}. We are within a spring context and so equality can be determined by ID, however we also test by
+     * sortKey just in case.
+     */
+    @Override
+    public boolean equals(final Object other) {
+        if (null == other) {
+            return false;
+        }
+        if (!(other instanceof OAuth2ClientIdentifierContainer)) {
+            return false;
+        }
+        final OAuth2ClientIdentifierContainer otherRp = (OAuth2ClientIdentifierContainer) other;
+
+        return Objects.equal(otherRp.sortKey, sortKey) && Objects.equal(getId(), otherRp.getId());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(sortKey, getId());
+    }
+}
\ No newline at end of file
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/OAuth2ClientIdentifierResolverServiceStrategy.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/OAuth2ClientIdentifierResolverServiceStrategy.java
new file mode 100644
index 0000000..0820475
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/OAuth2ClientIdentifierResolverServiceStrategy.java
@@ -0,0 +1,67 @@
+/*
+ * 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.spring.impl;
+
+import java.util.Collection;
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+
+import org.springframework.beans.factory.BeanCreationException;
+import org.springframework.context.ApplicationContext;
+
+import net.shibboleth.oidc.security.impl.OAuth2ClientAuthenticationResolver;
+import net.shibboleth.oidc.security.impl.OAuth2ClientIdentifierResolver;
+import net.shibboleth.utilities.java.support.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.service.ServiceException;
+import net.shibboleth.utilities.java.support.service.ServiceableComponent;
+
+public class OAuth2ClientIdentifierResolverServiceStrategy extends AbstractIdentifiableInitializableComponent
+        implements Function<ApplicationContext, ServiceableComponent<OAuth2ClientIdentifierResolver>> {
+
+    @Override
+    public ServiceableComponent<OAuth2ClientIdentifierResolver> apply(
+            @Nullable final ApplicationContext appContext) {
+        
+        final Collection<OAuth2ClientIdentifierContainer> resolvers =
+                appContext.getBeansOfType(OAuth2ClientIdentifierContainer.class).values();
+
+        if (resolvers.isEmpty()) {
+            throw new ServiceException(
+                    "Reload did not produce any bean of type " + OAuth2ClientAuthenticationContainer.class.getName());
+        }
+        if (1 == resolvers.size()) {
+            // done
+            return resolvers.iterator().next();
+        }
+        // initialize so we can sort
+        for (final OAuth2ClientIdentifierContainer resolver : resolvers) {
+            try {
+                resolver.initialize();
+            } catch (final ComponentInitializationException e) {
+                throw new BeanCreationException("Could not preinitialize the Client Identifer Provider " 
+                        + resolver.getId(), e);
+            }
+        }
+
+        throw new BeanCreationException("Could not preinitialize the Client Identifer Provider"
+                + ", mulitple client identifer providers not supported");
+    }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/ReloadingOAuth2ClientAuthenticationProvider.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/ReloadingOAuth2ClientAuthenticationProvider.java
new file mode 100644
index 0000000..62dee46
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/ReloadingOAuth2ClientAuthenticationProvider.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.spring.impl;
+
+import java.util.Collections;
+
+import javax.annotation.Nonnull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
+
+import net.shibboleth.oidc.security.impl.OAuth2ClientAuthenticationResolver;
+import net.shibboleth.utilities.java.support.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+import net.shibboleth.utilities.java.support.resolver.ResolverException;
+import net.shibboleth.utilities.java.support.service.ReloadableService;
+import net.shibboleth.utilities.java.support.service.ServiceableComponent;
+
+
+/**
+ * A service interface to implement the {@link OAuth2ClientAuthenticationResolver}.
+ */
+public class ReloadingOAuth2ClientAuthenticationProvider extends AbstractIdentifiableInitializableComponent 
+    implements OAuth2ClientAuthenticationResolver {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(ReloadingOAuth2ClientAuthenticationProvider.class);
+    
+    /** The service which manages the reloading. */
+    private final ReloadableService<OAuth2ClientAuthenticationResolver> service;
+    
+    /**
+     * Constructor.
+     * 
+     * @param resolverService the service which will manage the loading.
+     */
+    public ReloadingOAuth2ClientAuthenticationProvider(
+            @Nonnull final ReloadableService<OAuth2ClientAuthenticationResolver> resolverService) {
+        service = Constraint.isNotNull(resolverService, "ProviderMetadataResolver Service cannot be null");
+    }
+
+    @Override
+    public Iterable<ClientAuthentication> resolve(@Nonnull final CriteriaSet criteria) throws ResolverException {
+        ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
+        ServiceableComponent<OAuth2ClientAuthenticationResolver> component = null;
+        try {
+            component = service.getServiceableComponent();
+            if (null == component) {
+                log.error("OAuth2ClientAuthenticationResolver '{}': Error accessing underlying source: "
+                        + "Invalid configuration.", getId());
+            } else {
+                final OAuth2ClientAuthenticationResolver resolver = component.getComponent();
+                return resolver.resolve(criteria);
+            }
+        } catch (final ResolverException e) {
+            log.error("OAuth2ClientAuthenticationResolver '{}': Error during resolution", getId(), e);
+        } finally {
+            if (null != component) {
+                component.unpinComponent();
+            }
+        }
+        return Collections.emptySet();
+    }
+
+    @Override
+    public ClientAuthentication resolveSingle(@Nonnull final CriteriaSet criteria) throws ResolverException {
+        ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
+        ServiceableComponent<OAuth2ClientAuthenticationResolver> component = null;
+        try {
+            component = service.getServiceableComponent();
+            if (null == component) {
+                log.error("ReloadingProviderMetadataProvider '{}': Error accessing underlying source: "
+                        + "Invalid configuration.", getId());
+            } else {
+                final OAuth2ClientAuthenticationResolver resolver = component.getComponent();
+                return resolver.resolveSingle(criteria);
+            }
+        } catch (final ResolverException e) {
+            log.error("ProviderMetadataResolver '{}': Error during resolution", getId(), e);
+        } finally {
+            if (null != component) {
+                component.unpinComponent();
+            }
+        }
+        return null;
+    }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/ReloadingOAuth2ClientIdentifierProvider.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/ReloadingOAuth2ClientIdentifierProvider.java
new file mode 100644
index 0000000..5d4ee68
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/ReloadingOAuth2ClientIdentifierProvider.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.spring.impl;
+
+import java.util.Collections;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.oidc.security.impl.OAuth2ClientIdentifierResolver;
+import net.shibboleth.utilities.java.support.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.resolver.ResolverException;
+import net.shibboleth.utilities.java.support.service.ReloadableService;
+import net.shibboleth.utilities.java.support.service.ServiceableComponent;
+
+
+/**
+ * A service interface to implement the {@link OAuth2ClientIdentifierResolver}.
+ */
+public class ReloadingOAuth2ClientIdentifierProvider extends AbstractIdentifiableInitializableComponent 
+    implements OAuth2ClientIdentifierResolver {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(ReloadingOAuth2ClientIdentifierProvider.class);
+    
+    /** The service which manages the reloading. */
+    private final ReloadableService<OAuth2ClientIdentifierResolver> service;
+    
+    /**
+     * Constructor.
+     * 
+     * @param resolverService the service which will manage the loading.
+     */
+    public ReloadingOAuth2ClientIdentifierProvider(
+            @Nonnull final ReloadableService<OAuth2ClientIdentifierResolver> resolverService) {
+        service = Constraint.isNotNull(resolverService, "ProviderMetadataResolver Service cannot be null");
+    }
+
+    @Override
+    public Iterable<String> resolve(@Nonnull final ProfileRequestContext criteria) throws ResolverException {
+        ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
+        ServiceableComponent<OAuth2ClientIdentifierResolver> component = null;
+        try {
+            component = service.getServiceableComponent();
+            if (null == component) {
+                log.error("OAuth2ClientAuthenticationResolver '{}': Error accessing underlying source: "
+                        + "Invalid configuration.", getId());
+            } else {
+                final OAuth2ClientIdentifierResolver resolver = component.getComponent();
+                return resolver.resolve(criteria);
+            }
+        } catch (final ResolverException e) {
+            log.error("OAuth2ClientAuthenticationResolver '{}': Error during resolution", getId(), e);
+        } finally {
+            if (null != component) {
+                component.unpinComponent();
+            }
+        }
+        return Collections.emptySet();
+    }
+
+    @Override
+    public String resolveSingle(@Nonnull final ProfileRequestContext criteria) throws ResolverException {
+        ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
+        ServiceableComponent<OAuth2ClientIdentifierResolver> component = null;
+        try {
+            component = service.getServiceableComponent();
+            if (null == component) {
+                log.error("ReloadingProviderMetadataProvider '{}': Error accessing underlying source: "
+                        + "Invalid configuration.", getId());
+            } else {
+                final OAuth2ClientIdentifierResolver resolver = component.getComponent();
+                return resolver.resolveSingle(criteria);
+            }
+        } catch (final ResolverException e) {
+            log.error("ProviderMetadataResolver '{}': Error during resolution", getId(), e);
+        } finally {
+            if (null != component) {
+                component.unpinComponent();
+            }
+        }
+        return null;
+    }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index b464d4d..f9b0fc8 100644
--- a/idp-oidc-rp-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/idp-oidc-rp-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -14,6 +14,46 @@
     The default template shows an incomplete example authentication flow descriptor which can be
     removed if not needed 
     -->
+    
+    <!-- Functions use by the flow and global beans -->
+       
+    <bean id="shibboleth.ChildLookup.OIDCProviderMetadataContext"
+        class="org.opensaml.messaging.context.navigate.ChildContextLookup"
+        c:type="#{ T(net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext) }" />
+        
+    <bean id="shibboleth.ChildLookup.OAuth2ClientContext"
+        class="org.opensaml.messaging.context.navigate.ChildContextLookup"
+        c:type="#{ T(net.shibboleth.idp.plugin.authn.oidc.rp.context.OAuth2ClientContext) }" />
+                       
+    <bean id="shibboleth.ChildLookup.OIDCPeerEntityContext"
+        class="org.opensaml.messaging.context.navigate.ChildContextLookup"
+        c:type="#{ T(net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext) }" />
+        
+    <bean id="shibboleth.ChildLookup.OIDCProviderMetadataContextFromOutbound" parent="shibboleth.Functions.Compose">
+        <constructor-arg name="g">
+            <ref bean="shibboleth.ChildLookup.OIDCProviderMetadataContext"/>
+        </constructor-arg>
+        <constructor-arg name="f">
+            <ref bean="shibboleth.ChildLookup.OIDCPeerEntityFromOutbound"/>
+        </constructor-arg>
+    </bean>
+    
+    <bean id="shibboleth.ChildLookup.OAuth2ClientContextFromOutbound" parent="shibboleth.Functions.Compose">
+        <constructor-arg name="g">
+            <ref bean="shibboleth.ChildLookup.OAuth2ClientContext"/>
+        </constructor-arg>
+        <constructor-arg name="f">
+            <ref bean="shibboleth.ChildLookup.OIDCPeerEntityFromOutbound"/>
+        </constructor-arg>
+    </bean>
+    
+    <!-- Find OIDCPeerEntity Context under outbound message -->
+    <bean id="shibboleth.ChildLookup.OIDCPeerEntityFromOutbound" parent="shibboleth.Functions.Compose"
+                c:g-ref="shibboleth.ChildLookup.OIDCPeerEntityContext"
+                c:f-ref="shibboleth.MessageContextLookup.Outbound" />
+        
+        
+    <!-- The authentication flow descriptor -->
   
     <bean id="authn/OIDCRelyingParty" parent="shibboleth.AuthenticationFlow"
             p:order="%{idp.authn.oidc.rp.order:1000}"
@@ -98,8 +138,25 @@
     </bean>
     
     <!-- Controller implementation -->
-    <bean id="shibboleth.oidc.rp.OpenIDConnectStartServlet"
+    <bean id="shibboleth.authn.OIDC.externalServletPath" class="java.lang.String"
+        c:_0="%{idp.authn.oidc.rp.externalAuthnPath:/Authn/OIDC/RP}">
+    </bean>
+
+    <!-- location of the OIDC servlet authorization endpoint releative to the servlet -->
+    <bean id="shibboleth.authn.OIDC.externalAuthnPath" class="java.lang.String"
+        c:_0="servletRelative:#{getObject('shibboleth.authn.OIDC.externalServletPath')}#{T(net.shibboleth.idp.plugin.authn.oidc.rp.impl.AuthorizationController).AUTHORIZE_PATH_SEGMENT}" />
+
+    
+    
+    <bean id="shibboleth.oidc.rp.AuthorizationController"
+        p:redirectUriCreationStrategy="#{getObject('shibboleth.oidc.rp.RedirectUriCreationStrategy') ?: getObject('shibboleth.oidc.rp.DefaultRedirectUriCreationStrategy')}"
         class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.AuthorizationController" />
+        
+        
+    <bean id="shibboleth.oidc.rp.DefaultRedirectUriCreationStrategy" 
+        c:callbackPath="#{getObject('shibboleth.authn.OIDC.externalServletPath')}/callback"
+        c:allowedOrigins="%{idp.oidc.rp.redirecturl.allowedOrigins:}"
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.DefaultRedirectUriCreationFunction"/>
        
     <!-- TODO these bean names need to change - otherwise they may class with the OP plugin if installed
     at the same time -->
@@ -145,40 +202,72 @@
     </bean>
     
     
-    <!-- OIDC client information resolver service beans. -->
-    
+    <!-- Client authentication resolvers -->
     <!--  TODO add this back? depends-on="shibboleth.AttributeResolverService"-->
-
-    <bean id="shibboleth.oidc.rp.ClientInformationResolverService"
+    
+    <bean id="shibboleth.oidc.rp.ClientAuthenticationResolverService"
         class="net.shibboleth.ext.spring.service.ReloadableSpringService"
-        p:serviceConfigurations-ref="ExtendedClientInformationResolverResources"
+        p:serviceConfigurations-ref="ExtendedClientAuthenticationResolverResources"
         p:failFast="%{idp.service.clientinfo.failFast:%{idp.service.failFast:false}}"
         p:reloadCheckDelay="%{idp.service.clientinfo.checkInterval:PT0S}"
         p:beanPostProcessors-ref="shibboleth.IdentifiableBeanPostProcessor"
         p:beanFactoryPostProcessors-ref="shibboleth.PropertySourcesPlaceholderConfigurer">
         <constructor-arg name="claz"
-            value="net.shibboleth.oidc.metadata.ClientInformationResolver" />
+            value="net.shibboleth.oidc.security.impl.OAuth2ClientAuthenticationResolver" />
         <constructor-arg name="strategy">
-            <bean class="net.shibboleth.oidc.profile.spring.relyingparty.metadata.impl.ClientInformationResolverServiceStrategy" />
+            <bean class="net.shibboleth.idp.plugin.authn.oidc.rp.spring.impl.OAuth2ClientAuthenticationResolverServiceStrategy" />
         </constructor-arg>
     </bean>
-    <util:list id="shibboleth.DefaultClientInformationResolverResources">
-        <value>%{idp.home}/conf/authn/oidc-clientinfo-resolvers.xml</value> <!-- should be a conditional:? -->
+    
+    <bean id ="ExtendedClientAuthenticationResolverResources" class="net.shibboleth.ext.spring.factory.CombiningListFactoryBean"
+            p:firstList="#{getObject('%{idp.service.clientinfo.resources:shibboleth.ClientInformationResolverResources}'.trim()) ?:
+                getObject('shibboleth.DefaultClientAuthenticationResolverResources')}">
+        <property name="secondList">
+            <util:list >
+                <value>classpath:/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/clientauthentication-resolver-system.xml</value>
+            </util:list>
+        </property>
+    </bean>
+    <util:list id="shibboleth.DefaultClientAuthenticationResolverResources">
+        <value>%{idp.home}/conf/authn/oidc-client-registration-authentication.xml</value> <!-- should be a conditional:? -->
     </util:list>
-    <!-- Auto-append system config file to resource set. -->
-    <bean id ="ExtendedClientInformationResolverResources" class="net.shibboleth.ext.spring.factory.CombiningListFactoryBean"
+    
+    <bean id="shibboleth.oidc.rp.OAuth2ClientAuthenticationResolver"
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.spring.impl.ReloadingOAuth2ClientAuthenticationProvider"
+        c:resolverService-ref="shibboleth.oidc.rp.ClientAuthenticationResolverService" />
+        
+        
+    <!-- Client Identifier resolvers -->
+    <bean id="shibboleth.oidc.rp.ClientIdentifierResolverService"
+        class="net.shibboleth.ext.spring.service.ReloadableSpringService"
+        p:serviceConfigurations-ref="ExtendedClientIdentifierResolverResources"
+        p:failFast="%{idp.service.clientinfo.failFast:%{idp.service.failFast:false}}"
+        p:reloadCheckDelay="%{idp.service.clientinfo.checkInterval:PT0S}"
+        p:beanPostProcessors-ref="shibboleth.IdentifiableBeanPostProcessor"
+        p:beanFactoryPostProcessors-ref="shibboleth.PropertySourcesPlaceholderConfigurer">
+        <constructor-arg name="claz"
+            value="net.shibboleth.oidc.security.impl.OAuth2ClientIdentifierResolver" />
+        <constructor-arg name="strategy">
+            <bean class="net.shibboleth.idp.plugin.authn.oidc.rp.spring.impl.OAuth2ClientIdentifierResolverServiceStrategy" />
+        </constructor-arg>
+    </bean>
+    
+    <bean id ="ExtendedClientIdentifierResolverResources" class="net.shibboleth.ext.spring.factory.CombiningListFactoryBean"
             p:firstList="#{getObject('%{idp.service.clientinfo.resources:shibboleth.ClientInformationResolverResources}'.trim()) ?:
-                getObject('shibboleth.DefaultClientInformationResolverResources')}">
+                getObject('shibboleth.DefaultClientIdentifierResolverResources')}">
         <property name="secondList">
             <util:list >
-                <value>classpath:/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/clientinfo-resolver-system.xml</value>
+                <value>classpath:/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/clientidentifier-resolver-system.xml</value>
             </util:list>
         </property>
     </bean>
-
-    <bean id="shibboleth.oidc.rp.ClientInformationResolver"
-        class="net.shibboleth.oidc.metadata.impl.ReloadingRelyingPartyClientInformationProvider"
-        c:resolverService-ref="shibboleth.oidc.rp.ClientInformationResolverService" />
+    <util:list id="shibboleth.DefaultClientIdentifierResolverResources">
+        <value>%{idp.home}/conf/authn/oidc-client-registration-clientid.xml</value> <!-- should be a conditional:? -->
+    </util:list>
+    
+    <bean id="shibboleth.oidc.rp.OAuth2ClientIdentifierResolver"
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.spring.impl.ReloadingOAuth2ClientIdentifierProvider"
+        c:resolverService-ref="shibboleth.oidc.rp.ClientIdentifierResolverService" />
         
-  
+   
 </beans>
\ No newline at end of file
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 d49e603..84de395 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
@@ -21,14 +21,8 @@
     
     <!-- Initial discovery step -->
     <bean id="PropertyDrivenDiscovery" parent="shibboleth.Functions.Constant"
-        c:target="#{'%{idp.authn.oidc.rp.proxyIssuer:}'.trim()}" />  
-    
-    
-    
-    <!-- Spring controller to start the authentication request and recieve the response -->
-    <bean id="shibboleth.authn.OIDC.externalAuthnPath" class="java.lang.String"
-        c:_0="servletRelative:/Authn/OIDC/RP/authz">
-    </bean>
+        c:target="#{'%{idp.authn.oidc.rp.proxyIssuer:}'.trim()}" /> 
+  
 
     <!-- Parent beans for indirecting into nested PRC. -->
     
@@ -42,7 +36,7 @@
         
     <bean id="ParentAuthenticiationContextLookup" class="org.opensaml.messaging.context.navigate.ParentContextLookup"
         c:type="net.shibboleth.idp.authn.context.AuthenticationContext" />
-    
+        
     <!--  Action beans -->
     
     <!-- Explicitly wrapped by a non-nested action adaptor. -->
@@ -73,7 +67,8 @@
     <bean id="OIDCProviderMetadataLookup" parent="NestedWebFlowMessageHandlerAdaptor"
         scope="prototype" c:executionDirection="INBOUND">
         <constructor-arg name="messageHandler">
-            <bean class="net.shibboleth.idp.plugin.authn.oidc.rp.metadata.impl.OIDCProviderMetadataLookupHandler" scope="prototype">
+            <bean class="net.shibboleth.idp.plugin.authn.oidc.rp.metadata.impl.OIDCProviderMetadataLookupHandler" 
+            scope="prototype">
                <property name="providerMetadataResolver">
                     <ref bean="shibboleth.oidc.rp.ProviderMetadataResolver" />
                 </property>
@@ -82,23 +77,10 @@
                 </property>
             </bean>
         </constructor-arg>
-    </bean>
-    
-    <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.OIDCClientMetadataLookupHandler" scope="prototype">
-                <property name="clientInformationResolver">
-                    <ref bean="shibboleth.oidc.rp.ClientInformationResolver" />
-                </property>
-             <!--    <property name="clientIDLookupStrategy">
-                    <ref bean="shibboleth.oidc.rp.ClientIDLookupStrategy" />
-                </property> -->
-            </bean>
-        </constructor-arg>
-    </bean>
+    </bean> 
+
     
-     <bean id="shibboleth.oidc.rp.IssuerIDLookupStrategy"
+    <bean id="shibboleth.oidc.rp.IssuerIDLookupStrategy"
         class="net.shibboleth.idp.plugin.authn.oidc.rp.metadata.impl.DefaultIssuerIDLookupFunction"
         scope="prototype" />
 
@@ -112,6 +94,12 @@
         p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
         scope="prototype" />
         
+     <bean id="InitializeOAuth2ClientContext" scope="prototype"
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.InitializeOAuth2ClientContext"
+        p:clientIdResolver-ref="shibboleth.oidc.rp.OAuth2ClientIdentifierResolver"
+        p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"/>
+    
+       
     <bean id="SelectRelyingPartyConfiguration"
         class="net.shibboleth.idp.profile.impl.SelectRelyingPartyConfiguration" scope="prototype"
         p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
@@ -125,8 +113,8 @@
         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"
+    <bean id="AddOIDCAuthenticationRequest" scope="prototype"
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.AddOIDCAuthenticationRequest"
         p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"/>        
     
         
@@ -200,6 +188,12 @@
         
     <!-- CODE flow beans -->
     
+    <bean id="InitializeOAuth2ClientAuthenticationContext" scope="prototype"
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.InitializeOAuth2ClientAuthenticationContext"
+        p:clientAuthenticationResolver-ref="shibboleth.oidc.rp.OAuth2ClientAuthenticationResolver"
+        p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"/>
+        
+    
     <bean id="ExchangeCodeForAccessToken" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ExchangeCodeForAccessToken" 
         p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
@@ -207,7 +201,7 @@
         p:httpClient="#{getObject('shibboleth.authn.oidc.rp.HttpClient') ?: getObject('shibboleth.InternalHttpClient')}"
         p:httpClientSecurityParameters="#{getObject('shibboleth.authn.oidc.rp.HttpClientSecurityParameters')}"
         p:httpResponseDecoderStrategy="#{getObject('shibboleth.authn.oidc.rp.TokenResponseDecoder') ?: getObject('shibboleth.authn.oidc.rp.DefaultTokenResponseDecoder')}"
-        p:httpRequestEncoderStrategy="#{getObject('shibboleth.authn.oidc.rp.TokenRequestEncoder') ?: getObject('shibboleth.authn.oidc.rp.DefaultTokenRequestEncoder')}"/>   
+        p:httpRequestEncoderStrategy="#{getObject('shibboleth.authn.oidc.rp.TokenRequestEncoder') ?: getObject('shibboleth.authn.oidc.rp.DefaultAuthCodeTokenResponseEncoder')}"/>   
         
      <bean id="ValidateOAuthAccessTokenResponse" 
         class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateOAuthAccessTokenResponse"
@@ -227,8 +221,8 @@
         class="net.shibboleth.idp.plugin.authn.oidc.rp.decoding.impl.DefaultMapResponseDecoder"
         p:objectMapper-ref="shibboleth.authn.oidc.rp.JSONObjectMapper"/>
         
-    <bean id="shibboleth.authn.oidc.rp.DefaultTokenRequestEncoder" scope="prototype" 
-        class="net.shibboleth.idp.plugin.authn.oidc.rp.encoding.impl.DefaultTokenRequestEncoder"/>
+    <bean id="shibboleth.authn.oidc.rp.DefaultAuthCodeTokenResponseEncoder" scope="prototype" 
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.encoding.impl.NimbusAuthCodeTokenRequestEncoder"/>
         
      <!-- Create a default object mapper. Setup should not change once injected -->
     <bean id="shibboleth.authn.oidc.rp.JSONObjectMapper" class="com.fasterxml.jackson.databind.ObjectMapper" />
@@ -338,7 +332,7 @@
         
     <bean id="AzpClaimsValidator"
         class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
-        p:claimName="azp" p:valueToMatchLookupStrategy-ref="ClientIDFromOIDCMetadataContextLookupFunction"> 
+        p:claimName="azp" p:valueToMatchLookupStrategy-ref="ClientIDFromOAuth2ClientContextFunction"> 
          <property name="activationCondition">
             <bean id="AzpClaimExistsCondition"
                 class="net.shibboleth.oidc.security.jwt.claims.impl.ClaimExistsActivationCondition"
@@ -356,16 +350,14 @@
 
     <bean id="AudienceClaimsValidator"
         class="net.shibboleth.oidc.security.jwt.claims.impl.AudienceClaimsValidator"
-        p:audienceLookupStrategy-ref="ClientIDFromOIDCMetadataContextLookupFunction"/>
+        p:audienceLookupStrategy-ref="ClientIDFromOAuth2ClientContextFunction"/>
+        
     
-    <bean id="ClientIDFromOIDCMetadataContextLookupFunction"
-        class="net.shibboleth.oidc.profile.logic.ClientIDFromOIDCMetadataContextLookupFunction">
-        <property name="oIDCMetadataContextLookupStrategy">
-            <bean parent="shibboleth.Functions.Compose"
-                c:g-ref="OIDCMetadataContextChildLookup"
-                c:f-ref="shibboleth.MessageContextLookup.Outbound" />
-        </property>
-    </bean>
+    <bean id="ClientIDFromOAuth2ClientContextFunction"
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.ClientIDFromOAuth2ClientContextFunction"
+        c:oauth2ClientContextLookupStrategy-ref="shibboleth.ChildLookup.OAuth2ClientContextFromOutbound"/>
+    
+   
     
     <bean id="NonceClaimValidator" 
                 class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
@@ -385,6 +377,10 @@
     <bean id="OIDCMetadataContextChildLookup"
         class="org.opensaml.messaging.context.navigate.ChildContextLookup"
         c:type="#{ T(net.shibboleth.oidc.metadata.context.OIDCMetadataContext) }" />
+        
+    <bean id="OIDCPeerEntityContextChildLookup"
+        class="org.opensaml.messaging.context.navigate.ChildContextLookup"
+        c:type="#{ T(net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext) }" />
     
     <util:list id="IDTokenClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
         <ref bean="IssuerClaimsValidator" /> <!-- TODO prevent: if it contains additional audiences not trusted by the Client. -->
@@ -491,17 +487,9 @@
     
     <!-- OLD STUFF -->
     
-    <!-- TODO Add user import here
-    <import resource="openidconnect-authn-config.xml" /> -->
-
-    
-    <!-- Populate RP UI info from metadata? -->
-
-
     <bean id="ValidateIDTokenSignature"
         class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateIDTokenSignature" />
 
-    
 
 
     <bean id="shibboleth.authn.oidc.rp.jwt.DefaultAuthTimeActivationCondition"
diff --git a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml
index 44e1bd6..cb825fc 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
@@ -26,14 +26,13 @@
         <evaluate expression="OIDCProviderMetadataLookup" />             
         <evaluate expression="InitializeRelyingPartyContext" />
         <evaluate expression="SelectRelyingPartyConfiguration" />
-         <!--  Get RP information relating to this OP -->
-        <evaluate expression="OIDCClientMetadataLookup" /> 
         
         <!--  <evaluate expression="PostLookupPopulateAuditContext" /> -->
         <evaluate expression="InitializeOutboundMessageContext" />
+        <evaluate expression="InitializeOAuth2ClientContext" />
         <evaluate expression="SelectProfileConfiguration" />
         <evaluate expression="PopulateResponseTypeAndModeContext"/>
-        <evaluate expression="AddAuthzRequest"/>
+        <evaluate expression="AddOIDCAuthenticationRequest"/>
         <!-- <evaluate expression="PostRequestPopulateAuditContext" />
         <evaluate expression="WriteAuditLog" /> -->
                 
@@ -88,6 +87,7 @@
     </decision-state>
 
     <action-state id="CodeFlow">
+        <evaluate expression="InitializeOAuth2ClientAuthenticationContext"/>
         <evaluate expression="ExchangeCodeForAccessToken"/> 
         <evaluate expression="ValidateOAuthAccessTokenResponse"/>
         <evaluate expression="ExtractIDTokenFromTokenResponse"/>       
diff --git a/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/clientauthentication-resolver-system.xml b/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/clientauthentication-resolver-system.xml
new file mode 100644
index 0000000..db1fc48
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/clientauthentication-resolver-system.xml
@@ -0,0 +1,33 @@
+<beans xmlns="http://www.springframework.org/schema/beans"
+    xmlns:context="http://www.springframework.org/schema/context"
+    xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
+    xmlns:c="http://www.springframework.org/schema/c" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+
+    default-init-method="initialize" default-destroy-method="destroy" default-lazy-init="true">
+
+    <bean id="shibboleth.oidc.rp.OAuth2ClientAuthenticationProvider" lazy-init="false"
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.spring.impl.OAuth2ClientAuthenticationContainer"
+        p:embeddedResolver-ref="shibboleth.oidc.rp.OAuth2ClientAuthenticationResolverImpl">
+    </bean>
+
+
+    <bean id="shibboleth.oidc.rp.OAuth2ClientAuthenticationResolverImpl"
+        class="net.shibboleth.oidc.security.impl.OAuth2ClientAuthenticationResolverImpl"
+        p:storageService="#{getObject('shibboleth.authn.oidc.rp.StorageService') ?: getObject('DefaultMapBackedClientAuthenticationStorageService')}" />
+
+    
+    <bean id="DefaultMapBackedClientAuthenticationStorageService" scope="prototype"
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.MapBackedMemoryStorageServiceFactoryBean"
+        c:context="#{T(net.shibboleth.oidc.security.impl.OAuth2ClientAuthenticationResolverImpl).CONTEXT_NAME}"
+        c:map="#{getObject('shibboleth.authn.oidc.rp.ClientIdToClientAuthenticationMap')}"
+        c:id="DefaultClientIDToClientAuthenticationMapStorageService" />
+    
+    <!-- Client Authentication parent bean which defaults secrets to not expire -->
+    <bean id="shibboleth.authn.oidc.rp.ClientAuthenticationDetails"
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.storage.ClientAuthenticationDetails" abstract="true" 
+        c:clientSecretExpiresAt="0"/>
+
+</beans>
\ No newline at end of file
diff --git a/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/clientidentifier-resolver-system.xml b/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/clientidentifier-resolver-system.xml
new file mode 100644
index 0000000..ac5f449
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/clientidentifier-resolver-system.xml
@@ -0,0 +1,30 @@
+<beans xmlns="http://www.springframework.org/schema/beans"
+    xmlns:context="http://www.springframework.org/schema/context"
+    xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
+    xmlns:c="http://www.springframework.org/schema/c" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+
+    default-init-method="initialize" default-destroy-method="destroy" default-lazy-init="true">
+
+    <bean id="shibboleth.oidc.rp.OAuth2ClientIdentifierProvider" lazy-init="false"
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.spring.impl.OAuth2ClientIdentifierContainer"
+        p:embeddedResolver-ref="shibboleth.oidc.rp.OAuth2ClientIdentifierResolverImpl">
+    </bean>
+
+    <bean id="shibboleth.oidc.rp.OAuth2ClientIdentifierResolverImpl"
+        class="net.shibboleth.oidc.security.impl.OAuth2ClientIdentifierResolverImpl"
+        p:providerMetadataLookupStrategy-ref="shibboleth.ChildLookup.OIDCProviderMetadataContextFromOutbound"
+        p:storageService="#{getObject('shibboleth.authn.oidc.rp.StorageService') ?: getObject('DefaultMapBackedClientIdStorageService')}" />
+
+
+    <bean id="DefaultMapBackedClientIdStorageService" scope="prototype"
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.MapBackedMemoryStorageServiceFactoryBean"
+        c:context="#{T(net.shibboleth.oidc.security.impl.OAuth2ClientIdentifierResolverImpl).CONTEXT_NAME}"
+        c:map="#{getObject('shibboleth.authn.oidc.rp.IssuerToClientIdMap')}"
+        c:id="DefaultIssuerToClientIDMapStorageService" />
+
+
+
+</beans>
\ No newline at end of file
diff --git a/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/providermetadata-resolver-system.xml b/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/providermetadata-resolver-system.xml
index f5a6720..cff8c0d 100644
--- a/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/providermetadata-resolver-system.xml
+++ b/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/providermetadata-resolver-system.xml
@@ -32,29 +32,19 @@
     <bean id="shibboleth.oidc.rp.DefaultHTTProviderConfigurationMetadataResponseHandler"
         class="net.shibboleth.oidc.metadata.impl.HTTPProviderConfigurationFetchingStrategy.OIDCProviderMetadataResponseHandler" />
 
-
-    
-    <!--  Batch OIDC provider cache builder factory and template specification -->
-    <bean id="batchCacheFactory" class="net.shibboleth.oidc.metadata.cache.impl.BatchMetadataCacheBuilder$Builder"/>
+    <!-- Cache builder specifications -->
+    <bean id="cacheFactory" class="net.shibboleth.oidc.metadata.cache.impl.MetadataCacheBuilder$Builder"/>
     
-    <bean id="shibboleth.oidc.rp.BatchCacheBuilder" factory-bean="batchCacheFactory" factory-method="build"
+    <bean id="shibboleth.oidc.rp.CacheBuilder" factory-bean="cacheFactory" factory-method="build"
         abstract="true"/>
-
+    
     <bean class="net.shibboleth.oidc.metadata.cache.impl.BatchMetadataCacheBuilderSpec" 
         id="shibboleth.oidc.rp.BaseProviderBatchCacheBuilderSpec" abstract="true" 
         p:parsingStrategy-ref="shibboleth.oidc.rp.DefaultOIDCProviderMetadataParsingStrategy"
         p:criteriaToIdentifierStrategy-ref="shibboleth.oidc.rp.DefaultOIDCProviderMetadataCriteriaToIdentifierStrategy"
         p:sourceMetadataExpiryStrategy-ref="shibboleth.oidc.rp.DefaultODICProviderSourceMetadataExpirationTimeStrategy"
         p:identifierExtractionStrategy-ref="shibboleth.oidc.rp.DefaultOIDCProviderMetadataIdentifierExtractionStrategy"/>
-    
-    
-    <!--  Dynamic OIDC provider cache builder factory and template specification -->
-        
-    <bean id="dynamicCacheFactory" class="net.shibboleth.oidc.metadata.cache.impl.DynamicMetadataCacheBuilder$Builder"/>
-    
-    <bean id="shibboleth.oidc.rp.DynamicCacheBuilder" factory-bean="dynamicCacheFactory" factory-method="build"
-        abstract="true"/>
-    
+         
     <bean class="net.shibboleth.oidc.metadata.cache.impl.DynamicMetadataCacheBuilderSpec" 
         id="shibboleth.oidc.rp.BaseProviderDynamicCacheBuilderSpec" abstract="true"        
         p:fetchStrategy-ref="shibboleth.oidc.rp.DefaultHTTPProviderConfigurationMetadataFetchingStrategy" 
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/DefaultAuthCodeTokenRequestEncoderTest.java
similarity index 92%
rename from idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultTokenRequestEncoderTest.java
rename to idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultAuthCodeTokenRequestEncoderTest.java
index b0b68e8..ccbaaf1 100644
--- 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/DefaultAuthCodeTokenRequestEncoderTest.java
@@ -30,15 +30,15 @@ import org.testng.annotations.Test;
 import net.shibboleth.idp.plugin.authn.oidc.rp.impl.AbstractOIDCTest;
 
 /** Tests for the DefaultTokenRequestEncoder.*/
-public class DefaultTokenRequestEncoderTest extends AbstractOIDCTest {
+public class DefaultAuthCodeTokenRequestEncoderTest extends AbstractOIDCTest {
     
     /** The encoder to test.*/
-    private DefaultTokenRequestEncoder encoder;
+    private DefaultAuthCodeTokenRequestEncoder encoder;
     
     @BeforeMethod
     public void setup() throws Exception {
         super.setup();
-        encoder = new DefaultTokenRequestEncoder();
+        encoder = new DefaultAuthCodeTokenRequestEncoder();
     }
     
     
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/NimbusAuthCodeTokenRequestEncoderTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/NimbusAuthCodeTokenRequestEncoderTest.java
new file mode 100644
index 0000000..be33b90
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/NimbusAuthCodeTokenRequestEncoderTest.java
@@ -0,0 +1,169 @@
+/*
+ * 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 static org.testng.Assert.assertNull;
+
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.security.interfaces.RSAPrivateKey;
+
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.jwk.KeyUse;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
+import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
+import com.nimbusds.oauth2.sdk.auth.ClientSecretJWT;
+import com.nimbusds.oauth2.sdk.auth.ClientSecretPost;
+import com.nimbusds.oauth2.sdk.auth.PrivateKeyJWT;
+import com.nimbusds.oauth2.sdk.auth.Secret;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.impl.AbstractOIDCTest;
+import net.shibboleth.oidc.authn.context.OAuth2ClientAuthenticationContext;
+
+/** Tests for the DefaultTokenRequestEncoder.*/
+public class NimbusAuthCodeTokenRequestEncoderTest extends AbstractOIDCTest {
+    
+    /** The encoder to test.*/
+    private NimbusAuthCodeTokenRequestEncoder encoder;
+    
+    @Override
+    @BeforeMethod
+    public void setup() throws Exception {
+        super.setup();
+        encoder = new NimbusAuthCodeTokenRequestEncoder();
+    }
+    
+    
+    @Test
+    public void testEncoder_ClientSecretBasic_Success() throws Exception {
+        
+        final OAuth2ClientAuthenticationContext clientAuthContext = new OAuth2ClientAuthenticationContext();
+        clientAuthContext.setClientAuthentication(
+                new ClientSecretBasic(new ClientID("test_client"), new Secret("secret")));
+        peerEntityCtx.addSubcontext(clientAuthContext);
+        
+        encoder.initialize();
+        final HttpUriRequest request = encoder.apply(prc);
+        assertNotNull(request);
+        assertTrue(request instanceof HttpEntityEnclosingRequest);
+        assertNotNull(request.getFirstHeader("Authorization"));
+        assertNotNull(((HttpEntityEnclosingRequest)request).getEntity().getContent());
+        final 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"));
+    }
+    
+    @Test
+    public void testEncoder_ClientSecretPost_Success() throws Exception {
+        
+        final OAuth2ClientAuthenticationContext clientAuthContext = new OAuth2ClientAuthenticationContext();
+        clientAuthContext.setClientAuthentication(
+                new ClientSecretPost(new ClientID("test_client"), new Secret("secret")));
+        peerEntityCtx.addSubcontext(clientAuthContext);
+        
+        encoder.initialize();
+        final HttpUriRequest request = encoder.apply(prc);
+        assertNotNull(request);
+        assertTrue(request instanceof HttpEntityEnclosingRequest);
+        assertNull(request.getFirstHeader("Authorization"));
+        assertNotNull(((HttpEntityEnclosingRequest)request).getEntity().getContent());
+        final String content = new String(
+                ((HttpEntityEnclosingRequest)request).getEntity().getContent().readAllBytes(), StandardCharsets.UTF_8);
+        assertTrue("grant_type expected in request", content.contains("grant_type"));
+        assertTrue("client_secret expected in request", content.contains("client_secret"));
+        assertTrue("client_id expected in request", content.contains("client_id"));
+        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"));
+    }
+    
+    @Test
+    public void testEncoder_ClientSecretJWT_Success() throws Exception {
+        
+        final OAuth2ClientAuthenticationContext clientAuthContext = new OAuth2ClientAuthenticationContext();
+        clientAuthContext.setClientAuthentication(
+                new ClientSecretJWT(new ClientID("test_client"), 
+                        new URI("https://rp.example.com/callback"), JWSAlgorithm.HS256, 
+                        new Secret("9y$B?E(H+MbQeThWmZq4t7w!z%C*F)J@")));
+        peerEntityCtx.addSubcontext(clientAuthContext);
+        
+        encoder.initialize();
+        final HttpUriRequest request = encoder.apply(prc);
+        assertNotNull(request);
+        assertTrue(request instanceof HttpEntityEnclosingRequest);
+        assertNull(request.getFirstHeader("Authorization"));
+        assertNotNull(((HttpEntityEnclosingRequest)request).getEntity().getContent());
+        final String content = new String(
+                ((HttpEntityEnclosingRequest)request).getEntity().getContent().readAllBytes(), StandardCharsets.UTF_8);
+        assertTrue("grant_type expected in request", content.contains("grant_type"));
+        assertTrue("client_assertion_type expected in request", content.contains("client_assertion_type"));
+        assertTrue("client_assertion expected in request", content.contains("client_assertion"));
+        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"));
+    }
+    
+    @Test
+    public void testEncoder_PrivateKeyJWT_Success() throws Exception {
+        
+        final RSAKey key = new RSAKeyGenerator(2048)
+                .keyID("1")
+                .keyUse(KeyUse.ENCRYPTION)
+                .generate();
+        
+        
+        final OAuth2ClientAuthenticationContext clientAuthContext = new OAuth2ClientAuthenticationContext();
+        clientAuthContext.setClientAuthentication(
+                new PrivateKeyJWT(
+                        new ClientID("test_client"), 
+                        new URI("https://rp.example.com/callback"), 
+                        JWSAlgorithm.RS256, 
+                        (RSAPrivateKey)key.toPrivateKey(), 
+                        "1", 
+                        null));
+        peerEntityCtx.addSubcontext(clientAuthContext);
+        
+        encoder.initialize();
+        final HttpUriRequest request = encoder.apply(prc);
+        assertNotNull(request);
+        assertTrue(request instanceof HttpEntityEnclosingRequest);
+        assertNull(request.getFirstHeader("Authorization"));
+        assertNotNull(((HttpEntityEnclosingRequest)request).getEntity().getContent());
+        final String content = new String(
+                ((HttpEntityEnclosingRequest)request).getEntity().getContent().readAllBytes(), StandardCharsets.UTF_8);
+        assertTrue("grant_type expected in request", content.contains("grant_type"));
+        assertTrue("client_assertion_type expected in request", content.contains("client_assertion_type"));
+        assertTrue("client_assertion expected in request", content.contains("client_assertion"));
+        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
index f2dede9..6c5b8e5 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java
@@ -137,6 +137,9 @@ public abstract class AbstractOIDCTest {
     /** The request context to use.*/
     protected RequestContext src;
     
+    /** The request context to use.*/
+    protected OIDCPeerEntityContext peerEntityCtx;
+    
     /** 
      * Setup the various contexts.
      * 
@@ -157,6 +160,7 @@ public abstract class AbstractOIDCTest {
         final MessageContext outMsgCtx = new MessageContext();
         final OIDCAuthenticationRequest request = new OIDCAuthenticationRequest(new ClientID("https://rp.example.com"));
         request.setState(new State("8df98fd63a53fa5b5433d6f8754bca5d.65317332"));
+        request.setRedirectURI(new URI("https://rp.example.com/callback"));
         outMsgCtx.setMessage(request);
         prc.setOutboundMessageContext(outMsgCtx);      
 
@@ -166,12 +170,14 @@ public abstract class AbstractOIDCTest {
                 OIDCClientInformation.parse((JSONObject)((JSONArray)parser.parse(CLIENT_METADATA)).get(0)));
         outMsgCtx.addSubcontext(metadataContext);
         
-        final OIDCPeerEntityContext peerEntitCtx = outMsgCtx.getSubcontext(OIDCPeerEntityContext.class,true);
+        
+        
+        peerEntityCtx = outMsgCtx.getSubcontext(OIDCPeerEntityContext.class,true);
         final OIDCProviderMetadataContext providerCtx = new OIDCProviderMetadataContext();
         providerCtx.setProviderInformation(
                         OIDCProviderMetadata.parse((JSONObject)parser.parse(GOOD_PROVIDER_CONFIGURATION_INFO)));
-        peerEntitCtx.addSubcontext(providerCtx);
-        outMsgCtx.addSubcontext(peerEntitCtx);
+        peerEntityCtx.addSubcontext(providerCtx);     
+        outMsgCtx.addSubcontext(peerEntityCtx);
         
         //Set the inbound reponse.
         
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 8cf421c..1550a2a 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
@@ -27,6 +27,7 @@ import static org.testng.Assert.assertTrue;
 import java.io.IOException;
 import java.net.URI;
 import java.net.URLEncoder;
+import java.util.Set;
 
 import javax.annotation.Nonnull;
 import javax.servlet.ServletContext;
@@ -71,7 +72,9 @@ import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.authn.context.ExternalAuthenticationContext;
 import net.shibboleth.idp.authn.context.SubjectCanonicalizationContext;
 import net.shibboleth.idp.authn.impl.ExternalAuthenticationImpl;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OAuth2ClientContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCAuthnContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
 import net.shibboleth.idp.plugin.authn.test.flow.mock.IdPPropertyConfigurer;
 import net.shibboleth.idp.session.IdPSession;
 import net.shibboleth.idp.session.context.SessionContext;
@@ -96,8 +99,11 @@ public class AuthorizationControllerTest extends AbstractTestNGSpringContextTest
     /** The endpoint to redirect the user-agent to.*/
     @Nonnull private final String ENDPOINT_URI = "https://op.example.com/";
     
+    /** The issuer or OP identifier.*/
+    @Nonnull private final String ISSUER = "https://op.example.com";
+    
     /** The redirect to direct the user-agent to after successful authentication. */
-    @Nonnull private final String REDIRECT_URI = "https://rp.example.com/callback";
+    @Nonnull private final String REDIRECT_URI = "http://localhost/Authn/OIDC/RP/callback";
     
     /** The mock MVC entry point for testing. */
     @Nonnull private MockMvc mockMvc;
@@ -125,6 +131,8 @@ public class AuthorizationControllerTest extends AbstractTestNGSpringContextTest
         assertNotNull(controller);
         assertNotNull(response);
         assertNotNull(request);
+        controller.setRedirectUriCreationStrategy(
+                new DefaultRedirectUriCreationFunction("/Authn/OIDC/RP/callback", Set.of("http://localhost")));
 
         mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
        
@@ -236,6 +244,13 @@ public class AuthorizationControllerTest extends AbstractTestNGSpringContextTest
         final MessageContext msgCtx = new MessageContext();
         msgCtx.setMessage(request);
         prc.setOutboundMessageContext(msgCtx);
+        
+        final OIDCPeerEntityContext peerEntity = new OIDCPeerEntityContext();
+        peerEntity.setIdentifier(ISSUER);
+        final OAuth2ClientContext clientContext = peerEntity.getSubcontext(OAuth2ClientContext.class, true);
+        
+        
+        prc.getOutboundMessageContext().addSubcontext(peerEntity);
     
         return rootPrc;
     }
@@ -261,7 +276,7 @@ public class AuthorizationControllerTest extends AbstractTestNGSpringContextTest
         
         
         /** The message encoder to be returned by this factory. */
-        @Nonnull private MessageEncoder messageEncoder;
+        @Nonnull private final MessageEncoder messageEncoder;
         
         /**
          * 
@@ -307,7 +322,7 @@ public class AuthorizationControllerTest extends AbstractTestNGSpringContextTest
     private class MockRedirectEncoder extends AbstractOIDCMessageEncoder {
 
         @Override
-        public boolean test(OIDCHttpRequestMethod t) {
+        public boolean test(final OIDCHttpRequestMethod t) {
             return true;
         }
 
@@ -319,7 +334,7 @@ public class AuthorizationControllerTest extends AbstractTestNGSpringContextTest
                 final OIDCAuthenticationRequest outboundMessage = (OIDCAuthenticationRequest)messageContext.getMessage();
                 
     
-                URLBuilder urlBuilder = new URLBuilder(outboundMessage.getEndpointURI().toString());
+                final URLBuilder urlBuilder = new URLBuilder(outboundMessage.getEndpointURI().toString());
                 serializeAuthorizationParamsToUrl(outboundMessage, urlBuilder);     
                 final String redirectURL = urlBuilder.buildURL();
                 
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultRedirectUriCreationFunctionTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultRedirectUriCreationFunctionTest.java
new file mode 100644
index 0000000..bc481ac
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultRedirectUriCreationFunctionTest.java
@@ -0,0 +1,169 @@
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import static org.testng.Assert.assertEquals;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.Set;
+
+import org.mockito.MockitoAnnotations;
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OAuth2ClientContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
+
+public class DefaultRedirectUriCreationFunctionTest {
+    
+    /** Static callback path from servlet request.*/
+    private static final String CALLBACK_PATH = "/Authn/OIDC/RP/callback";
+    
+    /** The action to test. */
+    private DefaultRedirectUriCreationFunction strategy;
+    
+    /** HTTP request mock.*/
+    private MockHttpServletRequest request;
+    
+    /** Created PRC.*/
+    private ProfileRequestContext prc;
+    
+    /** Created client context.*/
+    private OAuth2ClientContext clientContext;
+    
+
+    @BeforeMethod
+    public void setUp() throws Exception {        
+        request = new MockHttpServletRequest();
+        MockitoAnnotations.openMocks(this);
+        prc = new ProfileRequestContext();
+        prc.setOutboundMessageContext(new MessageContext());
+        final OIDCPeerEntityContext peerCtx = 
+                prc.getOutboundMessageContext().getSubcontext(OIDCPeerEntityContext.class, true);
+        clientContext = peerCtx.getSubcontext(OAuth2ClientContext.class, true);
+        
+    }
+    
+    @Test
+    public final void testComputedRedirectAllowed() throws Exception {
+        
+        strategy = new DefaultRedirectUriCreationFunction(CALLBACK_PATH, Set.of("https://allowed.com"));
+        
+        request.addHeader("Host", "allowed.com");
+        request.setServerPort(443);
+        request.setScheme("https");
+      
+        final URI redirect = strategy.apply(request, prc);
+        assertEquals(redirect.toASCIIString(), "https://allowed.com"+CALLBACK_PATH);
+        
+    }
+    
+    @Test
+    public final void testComputedRedirectDisallowed() throws Exception {
+        
+        strategy = new DefaultRedirectUriCreationFunction(CALLBACK_PATH, Set.of("https://allowed.com"));
+        
+        request.addHeader("Host", "not-allowed.com");
+        request.setServerPort(443);
+        request.setScheme("https");
+        final URI redirect = strategy.apply(request, prc);
+        assertEquals(redirect, null);
+        
+    }
+    
+    @Test
+    public final void testComputedRedirectDisallowedNotOrigins() throws Exception {
+        
+        strategy = new DefaultRedirectUriCreationFunction(CALLBACK_PATH, Collections.emptySet());
+        
+        request.addHeader("Host", "not-allowed.com");
+        request.setServerPort(443);
+        request.setScheme("https");
+        final URI redirect = strategy.apply(request, prc);
+        assertEquals(redirect, null);
+        
+    }
+    
+    @Test
+    public final void testComputedRedirectAllowedHTTPSCustomPort() throws Exception {
+        
+        strategy = new DefaultRedirectUriCreationFunction(CALLBACK_PATH, Set.of("https://allowed.com:8443"));
+        
+        request.addHeader("Host", "allowed.com");
+        request.setServerPort(8443);
+        request.setScheme("https");
+        final URI redirect = strategy.apply(request, prc);
+        assertEquals(redirect.toASCIIString(), "https://allowed.com:8443"+CALLBACK_PATH);
+        
+    }
+    
+    @Test
+    public final void testComputedRedirectAllowedHTTPCustomPort() throws Exception {
+        
+        strategy = new DefaultRedirectUriCreationFunction(CALLBACK_PATH, Set.of("http://allowed.com:8080"));
+        
+        request.addHeader("Host", "allowed.com");
+        request.setServerPort(8080);
+        request.setScheme("http");
+        final URI redirect = strategy.apply(request, prc);
+        assertEquals(redirect.toASCIIString(), "http://allowed.com:8080"+CALLBACK_PATH);
+        
+    }
+    
+    @Test
+    public final void testComputedRedirectAllowedNullPort() throws Exception {
+        
+        strategy = new DefaultRedirectUriCreationFunction(CALLBACK_PATH, Set.of("http://allowed.com"));
+        
+        request.addHeader("Host", "allowed.com");
+        request.setServerPort(-1);
+        request.setScheme("http");
+        final URI redirect = strategy.apply(request, prc);
+        assertEquals(redirect.toASCIIString(), "http://allowed.com"+CALLBACK_PATH);
+        
+    }
+    
+    @Test
+    public final void testComputedRedirectDisallowedOnPort() throws Exception {
+        
+        strategy = new DefaultRedirectUriCreationFunction(CALLBACK_PATH, Set.of("http://allowed.com"));
+        
+        request.addHeader("Host", "allowed.com");
+        request.setServerPort(443);
+        request.setScheme("https");
+        final URI redirect = strategy.apply(request, prc);
+        assertEquals(redirect, null);
+        
+    }
+    
+    @Test
+    public final void testPreregisteredURL() throws Exception {
+        
+        strategy = new DefaultRedirectUriCreationFunction(CALLBACK_PATH, Set.of("http://allowed.com"));
+        
+        //request is irrelevant
+        request.addHeader("Host", "notused.com");
+        request.setServerPort(443);
+        request.setScheme("https");        
+        clientContext.setRedirectUriOverride(new URI("https://registered.com"+CALLBACK_PATH));
+        final URI redirect = strategy.apply(request, prc);
+        assertEquals(redirect.toASCIIString(), "https://registered.com"+CALLBACK_PATH);
+        
+    }
+    
+    @Test
+    public final void testComputedRedirectBadHostname() throws Exception {
+        
+        strategy = new DefaultRedirectUriCreationFunction(CALLBACK_PATH, Set.of("http://allowed.com"));
+        
+        request.addHeader("Host", "<script>inject</script>");
+        request.setServerPort(80);
+        request.setScheme("http");
+        final URI redirect = strategy.apply(request, prc);
+        assertEquals(redirect, null);
+        
+    }
+
+}
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 1f5537f..1806afd 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
@@ -27,6 +27,7 @@ import java.util.List;
 import java.util.Map;
 
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
 import org.apache.http.conn.ssl.NoopHostnameVerifier;
 import org.apache.http.conn.ssl.TrustAllStrategy;
@@ -75,23 +76,19 @@ import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
 import com.nimbusds.openid.connect.sdk.AuthenticationResponseParser;
 import com.nimbusds.openid.connect.sdk.Nonce;
 import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
-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.context.SubjectCanonicalizationContext;
 import net.shibboleth.idp.authn.impl.ExternalAuthenticationImpl;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OAuth2ClientContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.ResponseTypeAndModeContext;
 import net.shibboleth.idp.plugin.authn.test.flow.AbstractAuthnXmlFlowExecutionTests;
 import net.shibboleth.idp.plugin.authn.test.flow.mock.MockFlowBuilder;
 import net.shibboleth.idp.profile.context.RelyingPartyContext;
 import net.shibboleth.idp.relyingparty.RelyingPartyConfiguration;
-import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
 import net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext;
 import net.shibboleth.oidc.profile.config.OIDCAuthorizationConfiguration;
 import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
@@ -107,6 +104,8 @@ import okhttp3.tls.HeldCertificate;
 public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
     
     private static final String OP_ISSUER_ID = "https://localhost:9918";
+    
+    private final String RP_ALLOWED_ORIGINS = "https://localhost";
 
     /**
      * Example of good provider metadata. Endpoints are localhost to support the 
@@ -170,22 +169,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
             + "\"urn:ietf:params:oauth:grant-type:jwt-bearer\"\n"
             + "]\n"
             + "}";
-    
-    @Nonnull private final String CLIENT_METADATA = "[\n"
-            + "  {\n"
-            + "    \"issuer\": \""+OP_ISSUER_ID+"\",\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"
-            + "    \"client_secret\": \"demo_rp_secret\",\n"
-            + "    \"response_types\": [\n"
-            + "      \"id_token\",\n"
-            + "      \"id_token token\"\n"
-            + "    ]\n"
-            + "  }"
-            + "]";
+   
 
     /** Mock JSON Object response from the UserInfo endpoint.*/
     @Nonnull @NotEmpty
@@ -456,20 +440,14 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         return request;
     }
     
-    /**
-     * Create a client metadata context
-     * 
-     * @return the context.
-     * 
-     * @throws Exception on error.
-     */
-    private OIDCMetadataContext createClientMedataContext() throws Exception {
-        final JSONParser parser = new JSONParser(JSONParser.MODE_JSON_SIMPLE);
-        final OIDCMetadataContext metadataContext = new OIDCMetadataContext();
-        metadataContext.setClientInformation(
-                OIDCClientInformation.parse((JSONObject)((JSONArray)parser.parse(CLIENT_METADATA)).get(0)));
-        return metadataContext;
+    private OAuth2ClientContext createOAuth2ClientContext(@Nonnull final String clientId, 
+            @Nullable final URI redirectOverride) {
+        final OAuth2ClientContext context = new OAuth2ClientContext();
+        context.setClientId(clientId);
+        context.setRedirectUriOverride(redirectOverride);
+        return context;
     }
+   
     
     /**
      * Create a response mode and type context.
@@ -513,7 +491,8 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         final Map<String,String> mockProperties = Map.of(
                 "idp.service.clientinfo.failFast","false",
                 "idp.entityID", "http://idp.example.com/",
-                "idp.authn.oidc.rp.proxyIssuer",OP_ISSUER_ID);
+                "idp.authn.oidc.rp.proxyIssuer",OP_ISSUER_ID,
+                "idp.oidc.rp.redirecturl.allowedOrigins", RP_ALLOWED_ORIGINS);
         
         setMockProperties(mockProperties);
         
@@ -552,7 +531,8 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         
         final Map<String,String> mockProperties = Map.of(                
                 "idp.entityID", "http://idp.example.com/",
-                "idp.authn.oidc.rp.proxyIssuer",OP_ISSUER_ID);
+                "idp.authn.oidc.rp.proxyIssuer",OP_ISSUER_ID,
+                "idp.oidc.rp.redirecturl.allowedOrigins", RP_ALLOWED_ORIGINS);
         
         setMockProperties(mockProperties);
         
@@ -591,8 +571,8 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         outMsgCtx.setMessage(createAuthenticationRequest());        
         outMsgCtx.addSubcontext(createPeerContext());
         outMsgCtx.addSubcontext(createResponseTypeAndModeContext());
-        outMsgCtx.addSubcontext(createClientMedataContext());
         nestPrc.setOutboundMessageContext(outMsgCtx);    
+        outMsgCtx.getSubcontext(OIDCPeerEntityContext.class).addSubcontext(createOAuth2ClientContext("demo_rp",null));
         
         // Setup inbound context.
         final MessageContext inMsgCtx = new MessageContext();
@@ -669,8 +649,8 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         outMsgCtx.setMessage(createAuthenticationRequest());        
         outMsgCtx.addSubcontext(createPeerContext());
         outMsgCtx.addSubcontext(createResponseTypeAndModeContext());
-        outMsgCtx.addSubcontext(createClientMedataContext());
-        nestPrc.setOutboundMessageContext(outMsgCtx);    
+        nestPrc.setOutboundMessageContext(outMsgCtx);  
+        outMsgCtx.getSubcontext(OIDCPeerEntityContext.class).addSubcontext(createOAuth2ClientContext("demo_rp",null));
         
         // Setup inbound context.
         final MessageContext inMsgCtx = new MessageContext();
@@ -711,7 +691,8 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         final Map<String,String> mockProperties = Map.of(
                 "idp.service.clientinfo.failFast","false",
                 "idp.entityID", "http://idp.example.com/",
-                "idp.authn.oidc.rp.proxyIssuer",OP_ISSUER_ID);
+                "idp.authn.oidc.rp.proxyIssuer",OP_ISSUER_ID,
+                "idp.oidc.rp.redirecturl.allowedOrigins", RP_ALLOWED_ORIGINS);
         
         setMockProperties(mockProperties);
         
@@ -748,8 +729,8 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         outMsgCtx.setMessage(createAuthenticationRequest());        
         outMsgCtx.addSubcontext(createPeerContext());
         outMsgCtx.addSubcontext(createResponseTypeAndModeContext());
-        outMsgCtx.addSubcontext(createClientMedataContext());
         nestPrc.setOutboundMessageContext(outMsgCtx);    
+        outMsgCtx.getSubcontext(OIDCPeerEntityContext.class).addSubcontext(createOAuth2ClientContext("demo_rp",null));
         
         // Setup inbound context.
         final MessageContext inMsgCtx = new MessageContext();
@@ -791,7 +772,8 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         final Map<String,String> mockProperties = Map.of(
                 "idp.service.clientinfo.failFast","false",
                 "idp.entityID", "http://idp.example.com/",
-                "idp.authn.oidc.rp.proxyIssuer",OP_ISSUER_ID);
+                "idp.authn.oidc.rp.proxyIssuer",OP_ISSUER_ID,
+                "idp.oidc.rp.redirecturl.allowedOrigins", RP_ALLOWED_ORIGINS);
         
         setMockProperties(mockProperties);
         
@@ -828,8 +810,8 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         outMsgCtx.setMessage(createAuthenticationRequest());        
         outMsgCtx.addSubcontext(createPeerContext());
         outMsgCtx.addSubcontext(createResponseTypeAndModeContext());
-        outMsgCtx.addSubcontext(createClientMedataContext());
         nestPrc.setOutboundMessageContext(outMsgCtx);    
+        outMsgCtx.getSubcontext(OIDCPeerEntityContext.class).addSubcontext(createOAuth2ClientContext("demo_rp",null));
         
         // Setup inbound context.
         final MessageContext inMsgCtx = new MessageContext();
@@ -878,7 +860,8 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         final Map<String,String> mockProperties = Map.of(
                 "idp.service.clientinfo.failFast","false",
                 "idp.entityID", "http://idp.example.com/",
-                "idp.authn.oidc.rp.proxyIssuer",OP_ISSUER_ID);
+                "idp.authn.oidc.rp.proxyIssuer",OP_ISSUER_ID,
+                "idp.oidc.rp.redirecturl.allowedOrigins", RP_ALLOWED_ORIGINS);
         
         setMockProperties(mockProperties);
 
diff --git a/idp-oidc-rp-impl/src/test/resources/conf/authn/oidc-client-registration-authentication.xml b/idp-oidc-rp-impl/src/test/resources/conf/authn/oidc-client-registration-authentication.xml
new file mode 100644
index 0000000..667b19b
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/resources/conf/authn/oidc-client-registration-authentication.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+    xmlns:context="http://www.springframework.org/schema/context"
+    xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
+    xmlns:c="http://www.springframework.org/schema/c" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+
+    default-init-method="initialize" default-destroy-method="destroy">
+
+
+    <!-- 
+    Map clients to appropriate client authentication 
+    -->
+    
+    <util:map id="shibboleth.authn.oidc.rp.ClientIdToClientAuthenticationMap">
+        <entry key="mytestclient">
+            <bean parent="shibboleth.authn.oidc.rp.ClientAuthenticationDetails" c:clientSecret="myclientsecret"
+                c:tokenEndpointAuthMethod="client_secret_basic" />
+        </entry>
+        <entry key="demo_rp">
+            <bean parent="shibboleth.authn.oidc.rp.ClientAuthenticationDetails" c:clientSecret="myclientsecret"
+                c:tokenEndpointAuthMethod="client_secret_basic" />
+        </entry>
+    </util:map>
+
+</beans>
\ No newline at end of file
diff --git a/idp-oidc-rp-impl/src/test/resources/conf/authn/oidc-client-registration-clientid.xml b/idp-oidc-rp-impl/src/test/resources/conf/authn/oidc-client-registration-clientid.xml
new file mode 100644
index 0000000..d684f62
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/resources/conf/authn/oidc-client-registration-clientid.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+    xmlns:context="http://www.springframework.org/schema/context"
+    xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
+    xmlns:c="http://www.springframework.org/schema/c" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+
+    default-init-method="initialize" default-destroy-method="destroy">
+
+    <!-- 
+    Map issuers to appropriate clients 
+    -->
+
+    <util:map id="shibboleth.authn.oidc.rp.IssuerToClientIdMap">
+        <entry key="https://localhost:9918" value="mytestclient" />
+    </util:map>
+
+
+</beans>
\ No newline at end of file
diff --git a/idp-oidc-rp-impl/src/test/resources/conf/authn/oidc-providermetadata-resolvers.xml b/idp-oidc-rp-impl/src/test/resources/conf/authn/oidc-providermetadata-resolvers.xml
index c789a44..4f5bd93 100644
--- a/idp-oidc-rp-impl/src/test/resources/conf/authn/oidc-providermetadata-resolvers.xml
+++ b/idp-oidc-rp-impl/src/test/resources/conf/authn/oidc-providermetadata-resolvers.xml
@@ -19,7 +19,7 @@
    
    <bean id="ExampleHTTPResolver" parent="shibboleth.oidc.rp.OIDCProviderMetadataResolver">
         <constructor-arg>
-            <bean parent="shibboleth.oidc.rp.DynamicCacheBuilder">
+            <bean parent="shibboleth.oidc.rp.CacheBuilder">
                 <constructor-arg>
                     <bean p:cacheId="ExampleHTTPDynamicResolver" parent="shibboleth.oidc.rp.BaseProviderDynamicCacheBuilderSpec" 
                     p:minCacheDuration="PT1M"
diff --git a/pom.xml b/pom.xml
index 2f965b9..05904f9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -20,8 +20,6 @@
         <opensaml.groupId>org.opensaml</opensaml.groupId>
         <opensaml.version>4.2.0-SNAPSHOT</opensaml.version>
         <oidc.common.version>1.2.0-SNAPSHOT</oidc.common.version>
-        <!-- This is tmp until certain classes move over to commons -->
-        <oidc.op.version>3.1.0-SNAPSHOT</oidc.op.version>
         <checkstyle.configLocation>${project.basedir}/checkstyle.xml</checkstyle.configLocation>
     </properties>
 
@@ -97,17 +95,6 @@
                 <artifactId>idp-plugin-oidc-rp-impl</artifactId>
                 <version>${project.version}</version>
             </dependency>
-            <!-- Tmp OP deps until things move to commons -->
-            <dependency>
-                <groupId>net.shibboleth.idp.plugin.oidc</groupId>
-                <artifactId>idp-plugin-oidc-op-api</artifactId>
-                <version>${oidc.op.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>net.shibboleth.idp.plugin.oidc</groupId>
-                <artifactId>idp-plugin-oidc-op-impl</artifactId>
-                <version>${oidc.op.version}</version>
-            </dependency>
             <!-- OIDC Common BOM when importing OIDC dependencies -->
             <dependency>
                 <groupId>net.shibboleth.oidc</groupId>

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


More information about the commits mailing list