[java-plugin-shibd-oidc] 04/06: WiP for logout: Add basic logout request message handlers

Codeberg noreply at shibboleth.net
Fri Jul 3 13:33:01 UTC 2026


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

codeberg pushed a commit to branch dev/JSHIBDOIDC-28
in repository java-plugin-shibd-oidc.

View the commit online:
https://codeberg.org/Shibboleth/java-plugin-shibd-oidc/commit/db84456de822f06ad7a807dd4d703fc1158d6da1

commit db84456de822f06ad7a807dd4d703fc1158d6da1
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Jun 26 17:10:06 2026 +0100

    WiP for logout: Add basic logout request message handlers
    
     - Add basic logout request message handlers to build the logout request
     - Some of these will need to be moved into commons.
---
 ...outRequestFromMessageContextLookupFunction.java |  37 ++++
 ...oviderMetadataLogoutEndpointLookupStrategy.java |  70 +++++++
 .../shibboleth/sp/oidc/testing/TestConstants.java  |   7 +-
 .../idp/flows/sp/consumer/oidc/oidc-beans.xml      |   2 +-
 .../idp/flows/sp/initiator/oidc/oidc-beans.xml     |  17 --
 .../idp/flows/sp/initiator/oidc/oidc-flow.xml      |   2 +-
 .../flows/sp/logout/consumer/oidc/oidc-beans.xml   |   2 +-
 .../flows/sp/logout/initiator/oidc/oidc-beans.xml  |  75 +++++++-
 .../flows/sp/logout/initiator/oidc/oidc-flow.xml   |  22 ++-
 .../shibboleth/idp/flows/sp/oidc-common-beans.xml  |  30 ++-
 .../net/shibboleth/sp/service/agent/postconfig.xml |  18 +-
 .../oidc/flows/OIDCSessionInitiatorFlowTest.java   |  20 +-
 .../sp/oidc/flows/OIDCTokenConsumerFlowTest.java   |  64 +++----
 ...DCTokenConsumerFlowUsingStorageServiceTest.java |   6 +-
 .../resources/metadata/openid-configuration.json   |   4 +-
 ...tLogoutRequestParameterValueMessageHandler.java | 204 +++++++++++++++++++++
 .../sp/oidc/profile/impl/AddClientIDHandler.java   |  50 +++++
 .../oidc/profile/impl/AddIDTokenHintHandler.java   |  52 ++++++
 .../sp/oidc/profile/impl/AddLoginHintHandler.java  |  50 +++++
 .../impl/AddPostLogoutRedirectURIHandler.java      |  52 ++++++
 .../sp/oidc/profile/impl/AddUILocalesHandler.java  |  68 +++++++
 .../oidc/profile/impl/InitializeLogoutRequest.java | 151 +++++++++++++++
 .../impl/InitializeOutboundMessageContext.java     |   2 +-
 .../InitializeRelyingPartyContextFromOIDCPeer.java |   2 +
 .../impl/ProcessLogoutInitiatorRequest.java        |   2 +
 25 files changed, 924 insertions(+), 85 deletions(-)

diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/context/navigate/LogoutRequestFromMessageContextLookupFunction.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/context/navigate/LogoutRequestFromMessageContextLookupFunction.java
new file mode 100644
index 0000000..4e64221
--- /dev/null
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/context/navigate/LogoutRequestFromMessageContextLookupFunction.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed 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.sp.oidc.messaging.context.navigate;
+
+import java.util.function.Function;
+
+import org.opensaml.messaging.context.MessageContext;
+
+import net.shibboleth.oidc.profile.core.OIDCLogoutRequest;
+
+/**
+ * A function that returns the {@link OIDCLogoutRequest} from the given {@link MessageContext}.
+ */
+public class LogoutRequestFromMessageContextLookupFunction implements Function<MessageContext, OIDCLogoutRequest>{
+
+    /** {@inheritDoc} */
+    @Override
+    public OIDCLogoutRequest apply(final MessageContext msgContext) {
+        if (msgContext.getMessage() instanceof final OIDCLogoutRequest request) {
+            return request;
+        }
+        return null;
+    }
+
+}
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/context/navigate/ProviderMetadataLogoutEndpointLookupStrategy.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/context/navigate/ProviderMetadataLogoutEndpointLookupStrategy.java
new file mode 100644
index 0000000..70572e5
--- /dev/null
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/context/navigate/ProviderMetadataLogoutEndpointLookupStrategy.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed 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.sp.oidc.messaging.context.navigate;
+
+import java.net.URI;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+import net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext;
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * A strategy to locate the end_session_endpoint (logout) of the OP from it's discovery metadata.
+ */
+public class ProviderMetadataLogoutEndpointLookupStrategy implements Function<ProfileRequestContext, URI> {
+    
+    /** Lookup strategy to locate the OpenID Provider metadata to use.*/
+    @NonnullAfterInit 
+    private final Function<ProfileRequestContext, OIDCProviderMetadataContext> providerMetadataLookupStrategy;
+    
+    /**
+     * 
+     * Constructor.
+     *
+     * @param strategy strategy to locate the provider metadata context.
+     */
+    public ProviderMetadataLogoutEndpointLookupStrategy(
+           @ParameterName(name="providerMetadataLookupStrategy") @Nonnull final 
+           Function<ProfileRequestContext, OIDCProviderMetadataContext> strategy) {
+        providerMetadataLookupStrategy = 
+                Constraint.isNotNull(strategy, "ProviderMetadataLookupStrategy can not be null");
+    }
+    
+
+    /** {@inheritDoc} */
+    @Override
+    public URI apply(final ProfileRequestContext prc) {
+        
+        final OIDCProviderMetadataContext providerMetadataContext = providerMetadataLookupStrategy.apply(prc);
+        if (providerMetadataContext == null) {
+            return null;
+        }
+        
+        final OIDCProviderMetadata metadata = providerMetadataContext.getProviderInformation();
+        if (metadata == null) {
+            return null;
+        }
+        return metadata.getEndSessionEndpointURI();
+    }
+
+}
diff --git a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/testing/TestConstants.java b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/testing/TestConstants.java
index 10849a3..1ec7ffb 100644
--- a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/testing/TestConstants.java
+++ b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/testing/TestConstants.java
@@ -46,8 +46,11 @@ public final class TestConstants {
     /** State token string used in cookie names. */
     public static final String STATE_TOKEN = "1761316967710_1622a5c726da8f7b36e24f19eed82aea";    
 
-    /** Application ID. */
-    public static final String APPLICATION_ID = "test-oidc-application-with-ro";
+    /** Application ID for an application that uses the request object in the request. */
+    public static final String APPLICATION_ID_REQUEST_OBJECT = "test-oidc-application-with-ro";    
+    
+    /** Default Application ID that supports all default profiles and no special configuration. */
+    public static final String APPLICATION_ID = "test-oidc-application-with-default-profile";
     
     /** Application ID used when private_key_jwt has been configured.*/
     public static final String APPLICATION_ID_PRIVATE_KEY_JWT = "test-oidc-application-with-ro-private-key-jwt";
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-beans.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-beans.xml
index 758dbac..a3be0e8 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-beans.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-beans.xml
@@ -57,7 +57,7 @@
     <bean id="SelectProfileConfiguration"
         class="net.shibboleth.idp.profile.impl.SelectProfileConfiguration" scope="prototype"
         p:profileId-ref="shibboleth.sp.oidc.ProfileId" />
-    
+
     <!-- Build the Token endpoint client authentication method based on the inbound context -->
     <bean id="InitializeOAuth2ClientAuthenticationContextHandler" parent="WebFlowInboundMessageHandlerAdaptor"
         scope="prototype">
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/oidc/oidc-beans.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/oidc/oidc-beans.xml
index 2ae3c46..68f07e2 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/oidc/oidc-beans.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/oidc/oidc-beans.xml
@@ -29,26 +29,9 @@
         p:relyingPartyLookupStrategy-ref="shibboleth.RelyingPartyIdLookup.Simple">
     </bean>
 
-    <bean id="InitializeRelyingPartyContextFromOIDCPeer"
-        class="net.shibboleth.sp.oidc.profile.impl.InitializeRelyingPartyContextFromOIDCPeer" scope="prototype" />
-
-    <bean id="SelectRelyingPartyConfiguration"
-        class="net.shibboleth.sp.profile.impl.SelectRelyingPartyConfiguration" scope="prototype" />
-
-    <bean id="SelectProfileConfiguration" class="net.shibboleth.idp.profile.impl.SelectProfileConfiguration"
-        scope="prototype" p:profileId-ref="shibboleth.sp.oidc.ProfileId" />
-
     <!-- <bean id="InitializeOutboundMessageContext" class="net.shibboleth.idp.saml.profile.impl.InitializeOutboundMessageContext" 
         scope="prototype" p:selfIdentityLookupStrategy-ref="shibboleth.IssuerLookup.Simple" /> -->
 
-    <!-- TODO, self context -->
-    <bean id="InitializeOutboundMessageContext"
-        class="net.shibboleth.sp.oidc.profile.impl.InitializeOutboundMessageContext" scope="prototype" />
-
-    <bean id="InitializeOAuth2ClientContext" scope="prototype"
-        class="net.shibboleth.sp.oidc.profile.impl.InitializeOAuth2ClientContext"
-        p:issuerLookupStrategy-ref="shibboleth.ClientIdLookup.Simple" />
-
     <bean id="InitializeAuthorizationRequest"
         class="net.shibboleth.sp.oidc.profile.impl.InitializeAuthorizationRequest" scope="prototype" />
 
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/oidc/oidc-flow.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/oidc/oidc-flow.xml
index 297154b..a8f3e86 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/oidc/oidc-flow.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/oidc/oidc-flow.xml
@@ -5,7 +5,7 @@
     
     <action-state id="OIDCSessionInitiator">
         <evaluate expression="ValidateSessionInitiatorRequest" />
-        <evaluate expression="PrepareInboundMessageContext" /> <!-- needs session and logout support -->
+        <evaluate expression="PrepareInboundMessageContext" />
         <evaluate expression="ProviderMetadataLookup" />
         
         <evaluate expression="InitializeRelyingPartyContextFromOIDCPeer" />
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/oidc/oidc-beans.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/oidc/oidc-beans.xml
index c0154e6..7b37f0d 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/oidc/oidc-beans.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/oidc/oidc-beans.xml
@@ -7,7 +7,7 @@
                            http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
     default-init-method="initialize" default-destroy-method="destroy">
 
-    <util:constant id="shiibboleth.sp.ProfileId"
+    <util:constant id="shibboleth.sp.oidc.ProfileId"
         static-field="net.shibboleth.saml.saml2.profile.config.SingleLogoutProfileConfiguration.PROFILE_ID" />
 
     <util:constant id="shibboleth.EndpointType"
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/initiator/oidc/oidc-beans.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/initiator/oidc/oidc-beans.xml
index b4f14c4..937c0b4 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/initiator/oidc/oidc-beans.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/initiator/oidc/oidc-beans.xml
@@ -7,14 +7,20 @@
                            http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
     default-init-method="initialize" default-destroy-method="destroy">
 
-    <util:constant id="shiibboleth.sp.ProfileId"
-        static-field="net.shibboleth.saml.saml2.profile.config.SingleLogoutProfileConfiguration.PROFILE_ID" />
+    <util:constant id="shibboleth.sp.oidc.ProfileId"
+        static-field="net.shibboleth.oidc.profile.config.OIDCLogoutProfileConfiguration.PROFILE_ID" />
 
     <import resource="classpath:/META-INF/net/shibboleth/idp/flows/sp/oidc-common-beans.xml" />
 
     <bean id="ProcessLogoutInitiatorRequest"
         class="net.shibboleth.sp.oidc.profile.impl.ProcessLogoutInitiatorRequest" scope="prototype"
         p:dataSealer-ref="#{'%{sp.dataSealer:shibboleth.DataSealer}'.trim()}"/>
+        
+    <!-- Prepare the OIDC Peer Entity with the relying party ID (the OP identifier) -->
+    <bean id="PrepareInboundMessageContext"
+        class="net.shibboleth.sp.oidc.profile.impl.PrepareOIDCInboundMessageContext" scope="prototype"
+        p:relyingPartyLookupStrategy-ref="shibboleth.RelyingPartyIdLookup.Simple">
+    </bean>
 
     <!-- <util:constant id="shibboleth.EndpointType"
         static-field="org.opensaml.saml.saml2.metadata.SingleLogoutService.DEFAULT_ELEMENT_NAME" />
@@ -28,4 +34,69 @@
         </property>
     </bean> -->
     
+    <bean id="InitializeLogoutRequest" class="net.shibboleth.sp.oidc.profile.impl.InitializeLogoutRequest">
+        <property name="logoutEndpointLookupStrategy">
+            <bean id="logoutEndpointLookupStrategy" 
+                class="net.shibboleth.sp.oidc.messaging.context.navigate.ProviderMetadataLogoutEndpointLookupStrategy"
+                c:providerMetadataLookupStrategy-ref="shibboleth.ChildLookup.OIDCProviderMetadataContextFromOutbound"/>
+        </property>
+    </bean>
+    
+    <!-- Abstract LogoutRequestHandler bean to establish strategies -->
+    <bean id="LogoutRequestHandler" abstract="true" 
+        p:providerMetadataLookupStrategy-ref="shibboleth.ChildLookup.OIDCProviderMetadataContextFromPeerContext">
+        <property name="logoutRequestLookupStrategy">
+            <bean class="net.shibboleth.sp.oidc.messaging.context.navigate.LogoutRequestFromMessageContextLookupFunction"/>
+        </property>
+    </bean>
+    
+     <bean id="BuildLogoutRequest" parent="WebFlowOutboundMessageHandlerAdaptor" scope="prototype">
+        <constructor-arg>
+            <bean class="org.opensaml.messaging.handler.impl.BasicMessageHandlerChain">
+                <property name="handlers">
+                    <list>
+                        <bean id="AddIDTokenHint" scope="prototype" parent="LogoutRequestHandler"
+                            class="net.shibboleth.sp.oidc.profile.impl.AddIDTokenHintHandler">
+                           <!--  <property name="parameterValueLookupStrategy">
+                                <bean class="net.shibboleth.sp.oidc.profile.config.navigate.ResponseTypeLookupStrategy"
+                                    scope="prototype" />
+                            </property> -->
+                        </bean>
+                        <bean id="AddLoginHint" scope="prototype" parent="LogoutRequestHandler"
+                            class="net.shibboleth.sp.oidc.profile.impl.AddLoginHintHandler">
+                           <!--  <property name="parameterValueLookupStrategy">
+                                <bean class="net.shibboleth.sp.oidc.profile.config.navigate.ResponseTypeLookupStrategy"
+                                    scope="prototype" />
+                            </property> -->
+                        </bean>
+                        <bean id="AddClientID" scope="prototype" parent="LogoutRequestHandler"
+                            class="net.shibboleth.sp.oidc.profile.impl.AddClientIDHandler">
+                           <!--  <property name="parameterValueLookupStrategy">
+                                <bean class="net.shibboleth.sp.oidc.profile.config.navigate.ResponseTypeLookupStrategy"
+                                    scope="prototype" />
+                            </property> -->
+                        </bean>
+                        <bean id="AddPostLogoutRedirectURI" scope="prototype" parent="LogoutRequestHandler"
+                            class="net.shibboleth.sp.oidc.profile.impl.AddPostLogoutRedirectURIHandler">
+                           <!--  <property name="parameterValueLookupStrategy">
+                                <bean class="net.shibboleth.sp.oidc.profile.config.navigate.ResponseTypeLookupStrategy"
+                                    scope="prototype" />
+                            </property> -->
+                        </bean>
+                        <bean id="AddUILocales" scope="prototype" parent="LogoutRequestHandler"
+                            class="net.shibboleth.sp.oidc.profile.impl.AddUILocalesHandler">
+                           <!--  <property name="parameterValueLookupStrategy">
+                                <bean class="net.shibboleth.sp.oidc.profile.config.navigate.ResponseTypeLookupStrategy"
+                                    scope="prototype" />
+                            </property> -->
+                        </bean>
+                    </list>
+                </property>
+            </bean>
+        </constructor-arg>
+        <property name="errorEvent">
+            <util:constant static-field="org.opensaml.profile.action.EventIds.INVALID_MESSAGE" />
+        </property>
+    </bean>
+    
 </beans>
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/initiator/oidc/oidc-flow.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/initiator/oidc/oidc-flow.xml
index fb24083..017bbcb 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/initiator/oidc/oidc-flow.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/initiator/oidc/oidc-flow.xml
@@ -5,25 +5,27 @@
 
     <action-state id="SAML2LogoutInitiator">
         <evaluate expression="ProcessLogoutInitiatorRequest" />
-        <!-- <evaluate expression="PrepareInboundMessageContext" />
-        <evaluate expression="SAMLProtocolAndRole" />
-        <evaluate expression="SAMLMetadataLookup" />
+        <evaluate expression="PrepareInboundMessageContext" />
+        <evaluate expression="ProviderMetadataLookup" />
         
-        <evaluate expression="InitializeRelyingPartyContextFromSAMLPeer" />
+        <evaluate expression="InitializeRelyingPartyContextFromOIDCPeer" /> <!-- Seems a bit redundant in logout as ProcessLogoutInitiatorRequest has already created it, although not quite in the right state -->
         <evaluate expression="SelectRelyingPartyConfiguration" />
         <evaluate expression="SelectProfileConfiguration" />
         
-        <evaluate expression="InitializeOutboundMessageContext" />
-        <evaluate expression="InitializeMessageChannelSecurityContext" />
+        
+       <evaluate expression="InitializeOutboundMessageContext" />
+  <!--      <evaluate expression="InitializeOAuth2ClientContext" />--> 
+        <evaluate expression="InitializeLogoutRequest" />
+       <!--  <evaluate expression="InitializeMessageChannelSecurityContext" />
         <evaluate expression="PopulateBindingAndEndpointContexts" />
 
         <evaluate expression="PopulateRequestSignatureSigningParameters" />
-        <evaluate expression="PopulateEncryptionParameters" />
+        <evaluate expression="PopulateEncryptionParameters" />-->
         
-        <evaluate expression="AddLogoutRequest" />
-        <evaluate expression="EncryptNameIDs" />
+        <evaluate expression="BuildLogoutRequest" />
+ <!--        <evaluate expression="EncryptNameIDs" /> -->
 
-        <evaluate expression="HandleOutboundMessage" />
+<!--        <evaluate expression="HandleOutboundMessage" />
         <evaluate expression="PreserveRelayState" />
         <evaluate expression="EncodeMessage" /> -->
         <evaluate expression="'proceed'" />
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/oidc-common-beans.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/oidc-common-beans.xml
index 0894f60..5125d99 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/oidc-common-beans.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/oidc-common-beans.xml
@@ -79,6 +79,10 @@
         class="org.opensaml.messaging.context.navigate.ChildContextLookup"
         c:type="#{ T(net.shibboleth.sp.context.StateDataContext) }" />
    
+   <bean id="shibboleth.ChildLookup.LogoutContext"
+        class="org.opensaml.messaging.context.navigate.ChildContextLookup"
+        c:type="#{ T(net.shibboleth.sp.oidc.context.OIDCLogoutContext) }" />
+   
    <bean id="shibboleth.ChildLookupOrCreate.StateDataContext"
         class="org.opensaml.messaging.context.navigate.ChildContextLookup"
         c:type="#{ T(net.shibboleth.sp.context.StateDataContext) }"
@@ -223,19 +227,39 @@
         </constructor-arg>
     </bean>
     
-    
     <!-- Common Actions -->
     
     <bean id="ProviderMetadataLookup" parent="WebFlowInboundMessageHandlerAdaptor" scope="prototype">
         <constructor-arg name="messageHandler">
             <bean class="net.shibboleth.sp.oidc.metadata.impl.OIDCProviderMetadataLookupHandler"
-                scope="prototype">
+                scope="prototype"
+                p:contextClassLookupStrategy-ref="shibboleth.ChildLookup.OIDCPeerEntityContext"> <!-- Add under the PeerEntityContext -->
                 <property name="ProviderMetadataResolverLookupStrategy">
                     <bean class="net.shibboleth.sp.oidc.profile.impl.ApplicationMetadataResolverLookupFunction" />
                 </property>
             </bean>
         </constructor-arg>
-    </bean>
+    </bean>    
+    
+    <bean id="InitializeRelyingPartyContextFromOIDCPeer"
+        class="net.shibboleth.sp.oidc.profile.impl.InitializeRelyingPartyContextFromOIDCPeer" scope="prototype" />
+    
+    
+    <bean id="SelectRelyingPartyConfiguration"
+        class="net.shibboleth.sp.profile.impl.SelectRelyingPartyConfiguration" scope="prototype" />
+        
+        
+    <bean id="SelectProfileConfiguration" 
+        class="net.shibboleth.idp.profile.impl.SelectProfileConfiguration" scope="prototype" 
+        p:profileId-ref="shibboleth.sp.oidc.ProfileId" />
+        
+     <!-- TODO, self context -->
+    <bean id="InitializeOutboundMessageContext"
+        class="net.shibboleth.sp.oidc.profile.impl.InitializeOutboundMessageContext" scope="prototype" />
+        
+    <bean id="InitializeOAuth2ClientContext" scope="prototype"
+        class="net.shibboleth.sp.oidc.profile.impl.InitializeOAuth2ClientContext"
+        p:issuerLookupStrategy-ref="shibboleth.ClientIdLookup.Simple" />
     
 
 </beans>
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/agent/postconfig.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/agent/postconfig.xml
index eeb0406..5590049 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/agent/postconfig.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/agent/postconfig.xml
@@ -29,7 +29,7 @@
         <property name="defaultProfileConfigurations">
             <list>
                 <ref bean="OIDC.SSO" />
-                <!-- <ref bean="OIDC.Logout" /> -->
+                <ref bean="OIDC.Logout"/>
             </list>
         </property>
         <property name="metadataDrivenDefaultProfileConfigurations">
@@ -107,6 +107,22 @@
         p:checkAddressPredicate="%{sp.oidc.checkAddress:false}">
     </bean>
     
+    <bean id="OIDC.Logout" parent="AbstractOIDCProfile" lazy-init="true"
+          class="net.shibboleth.oidc.profile.config.impl.DefaultOIDCLogoutConfiguration"/>
+          
+    <!-- TODO, Which of these profile options do we need? -->
+    <!-- 
+          p:issuer-ref="shibboleth.oidc.issuer"
+          p:logoutHintMatchingStrategy-ref="%{idp.oidc.logout.logoutHintMatchingStrategy:DefaultLogoutHintMatchingPredicate}"
+          p:securityConfiguration-ref="%{idp.security.oidc.logout.config:shibboleth.oidc.logout.DefaultSecurityConfiguration}"
+          p:preferFrontChannel="%{idp.oidc.logout.preferFrontChannel:true}"
+          p:frontChannelSuccess="%{idp.oidc.logout.frontChannelSuccess:false}"
+          p:revokeTokens="%{idp.oidc.logout.revokeTokens:true}"
+          p:requireIdTokenHint="%{idp.oidc.logout.requireIdTokenHint:true}"
+          p:encryptionOptional="%{idp.oidc.logout.encryptionOptional:true}"
+          p:ignoreInvalidPostLogoutRedirectUri="%{idp.oidc.logout.ignoreInvalidPostLogoutRedirectUri:false}" -->
+
+    
      <util:constant id="OIDC.SSO.FEATURE_ESSENTIAL_ACR_REQUEST"
         static-field="net.shibboleth.oidc.profile.config.OIDCAuthorizationConfiguration.FEATURE_ESSENTIAL_ACR_REQUEST"/>
         
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCSessionInitiatorFlowTest.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCSessionInitiatorFlowTest.java
index 96ce49e..6d2f2ca 100644
--- a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCSessionInitiatorFlowTest.java
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCSessionInitiatorFlowTest.java
@@ -161,7 +161,7 @@ public class OIDCSessionInitiatorFlowTest extends AbstractSPFlowTest {
         input.addmember(RemotedHttpServletRequest.STRUCTURE_NAME).structure();        
         input.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
         input.addmember(SPConstants.TARGET).unsafe_string(RESOURCE_URL);       
-        setApplicationRequest(TestConstants.APPLICATION_ID, input);
+        setApplicationRequest(TestConstants.APPLICATION_ID_REQUEST_OBJECT, input);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         assertFlowExecutionResult(result, FLOW_ID);
@@ -234,7 +234,7 @@ public class OIDCSessionInitiatorFlowTest extends AbstractSPFlowTest {
         input.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
         input.addmember(SPConstants.TARGET).unsafe_string(RESOURCE_URL);     
         input.addmember(SPConstants.STATE).string("state");
-        setApplicationRequest(TestConstants.APPLICATION_ID, input);
+        setApplicationRequest(TestConstants.APPLICATION_ID_REQUEST_OBJECT, input);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         assertFlowExecutionResult(result, FLOW_ID);
@@ -257,7 +257,7 @@ public class OIDCSessionInitiatorFlowTest extends AbstractSPFlowTest {
         input.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
         input.addmember(SPConstants.TARGET).unsafe_string(RESOURCE_URL); 
         input.addmember(OIDCInitiatorConstants.FORCE_AUTHN).integer(1);
-        setApplicationRequest(TestConstants.APPLICATION_ID, input);
+        setApplicationRequest(TestConstants.APPLICATION_ID_REQUEST_OBJECT, input);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         assertFlowExecutionResult(result, FLOW_ID);
@@ -282,7 +282,7 @@ public class OIDCSessionInitiatorFlowTest extends AbstractSPFlowTest {
         input.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
         input.addmember(SPConstants.TARGET).unsafe_string(RESOURCE_URL); 
         input.addmember(OIDCInitiatorConstants.MAX_AGE).longinteger(60l);
-        setApplicationRequest(TestConstants.APPLICATION_ID, input);
+        setApplicationRequest(TestConstants.APPLICATION_ID_REQUEST_OBJECT, input);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         assertFlowExecutionResult(result, FLOW_ID);
@@ -306,7 +306,7 @@ public class OIDCSessionInitiatorFlowTest extends AbstractSPFlowTest {
         input.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
         input.addmember(SPConstants.TARGET).unsafe_string(RESOURCE_URL); 
         input.addmember(OIDCInitiatorConstants.PROMPT).string("none");
-        setApplicationRequest(TestConstants.APPLICATION_ID, input);
+        setApplicationRequest(TestConstants.APPLICATION_ID_REQUEST_OBJECT, input);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         assertFlowExecutionResult(result, FLOW_ID);
@@ -333,7 +333,7 @@ public class OIDCSessionInitiatorFlowTest extends AbstractSPFlowTest {
         aclist.add(new DDF(null).string("loa1"));
         aclist.add(new DDF(null).string("loa2"));
         
-        setApplicationRequest(TestConstants.APPLICATION_ID, input);
+        setApplicationRequest(TestConstants.APPLICATION_ID_REQUEST_OBJECT, input);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         assertFlowExecutionResult(result, FLOW_ID);
@@ -402,7 +402,7 @@ public class OIDCSessionInitiatorFlowTest extends AbstractSPFlowTest {
         resourceList.add(new DDF(null).string("https://cal.example.com"));
         resourceList.add(new DDF(null).string("https://mail.example.com"));
         
-        setApplicationRequest(TestConstants.APPLICATION_ID, input);
+        setApplicationRequest(TestConstants.APPLICATION_ID_REQUEST_OBJECT, input);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         assertFlowExecutionResult(result, FLOW_ID);
@@ -428,7 +428,7 @@ public class OIDCSessionInitiatorFlowTest extends AbstractSPFlowTest {
         input.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
         input.addmember(SPConstants.TARGET).unsafe_string(RESOURCE_URL); 
         input.addmember(OIDCInitiatorConstants.SCOPE).string("email");
-        setApplicationRequest(TestConstants.APPLICATION_ID, input);
+        setApplicationRequest(TestConstants.APPLICATION_ID_REQUEST_OBJECT, input);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         assertFlowExecutionResult(result, FLOW_ID);
@@ -452,7 +452,7 @@ public class OIDCSessionInitiatorFlowTest extends AbstractSPFlowTest {
         input.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
         input.addmember(SPConstants.TARGET).unsafe_string(RESOURCE_URL); 
         input.addmember(OIDCInitiatorConstants.UI_LOCALES).string("fr-CA fr en");
-        setApplicationRequest(TestConstants.APPLICATION_ID, input);
+        setApplicationRequest(TestConstants.APPLICATION_ID_REQUEST_OBJECT, input);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         assertFlowExecutionResult(result, FLOW_ID);
@@ -477,7 +477,7 @@ public class OIDCSessionInitiatorFlowTest extends AbstractSPFlowTest {
         input.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
         input.addmember(SPConstants.TARGET).unsafe_string(RESOURCE_URL); 
         input.addmember(OIDCInitiatorConstants.DISPLAY).string("page");
-        setApplicationRequest(TestConstants.APPLICATION_ID, input);
+        setApplicationRequest(TestConstants.APPLICATION_ID_REQUEST_OBJECT, input);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         assertFlowExecutionResult(result, FLOW_ID);
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowTest.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowTest.java
index a06afe6..d6bb66f 100644
--- a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowTest.java
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowTest.java
@@ -99,10 +99,10 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
         // Add cookies
         input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
                 TestConstants.STATE_TOKEN, 
-                TestConstants.APPLICATION_ID,
+                TestConstants.APPLICATION_ID_REQUEST_OBJECT,
                 TestHelper.buildAuthenticationState(null, false, null), true));
         
-        setApplicationRequest(TestConstants.APPLICATION_ID, input);
+        setApplicationRequest(TestConstants.APPLICATION_ID_REQUEST_OBJECT, input);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(TestConstants.FLOW_ID, null, externalContext);
         assertFlowExecutionResult(result, TestConstants.FLOW_ID);
@@ -204,10 +204,10 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
         // Add cookies
         input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
                 TestConstants.STATE_TOKEN,
-                TestConstants.APPLICATION_ID,
+                TestConstants.APPLICATION_ID_REQUEST_OBJECT,
                 TestHelper.buildAuthenticationState(Duration.ofMinutes(1), true, null), true));
         
-        setApplicationRequest(TestConstants.APPLICATION_ID, input);
+        setApplicationRequest(TestConstants.APPLICATION_ID_REQUEST_OBJECT, input);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(TestConstants.FLOW_ID, null, externalContext);
         assertFlowExecutionResult(result, TestConstants.FLOW_ID);
@@ -238,10 +238,10 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
         // Add cookies
         input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
                 TestConstants.STATE_TOKEN,
-                TestConstants.APPLICATION_ID,
+                TestConstants.APPLICATION_ID_REQUEST_OBJECT,
                 TestHelper.buildAuthenticationState(Duration.ofMinutes(1), true, null), true));
         
-        setApplicationRequest(TestConstants.APPLICATION_ID, input);
+        setApplicationRequest(TestConstants.APPLICATION_ID_REQUEST_OBJECT, input);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(TestConstants.FLOW_ID, null, externalContext);
         assertFlowExecutionResult(result, TestConstants.FLOW_ID);
@@ -271,10 +271,10 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
         // Add cookies
         input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
                 TestConstants.STATE_TOKEN, 
-                TestConstants.APPLICATION_ID,
+                TestConstants.APPLICATION_ID_REQUEST_OBJECT,
                 TestHelper.buildAuthenticationState(null, false, null), true));
         
-        setApplicationRequest(TestConstants.APPLICATION_ID, input);
+        setApplicationRequest(TestConstants.APPLICATION_ID_REQUEST_OBJECT, input);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(TestConstants.FLOW_ID, null, externalContext);
         assertFlowExecutionResult(result, TestConstants.FLOW_ID);
@@ -305,10 +305,10 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
         // Add cookies
         input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
                 TestConstants.STATE_TOKEN, 
-                TestConstants.APPLICATION_ID,
+                TestConstants.APPLICATION_ID_REQUEST_OBJECT,
                 TestHelper.buildAuthenticationState(null, false, null), true));
         
-        setApplicationRequest(TestConstants.APPLICATION_ID, input);
+        setApplicationRequest(TestConstants.APPLICATION_ID_REQUEST_OBJECT, input);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(TestConstants.FLOW_ID, null, externalContext);
         assertFlowExecutionResult(result, TestConstants.FLOW_ID);
@@ -340,10 +340,10 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
         // Add cookies
         input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
                 TestConstants.STATE_TOKEN, 
-                TestConstants.APPLICATION_ID,
+                TestConstants.APPLICATION_ID_REQUEST_OBJECT,
                 TestHelper.buildAuthenticationState(null, false, CollectionSupport.listOf("loa1")), true));
         
-        setApplicationRequest(TestConstants.APPLICATION_ID, input);
+        setApplicationRequest(TestConstants.APPLICATION_ID_REQUEST_OBJECT, input);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(TestConstants.FLOW_ID, null, externalContext);
         assertFlowExecutionResult(result, TestConstants.FLOW_ID);
@@ -378,10 +378,10 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
         // Add cookies
         input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
                 TestConstants.STATE_TOKEN, 
-                TestConstants.APPLICATION_ID,
+                TestConstants.APPLICATION_ID_REQUEST_OBJECT,
                 TestHelper.buildAuthenticationState(null, false, CollectionSupport.listOf("loa1")), true));
         
-        setApplicationRequest(TestConstants.APPLICATION_ID, input);
+        setApplicationRequest(TestConstants.APPLICATION_ID_REQUEST_OBJECT, input);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(TestConstants.FLOW_ID, null, externalContext);
         assertFlowExecutionResult(result, TestConstants.FLOW_ID);
@@ -413,10 +413,10 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
         // Add cookies
         input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
                 TestConstants.STATE_TOKEN, 
-                TestConstants.APPLICATION_ID,
+                TestConstants.APPLICATION_ID_REQUEST_OBJECT,
                 TestHelper.buildAuthenticationState(null, false, null), true));
         
-        setApplicationRequest(TestConstants.APPLICATION_ID, input);
+        setApplicationRequest(TestConstants.APPLICATION_ID_REQUEST_OBJECT, input);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(TestConstants.FLOW_ID, null, externalContext);
         assertFlowExecutionResult(result, TestConstants.FLOW_ID);
@@ -446,10 +446,10 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
         // Add cookies
         input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
                 TestConstants.STATE_TOKEN, 
-                TestConstants.APPLICATION_ID,
+                TestConstants.APPLICATION_ID_REQUEST_OBJECT,
                 TestHelper.buildAuthenticationState(null, false, null), true));
 
-        setApplicationRequest(TestConstants.APPLICATION_ID, input);
+        setApplicationRequest(TestConstants.APPLICATION_ID_REQUEST_OBJECT, input);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(TestConstants.FLOW_ID, null, externalContext);
         assertFlowExecutionResult(result, TestConstants.FLOW_ID);
@@ -480,10 +480,10 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
         // Add cookies
         input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
                 TestConstants.STATE_TOKEN, 
-                TestConstants.APPLICATION_ID,
+                TestConstants.APPLICATION_ID_REQUEST_OBJECT,
                 TestHelper.buildAuthenticationState(null, false, null), true));
 
-        setApplicationRequest(TestConstants.APPLICATION_ID, input);
+        setApplicationRequest(TestConstants.APPLICATION_ID_REQUEST_OBJECT, input);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(TestConstants.FLOW_ID, null, externalContext);
         assertFlowExecutionResult(result, TestConstants.FLOW_ID);
@@ -511,10 +511,10 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
         // Add cookies
         input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
                 TestConstants.STATE_TOKEN, 
-                TestConstants.APPLICATION_ID,
+                TestConstants.APPLICATION_ID_REQUEST_OBJECT,
                 TestHelper.buildAuthenticationState(null, false, null), true));
         
-        setApplicationRequest(TestConstants.APPLICATION_ID, input);
+        setApplicationRequest(TestConstants.APPLICATION_ID_REQUEST_OBJECT, input);
         final FlowExecutionResult result = flowExecutor.launchExecution(TestConstants.FLOW_ID, null, externalContext);
         assertFlowExecutionResult(result, TestConstants.FLOW_ID);
         assertFlowExecutionOutcome(result.getOutcome());
@@ -542,10 +542,10 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
         // Add cookies
         input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
                 TestConstants.STATE_TOKEN,
-                TestConstants.APPLICATION_ID,
+                TestConstants.APPLICATION_ID_REQUEST_OBJECT,
                 TestHelper.buildAuthenticationState(null, false, null), true));
 
-        setApplicationRequest(TestConstants.APPLICATION_ID, input);
+        setApplicationRequest(TestConstants.APPLICATION_ID_REQUEST_OBJECT, input);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(TestConstants.FLOW_ID, null, externalContext);
         assertFlowExecutionResult(result, TestConstants.FLOW_ID);
@@ -573,10 +573,10 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
         // Add cookies
         input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
                 TestConstants.STATE_TOKEN, 
-                TestConstants.APPLICATION_ID,
+                TestConstants.APPLICATION_ID_REQUEST_OBJECT,
                 TestHelper.buildAuthenticationState(null, false, null), true));
 
-        setApplicationRequest(TestConstants.APPLICATION_ID, input);
+        setApplicationRequest(TestConstants.APPLICATION_ID_REQUEST_OBJECT, input);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(TestConstants.FLOW_ID, null, externalContext);
         assertFlowExecutionResult(result, TestConstants.FLOW_ID);
@@ -599,10 +599,10 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
         // Add cookies
         input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
                 TestConstants.STATE_TOKEN, 
-                TestConstants.APPLICATION_ID,
+                TestConstants.APPLICATION_ID_REQUEST_OBJECT,
                 TestHelper.buildAuthenticationState(null, false, null), true));
 
-        setApplicationRequest(TestConstants.APPLICATION_ID, input);
+        setApplicationRequest(TestConstants.APPLICATION_ID_REQUEST_OBJECT, input);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(TestConstants.FLOW_ID, null, externalContext);
         assertFlowExecutionResult(result, TestConstants.FLOW_ID);
@@ -630,10 +630,10 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
         // Add cookies
         input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
                 TestConstants.STATE_TOKEN, 
-                TestConstants.APPLICATION_ID,
+                TestConstants.APPLICATION_ID_REQUEST_OBJECT,
                 TestHelper.buildAuthenticationState(null, false, null), true));
 
-        setApplicationRequest(TestConstants.APPLICATION_ID, input);
+        setApplicationRequest(TestConstants.APPLICATION_ID_REQUEST_OBJECT, input);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(TestConstants.FLOW_ID, null, externalContext);
         assertFlowExecutionResult(result, TestConstants.FLOW_ID);
@@ -654,10 +654,10 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
         // Add cookies
         inputSuccessResponse.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
                 TestConstants.STATE_TOKEN, 
-                TestConstants.APPLICATION_ID,
+                TestConstants.APPLICATION_ID_REQUEST_OBJECT,
                 TestHelper.buildAuthenticationState(null, false, null), true));       
         
-        setApplicationRequest(TestConstants.APPLICATION_ID, inputSuccessResponse);
+        setApplicationRequest(TestConstants.APPLICATION_ID_REQUEST_OBJECT, inputSuccessResponse);
         
         final FlowExecutionResult resultReplayFail = 
                 flowExecutor.launchExecution(TestConstants.FLOW_ID, null, externalContext);
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowUsingStorageServiceTest.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowUsingStorageServiceTest.java
index eb8b658..a3a5fd6 100644
--- a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowUsingStorageServiceTest.java
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowUsingStorageServiceTest.java
@@ -107,7 +107,7 @@ public class OIDCTokenConsumerFlowUsingStorageServiceTest extends AbstractOIDCTo
         
         //TODO this is brittle
         final StringBuilder builder = new StringBuilder(StorageServiceStateManager.class.getName());
-        builder.append('!').append("testsp.example.org").append('!').append(TestConstants.APPLICATION_ID);
+        builder.append('!').append("testsp.example.org").append('!').append(TestConstants.APPLICATION_ID_REQUEST_OBJECT);
         final String context = builder.toString();
         storageService.create(context, 
                 TestConstants.STATE_COOKIE_STORAGE_KEY, encoded, 
@@ -136,10 +136,10 @@ public class OIDCTokenConsumerFlowUsingStorageServiceTest extends AbstractOIDCTo
         // Add cookies
         input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
                 TestConstants.STATE_TOKEN, 
-                TestConstants.APPLICATION_ID,
+                TestConstants.APPLICATION_ID_REQUEST_OBJECT,
                 TestConstants.STATE_COOKIE_STORAGE_KEY, false));
         
-        setApplicationRequest(TestConstants.APPLICATION_ID, input);
+        setApplicationRequest(TestConstants.APPLICATION_ID_REQUEST_OBJECT, input);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(TestConstants.FLOW_ID, null, externalContext);
         assertFlowExecutionResult(result, TestConstants.FLOW_ID);
diff --git a/sp-oidc-conf-impl/src/test/resources/metadata/openid-configuration.json b/sp-oidc-conf-impl/src/test/resources/metadata/openid-configuration.json
index 592e3b4..e6b8813 100644
--- a/sp-oidc-conf-impl/src/test/resources/metadata/openid-configuration.json
+++ b/sp-oidc-conf-impl/src/test/resources/metadata/openid-configuration.json
@@ -56,5 +56,7 @@
 "grant_types_supported": [
 "authorization_code",
 "refresh_token"
-]
+],
+"end_session_endpoint":
+   "https://op.example.org/end_session"
 }
\ No newline at end of file
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AbstractLogoutRequestParameterValueMessageHandler.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AbstractLogoutRequestParameterValueMessageHandler.java
new file mode 100644
index 0000000..4ae0dde
--- /dev/null
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AbstractLogoutRequestParameterValueMessageHandler.java
@@ -0,0 +1,204 @@
+/*
+ * Licensed 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.sp.oidc.profile.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.AbstractMessageHandler;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.opensaml.profile.context.navigate.ParentProfileRequestContextLookup;
+
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+import net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext;
+import net.shibboleth.oidc.profile.core.OIDCLogoutRequest;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+
+/** 
+ * Base class for message handlers that process and apply values of OpenID Connect logout requests.
+ * 
+ * <p>
+ * This abstract class provides common functionality for locating:
+ * </p>
+ * <ul>
+ *   <li>the {@link OIDCLogoutRequest} associated with the current
+ *       {@link MessageContext},</li>
+ *   <li>the {@link OIDCProviderMetadata} describing the peer OpenID Provider,</li>
+ *   <li>and the parameter value to be extracted and validated against
+ *       the expected Java type.</li>
+ * </ul>
+ * 
+ * @param <T> the logout request parameter value type
+ *
+ * TODO: move to commons
+ */
+public abstract class AbstractLogoutRequestParameterValueMessageHandler<T> extends AbstractMessageHandler {
+    
+    /** Lookup function for parent ProfileRequestContext. */
+    @Nonnull protected static final ParentProfileRequestContextLookup<MessageContext> PRC_LOOKUP
+        = new ParentProfileRequestContextLookup<>();
+    
+    /** Strategy used to locate the {@link OIDCLogoutRequest}.  */
+    @NonnullAfterInit private Function<MessageContext, OIDCLogoutRequest> logoutRequestLookupStrategy;
+    
+    /** Lookup strategy to locate the OpenID Provider metadata to use.*/
+    @NonnullAfterInit private Function<MessageContext, OIDCProviderMetadataContext> providerMetadataLookupStrategy;
+    
+    /** Lookup strategy for parameter value. */
+    @Nullable private Function<MessageContext,T> parameterValueLookupStrategy;
+    
+    /** The logout request parameter value type.*/
+    @Nonnull private final Class<T> type;
+    
+    /** The stashed {@link OIDCLogoutRequest}.*/
+    @NonnullBeforeExec private OIDCLogoutRequest logoutRequest;  
+    
+    /** The stashed OpenID Provider metadata .*/
+    @NonnullBeforeExec private OIDCProviderMetadata providerMetadata;
+    
+    
+    /**
+     * Constructor.
+     * 
+     * @param valueType type of value returned by handler
+     */
+    protected AbstractLogoutRequestParameterValueMessageHandler(@Nonnull final Class<T> valueType) {
+        type = Constraint.isNotNull(valueType, "Logout request parameter value type cannot be null");
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        if (logoutRequestLookupStrategy == null) {
+            throw new ComponentInitializationException("LogoutRequestLookupStrategy cannot be null");
+        }
+        if (providerMetadataLookupStrategy == null) {
+            throw new ComponentInitializationException("ProviderMetadataLookupStrategy cannot be null");
+        }
+    }
+    
+    /**
+     * Get the logout request.
+     * 
+     * @return the logout request
+     */
+    @NonnullBeforeExec protected OIDCLogoutRequest getLogoutRequest() {
+        return logoutRequest;
+    }
+    
+    /**
+     * Set the lookup strategy to locate the OpenID providers metadata.
+     * 
+     * @param strategy the strategy.
+     */
+    public void setProviderMetadataLookupStrategy(
+            @Nonnull final Function<MessageContext, OIDCProviderMetadataContext> strategy) {
+        checkSetterPreconditions();
+        
+        providerMetadataLookupStrategy = 
+                Constraint.isNotNull(strategy,"Provider metadata lookup strategy can not be null");
+    }
+    
+    /**
+     * Returns the OpenID Provider metadata. Should never be {@code null} after
+     * {@code doPreExecute} has been called.
+     * 
+     * @return The provider metadata context.
+     */
+    @NonnullBeforeExec protected OIDCProviderMetadata getProviderMetadata() {
+        return providerMetadata;
+    }
+    
+    /**
+     * Set the parameter value lookup strategy used to find the value to set onto the logout request.
+     * 
+     * @param strategy The parameter value lookup strategy to set.
+     */
+    public void setParameterValueLookupStrategy(@Nonnull final Function<MessageContext, T> strategy) {
+        checkSetterPreconditions();
+        parameterValueLookupStrategy = Constraint.isNotNull(strategy,
+                "ParameterValueLookupStrategy can not be null");
+    }
+    
+    /**
+     * Retrieves the parameter value or configuration options from the configured lookup strategy, 
+     * verifying at runtime that the result matches the type expected by the subclass.
+     *  
+     * @param context the message context to pass to the lookup function
+     * 
+     * @return the parameter value
+     * 
+     * @throws MessageHandlerException if the value is not the expected type
+     */
+    @Nullable protected T getParameterValue(final MessageContext context) 
+            throws MessageHandlerException {
+        final var localParameterValueLookupStrategy = parameterValueLookupStrategy;
+        if (localParameterValueLookupStrategy == null) {
+            return null;
+        }
+        final Object value = localParameterValueLookupStrategy.apply(context);
+        if (value == null) {
+            return null;
+        }
+        if (type.isInstance(value)) {
+            return type.cast(value);
+        }
+        throw new MessageHandlerException("Logout request parameter value lookup returned the "
+                + "wrong value type");
+    }
+    
+    /**
+     * Set the strategy used to locate the {@link OIDCLogoutRequest} to use. 
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setLogoutRequestLookupStrategy(
+            @Nonnull final Function<MessageContext, OIDCLogoutRequest> strategy) {
+        checkSetterPreconditions();
+        
+        logoutRequestLookupStrategy =
+                Constraint.isNotNull(strategy, "LogoutContext lookup strategy cannot be null");
+    }
+    
+    @Override
+    protected boolean doPreInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+
+        logoutRequest = logoutRequestLookupStrategy.apply(messageContext);
+        if (logoutRequest == null) {
+            throw new MessageHandlerException("OIDC logout request is null");
+        }
+        final OIDCProviderMetadataContext providerMetadataContext = 
+                providerMetadataLookupStrategy.apply(messageContext);
+        if (providerMetadataContext == null) {
+            throw new MessageHandlerException("No provider metadata context found for peer");
+        }
+        providerMetadata = providerMetadataContext.getProviderInformation();
+        if (providerMetadata == null) {
+            throw new MessageHandlerException("No provider metadata found for peer");
+        }
+        
+        return super.doPreInvoke(messageContext);
+    }
+
+}
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AddClientIDHandler.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AddClientIDHandler.java
new file mode 100644
index 0000000..87d21e9
--- /dev/null
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AddClientIDHandler.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed 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.sp.oidc.profile.impl;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import com.nimbusds.oauth2.sdk.id.ClientID;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A message handler that populates the client_id parameter into the logout request.
+ */
+public class AddClientIDHandler extends AbstractLogoutRequestParameterValueMessageHandler<String> {
+
+    /** Logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddClientIDHandler.class);
+    
+    /** Constructor.*/
+    public AddClientIDHandler() {
+        super(String.class);
+    }    
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+        
+        final String clientId = getParameterValue(messageContext);
+        if (clientId != null) {
+            getLogoutRequest().setClientID(new ClientID(clientId));
+            log.trace("{}: Set client_id to '{}'", getLogPrefix(), clientId);
+        }       
+    }
+}
\ No newline at end of file
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AddIDTokenHintHandler.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AddIDTokenHintHandler.java
new file mode 100644
index 0000000..44be868
--- /dev/null
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AddIDTokenHintHandler.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed 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.sp.oidc.profile.impl;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import com.nimbusds.jwt.JWT;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A message handler that populates the login_hint parameter into the logout request.
+ */
+public class AddIDTokenHintHandler extends AbstractLogoutRequestParameterValueMessageHandler<JWT> {
+
+    /** Logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddIDTokenHintHandler.class);
+    
+    /** Constructor.*/
+    public AddIDTokenHintHandler() {
+        super(JWT.class);
+    }    
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+        
+        final JWT idTokenHint = getParameterValue(messageContext);
+        getLogoutRequest().setIdTokenHint(idTokenHint);
+        log.trace("{}: Set id_token_hint to '{}'", getLogPrefix(), idTokenHint);
+        
+    }
+    
+
+
+}
\ No newline at end of file
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AddLoginHintHandler.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AddLoginHintHandler.java
new file mode 100644
index 0000000..49543e3
--- /dev/null
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AddLoginHintHandler.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed 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.sp.oidc.profile.impl;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A message handler that populates the login_hint parameter into the logout request.
+ */
+public class AddLoginHintHandler extends AbstractLogoutRequestParameterValueMessageHandler<String> {
+
+    /** Logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddLoginHintHandler.class);
+    
+    /** Constructor.*/
+    public AddLoginHintHandler() {
+        super(String.class);
+    }    
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+        
+        final String loginHint = getParameterValue(messageContext);
+        getLogoutRequest().setLoginHint(loginHint);
+        log.trace("{}: Set login_hint to '{}'", getLogPrefix(), loginHint);
+        
+    }
+    
+
+
+}
\ No newline at end of file
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AddPostLogoutRedirectURIHandler.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AddPostLogoutRedirectURIHandler.java
new file mode 100644
index 0000000..b560b0a
--- /dev/null
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AddPostLogoutRedirectURIHandler.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed 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.sp.oidc.profile.impl;
+
+import java.net.URI;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A message handler that populates the client_id parameter into the logout request.
+ */
+public class AddPostLogoutRedirectURIHandler extends AbstractLogoutRequestParameterValueMessageHandler<URI> {
+
+    /** Logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddPostLogoutRedirectURIHandler.class);
+    
+    /** Constructor.*/
+    public AddPostLogoutRedirectURIHandler() {
+        super(URI.class);
+    }    
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+        
+        final URI postRedirectUri = getParameterValue(messageContext);
+        getLogoutRequest().setPostLogoutRedirectURI(postRedirectUri);
+        log.trace("{}: Set post_logout_redirect_uri to '{}'", getLogPrefix(), postRedirectUri);
+        
+    }
+    
+
+
+}
\ No newline at end of file
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AddUILocalesHandler.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AddUILocalesHandler.java
new file mode 100644
index 0000000..6691be0
--- /dev/null
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AddUILocalesHandler.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed 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.sp.oidc.profile.impl;
+
+import java.util.List;
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import com.nimbusds.langtag.LangTag;
+import com.nimbusds.langtag.LangTagException;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A message handler that populates the client_id parameter into the logout request.
+ */
+public class AddUILocalesHandler extends AbstractLogoutRequestParameterValueMessageHandler<List<String>> {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddUILocalesHandler.class);
+
+    /** Constructor.*/
+    public AddUILocalesHandler() {
+        super((Class)List.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+
+        final List<String> locales = getParameterValue(messageContext);
+        if (locales != null && !locales.isEmpty()) {
+            if (log.isTraceEnabled()) {
+                log.trace("{} Setting 'ui_locales={}'", getLogPrefix(), locales);
+            }
+            final List<LangTag> uiLocals = locales.stream().map(tag -> {
+                try {
+                    return LangTag.parse(tag);
+                } catch (final LangTagException e) {
+                    log.warn("Can not parse language tag '{}'", tag);
+                }
+                return null;
+            }).filter(Objects::nonNull).toList();
+            
+            getLogoutRequest().setUiLocales(uiLocals);            
+        }
+    }
+    
+
+
+}
\ No newline at end of file
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeLogoutRequest.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeLogoutRequest.java
new file mode 100644
index 0000000..df9d0d0
--- /dev/null
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeLogoutRequest.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed 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.sp.oidc.profile.impl;
+
+import java.net.URI;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.idp.profile.IdPEventIds;
+import net.shibboleth.oidc.profile.core.OIDCLogoutRequest;
+import net.shibboleth.oidc.profile.core.OidcEventIds;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * An action that creates an {@link OIDCLogoutRequest} shell to populate in future steps,
+ * and sets it to the outbound message context.
+ * 
+ * <p>When creating the shell, the logout endpoint of the OpenID Provider is resolved, typically from metadata..
+ * If no metadata is found, or the logout endpoint is not set, an error event will be emitted. If there is nowhere
+ * to send the request, there is no point in trying to build it.</p>
+ * 
+ * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
+ * @event {@link IdPEventIds#INVALID_PROFILE_CONFIG}
+ * @event {@link OidcEventIds#MISSING_END_SESSION_ENDPOINT}
+ * @post Add an {@link OIDCLogoutRequest} as the message of the outbound context.
+ */
+public class InitializeLogoutRequest extends AbstractProfileAction {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(InitializeLogoutRequest.class); 
+
+    /** Strategy function for access to {@link RelyingPartyContext}. */
+    @Nonnull private Function<ProfileRequestContext,RelyingPartyContext> relyingPartyContextLookupStrategy;
+    
+    /** Strategy to find the logout endpoint. Typically taken from the OP's metadata, but can come via other means.*/
+    @NonnullAfterInit private Function<ProfileRequestContext, URI> logoutEndpointLookupStrategy;
+    
+    /** The stashed outbound message context. */
+    @NonnullBeforeExec private MessageContext outMessageContext;
+    
+    /** Optional RP name for logging. */
+    @Nullable private String relyingPartyId;
+    
+    /** Constructor.*/
+    public InitializeLogoutRequest() {        
+        relyingPartyContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class);
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        if (logoutEndpointLookupStrategy == null) {
+            throw new ComponentInitializationException("LogoutEndpointLookupStrategy cannot be null");
+        }
+    }
+    
+    /**
+     * Set the strategy used to locate the logout endpoint URI.
+     * 
+     * @param strategy the strategy.
+     */
+    public void setLogoutEndpointLookupStrategy(@Nonnull final Function<ProfileRequestContext, URI> strategy) {
+        checkSetterPreconditions();
+        
+        logoutEndpointLookupStrategy = 
+                Constraint.isNotNull(strategy, "LogoutEndpointLookupStrategy can not be null");
+    }
+    
+    /**
+     * Set lookup strategy for {@link RelyingPartyContext}.
+     * 
+     * @param strategy  lookup strategy
+     */
+    public void setRelyingPartyContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext,RelyingPartyContext> strategy) {
+        checkSetterPreconditions();
+        relyingPartyContextLookupStrategy = Constraint.isNotNull(strategy,
+                "RelyingPartyContext lookup strategy cannot be null");
+    }
+    
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }
+        
+        outMessageContext = profileRequestContext.getOutboundMessageContext();
+        if (outMessageContext == null) {
+            log.error("{} Outbound message context not found", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
+            return false;
+        }       
+        
+
+        final RelyingPartyContext rpContext = relyingPartyContextLookupStrategy.apply(profileRequestContext);
+        if (rpContext != null) {
+            relyingPartyId = rpContext.getRelyingPartyId();
+        }
+        
+        return true;
+    }
+    
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        super.doExecute(profileRequestContext);
+        
+        final URI logoutEndpoint = logoutEndpointLookupStrategy.apply(profileRequestContext);
+        if (logoutEndpoint == null) {
+            log.warn("{} Unable to resolve logout endpoint for outbound messag '{}'",
+                    getLogPrefix(), relyingPartyId);
+            ActionSupport.buildEvent(profileRequestContext, OidcEventIds.MISSING_END_SESSION_ENDPOINT);
+            return;
+        }
+        final OIDCLogoutRequest logoutRequest = new OIDCLogoutRequest(logoutEndpoint);
+        
+        outMessageContext.setMessage(logoutRequest);
+        log.debug("{} Adding shell OIDC logout request to outbound context for client '{}'", getLogPrefix(), 
+                relyingPartyId);
+       
+    }
+
+}
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeOutboundMessageContext.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeOutboundMessageContext.java
index 57bfb65..02cec41 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeOutboundMessageContext.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeOutboundMessageContext.java
@@ -39,7 +39,7 @@ import net.shibboleth.shared.primitive.LoggerFactory;
 
 /** 
  * Initialize an outbound message context with an OIDC peer entity context ready for an authorization/authentication 
- * request to be built.
+ * request to be built. Constructs the outbound peer entity context based on that found RelyingPartyContext. 
  * 
  * TODO self context
  * 
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeRelyingPartyContextFromOIDCPeer.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeRelyingPartyContextFromOIDCPeer.java
index 9b035d1..43108f7 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeRelyingPartyContextFromOIDCPeer.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeRelyingPartyContextFromOIDCPeer.java
@@ -156,6 +156,8 @@ public class InitializeRelyingPartyContextFromOIDCPeer extends AbstractProfileAc
         
         log.debug("{} Attaching RelyingPartyContext based on OIDC peer '{}'", getLogPrefix(),
                 peerEntityCtx.getIdentifier());
+        // TODO, this is redundant if we've already set the relying party ID in the context by this point e.g. logout
+        // TODO We could set the relying Party ID strategy here, which by default pulls it out of the peerContext see InitializeRelyingPartyContextFromSAMLPeer
         rpContext.setRelyingPartyId(peerEntityCtx.getIdentifier());
         rpContext.setRelyingPartyIdContextTree(peerEntityCtx);
         final OIDCProviderMetadataContext oidcContext = 
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ProcessLogoutInitiatorRequest.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ProcessLogoutInitiatorRequest.java
index 8928ea5..471cd68 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ProcessLogoutInitiatorRequest.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ProcessLogoutInitiatorRequest.java
@@ -159,6 +159,8 @@ public class ProcessLogoutInitiatorRequest extends AbstractApplicationAction {
                 final JWTClaimsSet claims = localIdToken.getJWTClaimsSet();
                 if (claims != null) {
                     relyingPartyId = claims.getIssuer();
+                    log.trace("{} Set RelyingPartyID to '{}' based on issuer '{}' in recovered ID Token", 
+                            getLogPrefix(), relyingPartyId, claims.getIssuer());
                 }
                 if (relyingPartyId == null) {
                     ActionSupport.buildEvent(profileRequestContext, EventIds.UNABLE_TO_DECODE);

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


More information about the commits mailing list