[java-plugin-shibd-oidc] 01/05: JSHIBDOIDC-22 - Store off iss, sid in opaque session data to support logout

Codeberg noreply at shibboleth.net
Wed Jul 15 08:36:09 UTC 2026


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

codeberg pushed a commit to branch main
in repository java-plugin-shibd-oidc.

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

commit ebdbb28069d49a9ae0d7c043d15933938fd8e92c
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Wed Jun 17 17:37:54 2026 +0100

    JSHIBDOIDC-22 - Store off iss, sid in opaque session data to support
    logout
    
     - Add missing constants
     - Add basic logout request message handlers to build the logout request
---
 .../sp/oidc/context/OIDCLogoutContext.java         |  51 +++++
 ...outRequestFromMessageContextLookupFunction.java |  37 ++++
 ...oviderMetadataLogoutEndpointLookupStrategy.java |  70 +++++++
 .../sp/oidc/profile/LogoutRequestStateData.java    |  31 ++++
 .../shibboleth/sp/oidc/profile/OIDCConstants.java  |   8 +-
 .../config/navigate/ClientIDLookupStrategy.java    |  61 ++++++
 .../navigate/IDTokenHintLookupStrategy.java        |  72 +++++++
 .../shibboleth/sp/oidc/testing/TestConstants.java  |   7 +-
 .../net/shibboleth/sp/oidc/testing/TestHelper.java |  40 ++++
 .../META-INF/net.shibboleth.idp/postconfig.xml     |  34 ++++
 .../idp/flows/sp/consumer/oidc/oidc-beans.xml      |  11 --
 .../idp/flows/sp/initiator/oidc/oidc-beans.xml     |  41 +---
 .../idp/flows/sp/initiator/oidc/oidc-flow.xml      |   5 +-
 .../flows/sp/logout/consumer/oidc/oidc-beans.xml   |  18 ++
 .../flows/sp/logout/consumer/oidc/oidc-flow.xml    | 101 ++++++++++
 .../consumer/oidc/redirect/redirect-beans.xml      |  23 +++
 .../consumer/oidc/redirect/redirect-flow.xml       |   9 +
 .../flows/sp/logout/initiator/oidc/oidc-beans.xml  | 162 ++++++++++++++++
 .../flows/sp/logout/initiator/oidc/oidc-flow.xml   |  34 ++++
 .../shibboleth/idp/flows/sp/oidc-common-beans.xml  |  29 ++-
 .../net/shibboleth/sp/service/agent/postconfig.xml |  21 ++-
 .../resources/templates/oidc-request-form-post.vm  |  65 +++++++
 .../sp/oidc/flows/OIDCLogoutInitiatorFlowTest.java | 175 +++++++++++++++++
 .../oidc/flows/OIDCSessionInitiatorFlowTest.java   |  20 +-
 .../sp/oidc/flows/OIDCTokenConsumerFlowTest.java   |  64 +++----
 ...DCTokenConsumerFlowUsingStorageServiceTest.java |   6 +-
 ...DCEnvironmentApplicationContextInitializer.java |   1 +
 .../src/test/resources/logback-flow-test.xml       |  33 ++++
 .../src/test/resources/logback-test.xml            |   2 +-
 .../resources/metadata/openid-configuration.json   |   4 +-
 .../idp/module/conf/sp/oidc-test-agents.xml        |   2 +-
 ...tLogoutRequestParameterValueMessageHandler.java | 204 ++++++++++++++++++++
 ...ata.java => AddClientAddressToStateAction.java} | 102 +++-------
 .../sp/oidc/profile/impl/AddClientIDHandler.java   |  52 ++++++
 .../oidc/profile/impl/AddIDTokenHintHandler.java   |  91 +++++++++
 .../sp/oidc/profile/impl/AddLogoutHintHandler.java |  54 ++++++
 .../impl/AddPostLogoutRedirectURIHandler.java      |  55 ++++++
 .../sp/oidc/profile/impl/AddUILocalesHandler.java  |  68 +++++++
 ...ticationStateData.java => CreateStateData.java} |  33 +---
 .../oidc/profile/impl/InitializeLogoutRequest.java | 151 +++++++++++++++
 .../impl/InitializeOAuth2ClientContext.java        |  36 +---
 .../impl/InitializeOutboundMessageContext.java     |   4 +-
 .../InitializeRelyingPartyContextFromOIDCPeer.java |   2 +
 .../impl/LogoutRequestStateForStorageStrategy.java |  40 ++++
 .../sp/oidc/profile/impl/PrepareAgentResponse.java |  35 +++-
 .../sp/oidc/profile/impl/PreserveOAuthState.java   |  68 +++++++
 .../impl/ProcessLogoutInitiatorRequest.java        | 203 ++++++++++++++++++++
 .../profile/impl/BaseOIDCAuthenticationTest.java   |   3 +-
 .../impl/CreateAuthenticationStateDataTest.java    |  20 +-
 .../impl/ProcessLogoutInitiatorRequestTest.java    | 206 +++++++++++++++++++++
 .../sp/oidc/profile/impl/SealerKeyStore.jks        | Bin 0 -> 984 bytes
 .../sp/oidc/profile/impl/SealerKeyStore.kver       |   1 +
 52 files changed, 2395 insertions(+), 270 deletions(-)

diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/OIDCLogoutContext.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/OIDCLogoutContext.java
new file mode 100644
index 0000000..e56119f
--- /dev/null
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/OIDCLogoutContext.java
@@ -0,0 +1,51 @@
+/*
+ * 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.context;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+
+import com.nimbusds.jwt.JWT;
+
+/**
+ * Context to handle state for OIDC logout flows. Typically information that has been passed up in session data
+ * by the Agent.
+ */
+public class OIDCLogoutContext extends BaseContext {
+    
+    /** The id_token recovered from the session.*/
+    @Nullable private JWT idToken;
+
+    /**
+     * Set the id_token recovered from the session.
+     * 
+     * @param token the id_token
+     */
+    public OIDCLogoutContext setIdToken(@Nullable final JWT token) {
+        idToken = token;   
+        return this;
+    }
+    
+    /**
+     * Get the id_token recovered from the session.
+     * 
+     * @return the id_token.
+     */
+    @Nullable public JWT getIdToken() {
+        return idToken;
+    }
+
+}
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/main/java/net/shibboleth/sp/oidc/profile/LogoutRequestStateData.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/LogoutRequestStateData.java
new file mode 100644
index 0000000..b5eefd9
--- /dev/null
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/LogoutRequestStateData.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+import javax.annotation.concurrent.NotThreadSafe;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+import net.shibboleth.sp.state.StateData;
+
+/**
+ * A DTO class that carries OIDC specific logout request information that needs to be recovered to validate the
+ * logout response. This class is designed for JSON serialization and deserialization for storage.
+ */
+ at JsonInclude(JsonInclude.Include.NON_NULL)
+ at NotThreadSafe
+public class LogoutRequestStateData extends StateData {
+
+}
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/OIDCConstants.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/OIDCConstants.java
index e4783d4..7ada8fd 100644
--- a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/OIDCConstants.java
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/OIDCConstants.java
@@ -27,13 +27,7 @@ public final class OIDCConstants {
      * Name of the state field to add to the state JSON object. The state field represents the current 'state'
      * value of the DDF.
      */
-    @Nonnull @NotEmpty public static final String STATE_FIELD = "state";
-    
-    /** Name of the authentication state token field that references the authentication state in the token manager.*/
-    @Nonnull @NotEmpty public static final String AUTHN_STATE_FIELD = "authnState";
-    
-    /** Name of the Request Forgery Protection (nonce) field.*/
-    @Nonnull @NotEmpty public static final String RFP_FIELD = "rfp";    
+    @Nonnull @NotEmpty public static final String STATE_FIELD = "state"; 
     
     /** Private constructor. */
     private OIDCConstants() {
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/config/navigate/ClientIDLookupStrategy.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/config/navigate/ClientIDLookupStrategy.java
new file mode 100644
index 0000000..fbd072d
--- /dev/null
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/config/navigate/ClientIDLookupStrategy.java
@@ -0,0 +1,61 @@
+/*
+ * 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.config.navigate;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.ParentProfileRequestContextLookup;
+
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * A lookup strategy that locates the ClientID, used in a logout request.
+ */
+public class ClientIDLookupStrategy implements Function<MessageContext, String> {
+    
+    /** Lookup function for parent {@link ProfileRequestContext}. */
+    @Nonnull private static final ParentProfileRequestContextLookup<MessageContext> PRC_LOOKUP
+        = new ParentProfileRequestContextLookup<>();
+    
+    /** Strategy used to obtain the request issuer value. */
+    @Nonnull private final Function<ProfileRequestContext,String> issuerLookupStrategy;
+
+    /**
+     * Constructor.
+     *
+     * @param strategy strategy to find the logout context
+     */
+    public ClientIDLookupStrategy(@ParameterName(name = "issuerLookupStrategy")
+            @Nonnull final Function<ProfileRequestContext, String> strategy) {
+        issuerLookupStrategy = Constraint.isNotNull(strategy,
+                "IssuerLookupStrategy can not be null");
+    }
+    
+
+    @Override
+    @Nullable public String apply(@Nullable final MessageContext prc) {
+        if (prc == null) {
+            return null;
+        }
+        return issuerLookupStrategy.apply(PRC_LOOKUP.apply(prc));                
+    }
+
+}
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/context/navigate/IDTokenHintLookupStrategy.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/context/navigate/IDTokenHintLookupStrategy.java
new file mode 100644
index 0000000..5bf0c56
--- /dev/null
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/context/navigate/IDTokenHintLookupStrategy.java
@@ -0,0 +1,72 @@
+/*
+ * 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.context.navigate;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.ParentProfileRequestContextLookup;
+
+import com.nimbusds.jwt.JWT;
+
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.sp.oidc.context.OIDCLogoutContext;
+
+/**
+ * A lookup strategy that locates the ID Token, used as a hint in a logout request, from the {@link OIDCLogoutContext}.
+ */
+public class IDTokenHintLookupStrategy implements Function<MessageContext, JWT> {
+    
+    /** Lookup function for parent {@link ProfileRequestContext}. */
+    @Nonnull private static final ParentProfileRequestContextLookup<MessageContext> PRC_LOOKUP
+        = new ParentProfileRequestContextLookup<>();
+    
+    /** 
+     * Strategy used to locate the {@link OIDCLogoutContext}.
+     */
+    @Nonnull 
+    private final Function<ProfileRequestContext, OIDCLogoutContext> logoutContextLookupStrategy;
+
+    /**
+     * Constructor.
+     *
+     * @param strategy strategy to find the logout context
+     */
+    public IDTokenHintLookupStrategy(@ParameterName(name = "oidcLogoutContextLookupStrategy")
+            @Nonnull final Function<ProfileRequestContext, OIDCLogoutContext> strategy) {
+        logoutContextLookupStrategy = Constraint.isNotNull(strategy,
+                "StateDataContextLookupStrategy can not be null");
+    }
+    
+
+    @Override
+    @Nullable public JWT apply(@Nullable final MessageContext prc) {
+        if (prc == null) {
+            return null;
+        }
+        final OIDCLogoutContext logoutContext = logoutContextLookupStrategy.apply(PRC_LOOKUP.apply(prc));        
+        if (logoutContext != null && logoutContext.getIdToken() != null) {
+            return logoutContext.getIdToken();            
+        }
+        return null;
+        
+    }
+
+}
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 3c88442..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 used in cookie names. */
-    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-api/src/test/java/net/shibboleth/sp/oidc/testing/TestHelper.java b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/testing/TestHelper.java
index 123e635..7a91ee2 100644
--- a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/testing/TestHelper.java
+++ b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/testing/TestHelper.java
@@ -77,6 +77,7 @@ import net.shibboleth.oidc.security.credential.JWKCredential;
 import net.shibboleth.shared.codec.Base64Support;
 import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.sp.ddf.DDF;
+import net.shibboleth.sp.messaging.RemotedHttpServletRequest;
 import net.shibboleth.sp.oidc.profile.AuthenticationRequestStateData;
 import net.shibboleth.sp.profile.ConsumerConstants;
 
@@ -211,6 +212,28 @@ public final class TestHelper {
         return type.cast(defaultValue);
     }
     
+    /**
+     * Simplified method to create a signed JWT (Id Token) using a MAC.
+     * 
+     * @param issuer the issuer
+     * @param subject the subject
+     * @param macCredential the shared secret credential used for the MAC
+     * 
+     * @return a signed JWT
+     * 
+     * @throws Exception on error
+     */
+    public static JWT createSignedIDToken(@Nullable final String issuer, @Nullable final String subject, 
+            @Nullable final Credential macCredential) 
+                throws Exception {
+        final JWTClaimsSet claims = new JWTClaimsSet.Builder()
+                .issuer(issuer)
+                .subject(subject)
+                .expirationTime(new Date(System.currentTimeMillis() + 60000))
+                .build();
+        return createJWT(claims, JWSAlgorithm.HS256, null, null, macCredential, null);        
+    }
+    
     /**
      * Create a JWT from the given payload. The JWT can either be plain, or signed and encrypted. If encrypted, it must
      * be signed. 
@@ -547,5 +570,22 @@ public final class TestHelper {
             ResponseMode.QUERY);
     
     }
+    
+    
+    /**
+     * Build a basic DDF with session data that contains an id_token.
+     * 
+     * @param idToken the token to add to the DDF
+     * @param tokenParam the name of the token param
+     * @return the constructed DDF
+     */
+    @Nonnull public static DDF buildLogoutInitiatorDDFInput(@Nonnull final String idToken, 
+            @Nonnull final String tokenParam) {
+        final DDF root = new DDF(null).structure();
+        root.addmember(RemotedHttpServletRequest.STRUCTURE_NAME).structure();   
+        final DDF session = root.addmember(ConsumerConstants.SESSION_OPAQUE);
+        session.addmember(tokenParam).string(idToken);
+        return root;
+    }
 
 }
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index 2b2cdb3..4256b8b 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -88,6 +88,40 @@
             </set>
         </property>
     </bean>
+    
+     <!-- Logout consumer descriptors for OIDC -->
+
+<!--     <bean id="shibboleth.sp.TokenParameterPrefixCondition"
+        class="net.shibboleth.sp.profile.context.logic.InputStartsWithPredicate"
+        c:_0="token" c:_1="SAML:" /> -->
+    
+    <bean p:id="sp/logout/consumer/oidc/redirect" class="net.shibboleth.sp.profile.BasicLogoutConsumerFlowDescriptor">
+       <!--  <property name="activationCondition">
+            <bean parent="shibboleth.Conditions.OR">
+                <constructor-arg>
+                    <list>
+                        <ref bean="shibboletg.sp.TokenParameterPrefixCondition" />
+                        <bean class="net.shibboleth.sp.profile.context.logic.HttpServletRequestPredicate"
+                                p:httpServletRequestSupplier-ref="shibboleth.RemotedHttpServletRequestSupplier">
+                            <property name="httpServletRequestValidator">
+                                <bean class="net.shibboleth.shared.servlet.impl.ChainingHttpServletRequestValidator">
+                                    <property name="validators">
+                                        <list>
+                                            <bean class="net.shibboleth.shared.servlet.impl.BasicHttpServletRequestMethodValidator"
+                                                p:allowedMethods="GET" />
+                                            <ref bean="shibboleth.HttpServletRequestValidator.BasicParams.SAML2.Redirect.RequestOrResponse" />
+                                        </list>
+                                    </property>
+                                </bean>
+                            </property>
+                        </bean>
+                    </list>
+                </constructor-arg>
+            </bean>
+        </property> -->
+    </bean>
+    
+    <!-- Other global beans -->
 
     <bean id="shibboleth.ProviderMetadataResolverService"
         class="net.shibboleth.shared.spring.service.ReloadableSpringService"
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..81409fe 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
@@ -41,23 +41,12 @@
     
     <bean id="OAuthStateLookup" class="net.shibboleth.sp.oidc.messaging.context.navigate.StateFromResponseLookupFunction"/>
 
-
     <bean id="ValidateAuthenticationResponseResult" scope="prototype"
         class="net.shibboleth.sp.oidc.profile.impl.ValidateAuthenticationResponseResult" />
     
     <bean id="InitializePeerEntityContext" class="net.shibboleth.sp.oidc.profile.impl.InitializePeerEntityContext"
         p:stateDataContextLookupStrategy-ref="shibboleth.ChildLookup.StateDataContext"/>
         
-     <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" />
-    
     <!-- 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..1a2f90e 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
@@ -28,27 +28,12 @@
         class="net.shibboleth.sp.oidc.profile.impl.PrepareOIDCInboundMessageContext" scope="prototype"
         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" />
-
+    
+    <!-- Add information pertaining to the OAuth2.0 Client (much like a self-context) -->
     <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" />
 
@@ -267,7 +252,7 @@
         Create the Authentication State Data from the authentication request that is preserved for later recovery
         in the consumer flow.
     -->
-    <bean id="CreateAuthenticationStateData" class="net.shibboleth.sp.oidc.profile.impl.CreateAuthenticationStateData"
+    <bean id="CreateAuthenticationStateData" class="net.shibboleth.sp.oidc.profile.impl.CreateStateData"
         scope="prototype"
         p:stateDataContextCreationStrategy-ref="shibboleth.ChildLookupOrCreate.StateDataContext"
         p:stateDataLookupStrategy-ref="AuthenticationRequestStateForStorageStrategy"/>
@@ -279,14 +264,17 @@
     <bean id="AuthenticationRequestStateForStorageStrategy" scope="prototype"
         class="net.shibboleth.sp.oidc.profile.impl.AuthenticationRequestStateForStorageStrategy"
         p:authenticationAuthorityLookupStrategy-ref="shibboleth.RelyingPartyIdLookup.Simple"
-        p:authenticationRequestLookupStrategy-ref="shibboleth.AuthenticationRequestLookup.FromOutbound" /> 
+        p:authenticationRequestLookupStrategy-ref="shibboleth.AuthenticationRequestLookup.FromOutbound" />
+        
+    <bean id="AddClientAddressToState" scope="prototype" class="net.shibboleth.sp.oidc.profile.impl.AddClientAddressToStateAction"
+        p:stateDataContextLookupStrategy-ref="shibboleth.ChildLookup.StateDataContext"/> 
     
     <!-- 
         Store off the authentication state data in the state data context so it can be recovered later when processing 
         the response. By default, failure to store off the state will be fatal.
      -->
-    <bean id="PreserveState"
-        class="net.shibboleth.sp.profile.PreserveStateDataAction" scope="prototype"
+    <bean id="PreserveOAuthState"
+        class="net.shibboleth.sp.oidc.profile.impl.PreserveOAuthState" scope="prototype"
         p:stateDataContextLookupStrategy-ref="shibboleth.ChildLookup.StateDataContext"
         p:errorFatal="%{sp.stateToken.errorsFatal:true}" />
 
@@ -301,15 +289,6 @@
         scope="prototype">
         <property name="handlers">
             <list>
-                <!-- Pull out the state here, rather than during processToken of the PreserveState step -->
-                <bean id="AddState" class="net.shibboleth.oidc.profile.messaging.handler.impl.AddStateHandler"
-                    scope="prototype">
-                    <property name="parameterValueLookupStrategy">
-                        <bean class="net.shibboleth.sp.oidc.messaging.context.navigate.StateTokenLookupStrategy" scope="prototype"
-                        c:stateDataContextLookupStrategy-ref="shibboleth.ChildLookup.StateDataContext"/>
-                    </property>
-                </bean>
-
                 <bean id="BuildPlainRequestObjectJWT"
                     class="net.shibboleth.oidc.profile.messaging.handler.impl.BuildPlainRequestObjectJWT"
                     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..0c9f8e0 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" />
@@ -48,7 +48,8 @@
     <action-state id="BuildOutboundMessage">
         <!-- Must map authentication state to a state token first, so we can add the token to the state in HandleOutboundMessage -->
         <evaluate expression="CreateAuthenticationStateData"/> 
-        <evaluate expression="PreserveState"/>
+        <evaluate expression="AddClientAddressToState"/> 
+        <evaluate expression="PreserveOAuthState"/>
         <evaluate expression="PreservePostData" />
         <evaluate expression="HandleOutboundMessage" />
         <evaluate expression="EncodeMessage" />
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
new file mode 100644
index 0000000..ec428c1
--- /dev/null
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/oidc/oidc-beans.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:c="http://www.springframework.org/schema/c"
+    xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
+    xmlns:util="http://www.springframework.org/schema/util" 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">
+
+    <util:constant id="shibboleth.sp.oidc.ProfileId"
+        static-field="net.shibboleth.saml.saml2.profile.config.SingleLogoutProfileConfiguration.PROFILE_ID" />
+
+    <util:constant id="shibboleth.EndpointType"
+        static-field="org.opensaml.saml.saml2.metadata.SingleLogoutService.DEFAULT_ELEMENT_NAME" />
+
+  
+    
+</beans>
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/oidc/oidc-flow.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/oidc/oidc-flow.xml
new file mode 100644
index 0000000..24dfc46
--- /dev/null
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/oidc/oidc-flow.xml
@@ -0,0 +1,101 @@
+<flow xmlns="http://www.springframework.org/schema/webflow" 
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow.xsd"
+    abstract="true" parent="sp/logout/consumer">
+
+    <decision-state id="CheckForToken">
+        <if test="opensamlProfileRequestContext.ensureSubcontext(T(net.shibboleth.sp.context.AgentRequestContext)).getInput().getmember('http').isstruct()"
+            then="DoMessage"
+            else="IssueResponse" />
+    </decision-state>
+
+    <!-- This branch consumes a SAML message. -->
+    <action-state id="DoMessage">
+        <evaluate expression="ValidateHttpServletRequest" />
+        <evaluate expression="DecodeMessage" />
+<!--        <evaluate expression="PostDecodePopulateAuditContext" />-->
+
+        <!-- Only runs for LogoutResponse messages but early enough to head off errors. -->
+        <evaluate expression="CreateServletResponseForLogoutResponse" />
+        <evaluate expression="RecoverStateDataForLogoutResponse" />
+        
+        <evaluate expression="CheckMessageVersion" />
+        
+        <evaluate expression="SAMLProtocolAndRole" />
+        <evaluate expression="CheckDestinationAndIssuerHandler" />
+        <evaluate expression="SAMLMetadataLookup" />
+
+        <evaluate expression="InitializeRelyingPartyContextFromSAMLPeer" />
+        <evaluate expression="SelectRelyingPartyConfiguration" />
+<!--        <evaluate expression="PostLookupPopulateAuditContext" />-->
+        <evaluate expression="SelectProfileConfiguration" />
+
+        <evaluate expression="PopulateSignatureValidationParameters" />
+        <evaluate expression="PopulateClientTLSValidationParameters" />
+        
+        <evaluate expression="HandleMessageEarly" />
+        <evaluate expression="CheckSignature" />
+        <evaluate expression="HandleMessageLate" />
+<!--        <evaluate expression="PostResponsePopulateAuditContext" />-->        
+        <evaluate expression="'proceed'" />
+        
+        <transition on="proceed" to="PreProcessLogoutMessage" />
+    </action-state>
+    
+    <!-- Dispatch by message type. -->
+    <decision-state id="PreProcessLogoutMessage">
+        <if test="IsLogoutRequest.test(opensamlProfileRequestContext)" then="DoLogoutRequest" else="DoLogoutResponse"/>
+    </decision-state>
+    
+    <!-- For a LogoutRequest, we process against the "session.opaque" input and produce a "token" for the Agent. -->
+    <action-state id="DoLogoutRequest">
+        <evaluate expression="PopulateDecryptionParameters" />
+        <evaluate expression="DecryptNameIDs" />
+        <evaluate expression="ProcessLogoutRequest" />
+        <evaluate expression="'proceed'" />
+        
+        <transition on="proceed" to="proceed" />
+    </action-state>
+
+    <!-- For a LogoutResponse, we just extract some info for the Agent, simplest case by far. -->
+    <action-state id="DoLogoutResponse">
+        <evaluate expression="ProcessLogoutResponse" />
+        <evaluate expression="'proceed'" />
+        
+        <transition on="proceed" to="proceed" />
+    </action-state>
+    
+    <!-- This branch uses the "token" from the Agent to generate a LogoutResponse outbound. -->
+    <action-state id="IssueResponse">
+        <evaluate expression="ProcessLogoutTokenRequest" />
+        <evaluate expression="PrepareInboundMessageContext" />
+        <evaluate expression="SAMLProtocolAndRole" />
+        <evaluate expression="SAMLMetadataLookup" />
+        <evaluate expression="InitializeRelyingPartyContextFromSAMLPeer" />
+        <evaluate expression="SelectRelyingPartyConfiguration" />
+        <evaluate expression="SelectProfileConfiguration" />
+        <evaluate expression="InitializeOutboundMessageContext" />
+        <evaluate expression="InitializeMessageChannelSecurityContext" />
+        <evaluate expression="PopulateBindingAndEndpointContexts" />
+        <evaluate expression="PopulateResponseSignatureSigningParameters" />
+        <evaluate expression="CreateServletResponse" />
+        <evaluate expression="AddLogoutResponse" />
+        <evaluate expression="HandleOutboundMessage" />
+        <evaluate expression="EncodeMessage" />
+        <evaluate expression="'proceed'" />
+        
+        <transition on="proceed" to="proceed" />
+    </action-state>
+    
+    <end-state id="EndpointResolutionFailed" />
+    
+    <global-transitions>
+        <transition on="EndpointResolutionFailed" to="EndpointResolutionFailed" />
+        <!-- Remap some internal action errors. -->
+        <transition on="InvalidCredentials" to="InvalidMessage" />
+    </global-transitions>
+    
+    <!-- The file really exists in this directory, but it's referenced from extending flow-directories -->
+    <bean-import resource="classpath:/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/saml2-beans.xml" />
+
+</flow>
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/oidc/redirect/redirect-beans.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/oidc/redirect/redirect-beans.xml
new file mode 100644
index 0000000..729013a
--- /dev/null
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/oidc/redirect/redirect-beans.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:c="http://www.springframework.org/schema/c"
+    xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
+    xmlns:util="http://www.springframework.org/schema/util" 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">
+
+    <alias alias="shibboleth.HttpServletRequestValidator" name="shibboleth.HttpServletRequestValidator.BasicParams.SAML2.Redirect.RequestOrResponse" />
+
+    <bean id="DecodeMessage" class="net.shibboleth.sp.profile.impl.DecodeMessage" scope="prototype"
+            p:messageType="org.opensaml.saml.saml2.core.LogoutMessage">
+        <constructor-arg>
+            <bean class="org.opensaml.saml.saml2.binding.decoding.impl.HTTPRedirectDeflateDecoder" scope="prototype"
+                p:checkDuringInit="false"
+                p:parserPool-ref="shibboleth.ParserPool"
+                p:httpServletRequestSupplier-ref="shibboleth.RemotedHttpServletRequestSupplier"
+                p:bindingDescriptor-ref="shibboleth.Binding.SAML2Redirect" />
+        </constructor-arg>
+    </bean>
+
+</beans>
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/oidc/redirect/redirect-flow.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/oidc/redirect/redirect-flow.xml
new file mode 100644
index 0000000..c049c3b
--- /dev/null
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/oidc/redirect/redirect-flow.xml
@@ -0,0 +1,9 @@
+<flow xmlns="http://www.springframework.org/schema/webflow" 
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow.xsd"
+    parent="sp/logout/consumer/oidc">
+
+    <!-- The file really exists in this directory, but it's referenced from extending flow-directories -->
+    <bean-import resource="classpath:/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/oidc/redirect/redirect-beans.xml" />
+
+</flow>
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
new file mode 100644
index 0000000..70120b9
--- /dev/null
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/initiator/oidc/oidc-beans.xml
@@ -0,0 +1,162 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:c="http://www.springframework.org/schema/c"
+    xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
+    xmlns:util="http://www.springframework.org/schema/util" 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">
+
+    <util:constant id="shibboleth.sp.oidc.ProfileId"
+        static-field="net.shibboleth.oidc.profile.config.OIDCLogoutProfileConfiguration.PROFILE_ID" />
+        
+    <bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{sp.oidc.service.logging.token-consumer:LogoutInitiator.OIDC}" />
+
+    <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>
+    
+    <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>
+    
+    <!-- TODO, these should be optional via RP profile config switched, but we do not have a LogoutProfile config
+    for the RP -->
+     <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.context.navigate.IDTokenHintLookupStrategy"
+                                    c:oidcLogoutContextLookupStrategy-ref="shibboleth.ChildLookup.LogoutContext"
+                                    scope="prototype" />
+                            </property>
+                        </bean>
+                        <!-- Should this be conditional on the ID Token Hint being present? -->
+                        <bean id="AddLogoutHint" scope="prototype" parent="LogoutRequestHandler"
+                            class="net.shibboleth.sp.oidc.profile.impl.AddLogoutHintHandler">
+                           <!--  <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.ClientIDLookupStrategy"
+                                    scope="prototype" 
+                                    c:issuerLookupStrategy-ref="shibboleth.ClientIdLookup.Simple"/>
+                            </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.UiLocalesLookupStrategy"
+                                    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>
+    
+    <!-- 
+        Create the Authentication State Data from the authentication request that is preserved for later recovery
+        in the consumer flow.
+    -->
+    <bean id="CreateLogoutStateData" class="net.shibboleth.sp.oidc.profile.impl.CreateStateData"
+        scope="prototype"
+        p:stateDataContextCreationStrategy-ref="shibboleth.ChildLookupOrCreate.StateDataContext"
+        p:stateDataLookupStrategy-ref="LogoutRequestStateForStorageStrategy"/>
+        
+     <!-- 
+        A strategy for creating Logout Stata Data from the logout request. 
+    -->
+    <bean id="LogoutRequestStateForStorageStrategy" scope="prototype"
+        class="net.shibboleth.sp.oidc.profile.impl.LogoutRequestStateForStorageStrategy" /> 
+    
+    
+     <!-- 
+        Store off the authentication state data in the state data context so it can be recovered later when processing 
+        the response. By default, failure to store off the state will be fatal.
+     -->
+    <bean id="PreserveOAuthState"
+        class="net.shibboleth.sp.oidc.profile.impl.PreserveOAuthState" scope="prototype"
+        p:stateDataContextLookupStrategy-ref="shibboleth.ChildLookup.StateDataContext"
+        p:errorFatal="%{sp.stateToken.errorsFatal:true}" />
+    
+    <bean id="HandleOutboundMessage" class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor"
+        scope="prototype" c:messageHandler-ref="PreEncodeMessageHandler" c:executionDirection="OUTBOUND">
+        <property name="errorEvent">
+            <util:constant static-field="org.opensaml.profile.action.EventIds.MESSAGE_PROC_ERROR" />
+        </property>
+    </bean>
+    
+     <bean id="PreEncodeMessageHandler" class="org.opensaml.messaging.handler.impl.BasicMessageHandlerChain"
+        scope="prototype">
+        <property name="handlers">
+            <list>
+            </list>
+        </property>
+    </bean>
+    
+     <bean id="EncodeMessage" class="net.shibboleth.sp.profile.impl.EncodeMessage" scope="prototype"
+        p:messageEncoderFactory-ref="messageEncoderFactory" />
+
+    <!-- Message Encoder factory is a prototype to allow reuse of the encoders -->
+    <bean id="messageEncoderFactory"
+        class="net.shibboleth.oidc.profile.impl.LogoutRequestMessageEncoderFactory" scope="prototype"
+        c:encoders-ref="RequestEncoders" />
+
+    <!-- List must itself be a prototype so new encoders are created per request -->
+    <util:list id="RequestEncoders" scope="prototype">
+        <ref bean="HTTPRedirectRequestEncoder" />
+        <ref bean="HTTPPostRequestEncoder" /> 
+    </util:list>
+
+    <bean id="HTTPRedirectRequestEncoder"
+        class="net.shibboleth.oidc.profile.encoding.impl.HTTPRedirectRequestEncoder" init-method=""
+        scope="prototype" p:httpServletResponseSupplier-ref="shibboleth.RemotedHttpServletResponseSupplier" />
+
+    <bean id="HTTPPostRequestEncoder"
+        class="net.shibboleth.oidc.profile.encoding.impl.HTTPPostRequestEncoder" init-method="" scope="prototype"
+        p:velocityEngine-ref="shibboleth.VelocityEngine"
+        p:httpServletResponseSupplier-ref="shibboleth.RemotedHttpServletResponseSupplier"
+        p:cSPDigester="#{%{idp.encoders.cspEnabled:true} ? getObject('shibboleth.CSPDigester') : null}"
+        p:cSPNonceGenerator="#{%{idp.encoders.cspEnabled:true} ? getObject('shibboleth.CSPNonce') : null}" /> 
+    
+</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
new file mode 100644
index 0000000..b7a92bd
--- /dev/null
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/initiator/oidc/oidc-flow.xml
@@ -0,0 +1,34 @@
+<flow xmlns="http://www.springframework.org/schema/webflow" 
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow.xsd"
+    parent="sp/logout/initiator">
+
+    <action-state id="OIDCLogoutInitiator">
+        <evaluate expression="ProcessLogoutInitiatorRequest" />
+        <evaluate expression="PrepareInboundMessageContext" />
+        <evaluate expression="ProviderMetadataLookup" />
+        
+        <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="InitializeLogoutRequest" />
+        
+        <evaluate expression="BuildLogoutRequest" />
+        <evaluate expression="CreateLogoutStateData"/> 
+        <evaluate expression="PreserveOAuthState" />
+        <evaluate expression="HandleOutboundMessage" />
+        <evaluate expression="EncodeMessage" /> 
+        <evaluate expression="'proceed'" />
+
+        <transition on="proceed" to="proceed" />
+        <!-- Remap any other events into a fall-through to the next flow. -->
+        <transition to="ReselectFlow" />
+    </action-state>
+    
+    <!-- The file really exists in this directory, but it's referenced from extending flow-directories -->
+    <bean-import resource="classpath:/META-INF/net/shibboleth/idp/flows/sp/logout/initiator/oidc/oidc-beans.xml" />
+
+</flow>
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..c609f4f 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,38 @@
         </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" />
+        
+     <!-- 
+        We do not add self-context to the outbound message here, we construct an OAuth2ClientContext to store that and
+        other client information.
+     -->
+    <bean id="InitializeOutboundMessageContext"
+        class="net.shibboleth.sp.oidc.profile.impl.InitializeOutboundMessageContext" scope="prototype" />
+
 
 </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 fcb58b8..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
@@ -22,11 +22,14 @@
         p:id="OIDC"
         p:order="%{sp.oidc.relativeOrder:1}"
         p:sessionInitiators="oidc"
+        p:logoutInitiators="oidc"
         p:metadataResolver-ref="shibboleth.ProviderMetadataResolverService"
-        p:tokenConsumers="#{{'oidc/code/query', 'oidc/code/post'}}">
+        p:tokenConsumers="#{{'oidc/code/query', 'oidc/code/post'}}"
+        p:logoutConsumers="#{{ 'oidc/redirect' }}">
         <property name="defaultProfileConfigurations">
             <list>
                 <ref bean="OIDC.SSO" />
+                <ref bean="OIDC.Logout"/>
             </list>
         </property>
         <property name="metadataDrivenDefaultProfileConfigurations">
@@ -104,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/main/resources/templates/oidc-request-form-post.vm b/sp-oidc-conf-impl/src/main/resources/templates/oidc-request-form-post.vm
new file mode 100644
index 0000000..6ee1e87
--- /dev/null
+++ b/sp-oidc-conf-impl/src/main/resources/templates/oidc-request-form-post.vm
@@ -0,0 +1,65 @@
+##
+## Velocity Template for OIDC Form Post response mode.
+## cspDigester - Calculates base64-encoded SHA-2 hashes (call apply). Can be null for backward compatibility.
+## cspNonce - Calculates secure nonces (call generateIdentifier). Can be null for backward compatibility.
+##
+##
+#set ($onLoad = "document.forms[0].submit()")
+#if($cspDigester)
+    $response.addHeader("Content-Security-Policy","object-src 'none'; script-src 'none'; script-src-attr 'unsafe-hashes' 'sha256-$cspDigester.apply($onLoad)'")
+#end
+<!DOCTYPE html>
+<html>
+
+<head>
+    <meta charset="utf-8" />
+</head>
+
+<body onload="$onLoad">
+    <noscript>
+        <p>
+            <strong>Note:</strong> Since your browser does not support JavaScript, you must press the Continue button once to proceed.
+        </p>
+    </noscript>
+
+    <form action="${action}" method="post">
+         <div>
+            #if($id_token_hint) <input type="hidden" name="id_token_hint" value="${id_token_hint}" />#end #if($ui_locales)
+            
+            <input type="hidden" name="ui_locales" value="${ui_locales}" />#end #if($post_logout_redirect_uri)
+            
+            <input type="hidden" name="post_logout_redirect_uri" value="${post_logout_redirect_uri}" />#end #if($logout_hint)
+            
+            <input type="hidden" name="logout_hint" value="${logout_hint}" />#end #if($client_id)
+
+            <input type="hidden" name="client_id" value="${client_id}" />#end #if($scope)
+
+            <input type="hidden" name="scope" value="${scope}" />#end #if($response_type)
+
+            <input type="hidden" name="response_type" value="${response_type}" />#end #if($response_mode)
+
+            <input type="hidden" name="response_mode" value="${response_mode}" />#end #if($redirect_uri)
+
+            <input type="hidden" name="redirect_uri" value="${redirect_uri}" />#end #if($state)
+
+            <input type="hidden" name="state" value="${state}" />#end #if($prompt)
+
+            <input type="hidden" name="prompt" value="${prompt}" />#end #if($request)
+            
+            <input type="hidden" name="request" value="${request}" />#end #if($acr_values)
+            
+            <input type="hidden" name="acr_values" value="${acr_values}" />#end #if($claims)
+
+            <input type="hidden" name="claims" value="${claims}" />#end #if($nonce)
+            
+            <input type="hidden" name="nonce" value="${nonce}" />#end
+        </div>
+        <noscript>
+            <div>
+                <input type="submit" value="Continue" />
+            </div>
+        </noscript>
+    </form>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCLogoutInitiatorFlowTest.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCLogoutInitiatorFlowTest.java
new file mode 100644
index 0000000..672dbf7
--- /dev/null
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCLogoutInitiatorFlowTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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.flows;
+
+import static org.testng.Assert.fail;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.util.Date;
+
+import javax.annotation.Nonnull;
+
+import org.apache.hc.client5.http.classic.HttpClient;
+import org.apache.hc.core5.http.io.HttpClientResponseHandler;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.mockito.Mockito;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.webflow.executor.FlowExecutionResult;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+import net.shibboleth.idp.spring.IdPPropertiesApplicationContextInitializer;
+import net.shibboleth.idp.test.PreferFileSystemApplicationContextInitializer;
+import net.shibboleth.oidc.security.credential.JWKCredential;
+import net.shibboleth.shared.security.DataSealer;
+import net.shibboleth.sp.ddf.DDF;
+import net.shibboleth.sp.flows.AbstractSPFlowTest;
+import net.shibboleth.sp.oidc.profile.impl.PrepareAgentResponse;
+import net.shibboleth.sp.oidc.testing.TestConstants;
+import net.shibboleth.sp.oidc.testing.TestHelper;
+
+/**
+ * Unit test for the OIDC-SP RP-Initiated logout flow.
+ */
+ at ContextConfiguration(
+        locations = {
+                "classpath:/net/shibboleth/sp/oidc-test-beans.xml",
+                },
+        initializers = {
+                TestSPOIDCEnvironmentApplicationContextInitializer.class,
+                PreferFileSystemApplicationContextInitializer.class,
+                IdPPropertiesApplicationContextInitializer.class
+                },
+        inheritInitializers = false
+        )
+ at WebAppConfiguration
+public class OIDCLogoutInitiatorFlowTest extends AbstractSPFlowTest {
+    
+    /** Flow ID. */
+    @Nonnull public static final String FLOW_ID = "sp/logout-initiator";
+    
+    /** Dummy signing key of the dummy OP we are testing against. */
+    @Autowired @Qualifier("dummy.op.signing.Credential") private JWKCredential opSigningCredential;
+    
+    /** DataSealer used to seal session data as input to the logout flow. */
+    @Autowired @Qualifier("shibboleth.DataSealer") private DataSealer sealer;
+    
+    /** The mocked HttpClient to use when responding to Token and UserInfo requests.*/
+    private HttpClient httpClient;
+    
+    /** The OP metadata to use.*/
+    private OIDCProviderMetadata metadata;
+    
+    /** A signed id_token for adding to the input DDF.*/
+    private JWT signedIdToken;
+    
+    /** The sealed version of the compact JWT serialisation of the id_token.*/
+    private String sealedIdToken;
+    
+    /** Constructor. */
+    protected OIDCLogoutInitiatorFlowTest() {
+        super(FLOW_ID);
+    }
+    
+
+    /** 
+     * Pre-test work.
+     *  
+     * @throws Exception on error
+     */
+    @BeforeMethod
+    public void beforeMethod() throws Exception {
+        setDefaultAuth();
+        final ApplicationContext applicationContext2 = applicationContext;
+        if (applicationContext2 != null) {
+            httpClient = applicationContext2.getBean("Mock.HttpClient", HttpClient.class);
+        } else {
+            fail("Mocked Http Client could not be found");
+        }
+        if (httpClient == null) {
+            fail("Mocked Http Client could not be found");
+        }
+        // Add a default metadata response
+        final var metadataFromFile = new ClassPathResource("metadata/openid-configuration.json");
+        final String json = new String(metadataFromFile.getInputStream().readAllBytes(), StandardCharsets.UTF_8);        
+        metadata = OIDCProviderMetadata.parse(JSONObjectUtils.parse(json));        
+        mockProviderMetadataEndpoint(metadata);
+        
+        final JWTClaimsSet.Builder claimsSetBuilder = new JWTClaimsSet.Builder()
+                .subject("jdoe")
+                .issuer(TestConstants.ISSUER)
+                .audience(TestConstants.CLIENT_ID)
+                .expirationTime(Date.from(Instant.now().plusSeconds(60)))
+                    .issueTime(Date.from(Instant.now()));
+        
+        signedIdToken = 
+                TestHelper.createJWT(claimsSetBuilder.build(), JWSAlgorithm.RS256, null, null, 
+                        opSigningCredential, null);
+        
+        sealedIdToken = sealer.wrap(signedIdToken.serialize());
+    }
+    
+    @Test
+    public void testRPInitiated() throws Exception {
+        setDefaultAuth();
+        
+        final DDF input = 
+                TestHelper.buildLogoutInitiatorDDFInput(sealedIdToken, PrepareAgentResponse.ID_TOKEN_PARAM);
+        setApplicationRequest(TestConstants.APPLICATION_ID, input);
+        
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertFlowExecutionResult(result, FLOW_ID);
+        assertFlowExecutionOutcome(result.getOutcome());
+        final var output = assertOutputMessageSuccess(result);
+        assert output != null;
+        System.out.println("test output: " + output.toString());
+        
+        
+    }
+    
+    /**
+     * Mock OpenID Provider configuration (metadata) endpoint.
+     * 
+     * @param tokenResponse the token response
+     * @param userInfoResponse the user info response
+     * 
+     * @throws IOException on error.
+     */
+    @SuppressWarnings("unchecked")
+    private void mockProviderMetadataEndpoint(final OIDCProviderMetadata metadata) throws IOException {
+        
+        Mockito.when(httpClient.execute(
+                Mockito.argThat(req -> req != null && req.getRequestUri().toString()
+                .contains(".well-known/openid-configuration")),
+                Mockito.any(HttpContext.class),
+                Mockito.any(HttpClientResponseHandler.class)))
+            .thenReturn(metadata);
+
+    }
+
+}
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/java/net/shibboleth/sp/oidc/flows/TestSPOIDCEnvironmentApplicationContextInitializer.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/TestSPOIDCEnvironmentApplicationContextInitializer.java
index 9474543..6633acc 100644
--- a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/TestSPOIDCEnvironmentApplicationContextInitializer.java
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/TestSPOIDCEnvironmentApplicationContextInitializer.java
@@ -38,6 +38,7 @@ public class TestSPOIDCEnvironmentApplicationContextInitializer extends TestSPEn
         final MockPropertySource mock = new MockPropertySource();
         mock.setProperty("idp.home", "classpath:/net/shibboleth/idp/module");
         mock.setProperty("idp.webflows", "classpath*:/flows");
+        mock.setProperty("idp.service.logging.resource", "/logback-flow-test.xml");
         mock.setProperty("sp.service.agents.resources", "test.sp.oidc.AgentResolverResources");
         // Use cookie based state management
         mock.setProperty("sp.stateToken.Manager","shibboleth.sp.CookieStateManager");
diff --git a/sp-oidc-conf-impl/src/test/resources/logback-flow-test.xml b/sp-oidc-conf-impl/src/test/resources/logback-flow-test.xml
new file mode 100644
index 0000000..a89c91c
--- /dev/null
+++ b/sp-oidc-conf-impl/src/test/resources/logback-flow-test.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<configuration>
+
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+            <pattern>%level [%logger:%line] - %msg%n</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+
+    <root>
+        <level value="WARN" />
+        <appender-ref ref="STDOUT" />
+    </root>
+    
+    <logger name="net.shibboleth.sp" level="TRACE" additivity="false">
+        <appender-ref ref="STDOUT" />
+    </logger>
+    
+    <logger name="net.shibboleth.idp" level="DEBUG" additivity="false">
+        <appender-ref ref="STDOUT" />
+    </logger>
+     
+    <logger name="org.springframework.webflow" level="INFO" additivity="false">
+        <appender-ref ref="STDOUT" />
+    </logger>
+    
+    <logger name="net.shibboleth.oidc" level="TRACE" additivity="false">
+        <appender-ref ref="STDOUT" />
+    </logger>
+    
+</configuration>
\ No newline at end of file
diff --git a/sp-oidc-conf-impl/src/test/resources/logback-test.xml b/sp-oidc-conf-impl/src/test/resources/logback-test.xml
index 55f1d6e..a70b94e 100644
--- a/sp-oidc-conf-impl/src/test/resources/logback-test.xml
+++ b/sp-oidc-conf-impl/src/test/resources/logback-test.xml
@@ -9,7 +9,7 @@
         </encoder>
     </appender>
 
-    <root level="WARN">
+    <root level="INFO">
         <appender-ref ref="STDOUT" />
     </root>
     
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-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/oidc-test-agents.xml b/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/oidc-test-agents.xml
index 60c0576..583c1bb 100644
--- a/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/oidc-test-agents.xml
+++ b/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/oidc-test-agents.xml
@@ -24,7 +24,7 @@
             <set>
                 <bean p:id="test-oidc-application-with-default-profile" parent="shibboleth.sp.Application"/>
                     
-                <bean p:id="test-oidc-application-without-ro" parent="shibboleth.sp.Application"
+                <bean p:id="test-oidc-application-without-ro" parent="shibboleth.sp.Application" 
                     p:profileConfigurations-ref="test.ProfileConfigurations"/>
                     
                 <bean p:id="test-oidc-application-with-pkce" parent="shibboleth.sp.Application"
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..8a2ef51
--- /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(@Nonnull 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/CreateAuthenticationStateData.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AddClientAddressToStateAction.java
similarity index 54%
copy from sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/CreateAuthenticationStateData.java
copy to sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AddClientAddressToStateAction.java
index a560775..3c9e842 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/CreateAuthenticationStateData.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AddClientAddressToStateAction.java
@@ -27,41 +27,30 @@ import org.slf4j.Logger;
 import net.shibboleth.idp.profile.IdPEventIds;
 import net.shibboleth.oidc.profile.config.OIDCSSORelyingPartyConfiguration;
 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;
 import net.shibboleth.sp.context.StateDataContext;
 import net.shibboleth.sp.ddf.DDF;
 import net.shibboleth.sp.messaging.RemotedHttpServletRequest;
 import net.shibboleth.sp.profile.AbstractApplicationAction;
-import net.shibboleth.sp.profile.SPConstants;
 import net.shibboleth.sp.state.StateData;
 
 /**
+ * An action that adds, if enabled in profile config, the client address to State Data.
  * 
- * An action that looks up {@link StateData} using a configured lookup strategy and adds it to a 
- * {@link StateDataContext} obtained from the profile request context using a configured creation strategy.
- * 
- * <p>In addition, the target URL is pulled out of the Agent input and added to the state data, if available, or if 
- * not, from any existing state data.</p>
+ * <p>When disabled, the pre-execute step will return false and the reminder of the action will be skipped.</p>
  * 
  * @event {@link EventIds#INVALID_PROFILE_CTX}
+ * @event {@link EventIds#INVALID_PROFILE_CONFIG}
  */
-public class CreateAuthenticationStateData extends AbstractApplicationAction {
+public class AddClientAddressToStateAction extends AbstractApplicationAction {
     
     /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(CreateAuthenticationStateData.class);
-
-    /** 
-     * Lookup strategy for the contents of the state token, as stored in a {@link StateData} or subclass thereof. 
-     * This is augmented with other state data as defined in this action. 
-     */
-    @NonnullAfterInit private Function<ProfileRequestContext,StateData> stateDataLookupStrategy;
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddClientAddressToStateAction.class);
     
-    /** Strategy used to locate or create the {@link StateDataContext} to populate. */
-    @Nonnull private Function<ProfileRequestContext,StateDataContext> stateDataContextCreationStrategy;
+    /** Strategy used to locate the {@link StateDataContext} to populate. */
+    @Nonnull private Function<ProfileRequestContext,StateDataContext> stateDataContextLookupStrategy;
     
     /** Applicable stashed profile configuration. */
     @NonnullBeforeExec private OIDCSSORelyingPartyConfiguration profileConfiguration;
@@ -75,8 +64,8 @@ public class CreateAuthenticationStateData extends AbstractApplicationAction {
     /**
      * Constructor.
      */
-    public CreateAuthenticationStateData() {
-        stateDataContextCreationStrategy = new ChildContextLookup<>(StateDataContext.class, true);
+    public AddClientAddressToStateAction() {
+        stateDataContextLookupStrategy = new ChildContextLookup<>(StateDataContext.class, false);
     }
     
     /**
@@ -84,38 +73,16 @@ public class CreateAuthenticationStateData extends AbstractApplicationAction {
      * 
      * @param strategy creation strategy
      */
-    public void setStateDataContextCreationStrategy(
+    public void setStateDataContextLookupStrategy(
             @Nonnull final Function<ProfileRequestContext,StateDataContext> strategy) {
         checkSetterPreconditions();
-        stateDataContextCreationStrategy =
+        stateDataContextLookupStrategy =
                 Constraint.isNotNull(strategy, "StateDataContext creation strategy cannot be null");
     }
-
     
     /** {@inheritDoc} */
     @Override
-    protected void doInitialize() throws ComponentInitializationException {
-        super.doInitialize();
-        
-        if (stateDataLookupStrategy == null) {
-            throw new ComponentInitializationException("StateDataLookupStrategy cannot be null");
-        }
-    }
-    
-    /**
-     * Sets the lookup strategy for obtaining the {@link StateData} of the state token.
-     * 
-     * @param strategy lookup strategy
-     */
-    public void setStateDataLookupStrategy(@Nonnull final Function<ProfileRequestContext,StateData> strategy) {
-        checkSetterPreconditions();
-        
-        stateDataLookupStrategy = Constraint.isNotNull(strategy, "State data lookup strategy cannot be null");
-    }
-    
-    /** {@inheritDoc} */
-    @Override
-    protected boolean doPreExecute(final ProfileRequestContext profileRequestContext) {
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
         if (!super.doPreExecute(profileRequestContext)) {
             return false;
         }
@@ -132,7 +99,7 @@ public class CreateAuthenticationStateData extends AbstractApplicationAction {
             return false;            
         }
         
-        stateDataContext = stateDataContextCreationStrategy.apply(profileRequestContext);
+        stateDataContext = stateDataContextLookupStrategy.apply(profileRequestContext);
         
         if (stateDataContext == null) {
             log.error("{} Error creating or locating StateDataContext", getLogPrefix());
@@ -148,45 +115,34 @@ public class CreateAuthenticationStateData extends AbstractApplicationAction {
             return false;
         }
         
+        // If disabled, return false and ignore this action
+        if (!profileConfiguration.isCheckAddress(profileRequestContext)) {
+            log.trace("{} Client address support disabled",getLogPrefix());
+            return false;
+        }
+        
         
         return true;
     }
-
+    
     /** {@inheritDoc} */
     @Override
-    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {                
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {   
 
-        // Before we create new state data, pull out the target URL if either in existing state or from the Agent input        
-        byte[] target = input.getmember(SPConstants.TARGET).unsafe_string();
-        if (target == null) {
-            final StateData oldStateData = stateDataContext.getStateData();
-            if (oldStateData != null) {
-                target = oldStateData.getRawResource();
-            }
-        }
-        
-        final StateData stateData = stateDataLookupStrategy.apply(profileRequestContext);
-        
+        final StateData stateData = stateDataContext.getStateData();
         if (stateData == null) {
-            log.error("{} Error creating StateData", getLogPrefix());
+            log.debug("{} State Data was not found", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
             return;
         }
-        
-        // Now add target to state
-        stateData.setRawResource(target);
-        
-        // Now add client address if enabled
-        if (profileConfiguration.isCheckAddress(profileRequestContext)) {
-            stateData.setClientAddress(
-                    input.getmember(RemotedHttpServletRequest.STRUCTURE_NAME)
-                        .getmember(RemotedHttpServletRequest.REMOTE_ADDR)
-                        .string());
-        }
+        final var addr = input.getmember(RemotedHttpServletRequest.STRUCTURE_NAME)
+                .getmember(RemotedHttpServletRequest.REMOTE_ADDR)
+                .string();
+        stateData.setClientAddress(addr);
        
-        stateDataContext.setStateData(stateData);
-        log.debug("{} Created authentication state data for preservation '{}'", getLogPrefix(), stateData);
+        log.debug("{} Stored client address '{}' in state data", getLogPrefix(), addr);
         
     }
+    
 
 }
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..172ec36
--- /dev/null
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AddClientIDHandler.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.oauth2.sdk.id.ClientID;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+
+/**
+ * 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 (StringSupport.trimOrNull(clientId) == null) {
+            return;
+        }
+        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..3b16477
--- /dev/null
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AddIDTokenHintHandler.java
@@ -0,0 +1,91 @@
+/*
+ * 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.text.ParseException;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import com.nimbusds.jwt.EncryptedJWT;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A message handler that populates the id_token_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);
+        if (idTokenHint == null) {
+            return;
+        }
+        if (idTokenHint instanceof EncryptedJWT) {
+            log.trace("{}: ID Token is still encrypted, possibly for the wrong target, this maybe ignored by "
+                    + "the OP", getLogPrefix());
+        }
+        getLogoutRequest().setIdTokenHint(idTokenHint);
+        if (log.isTraceEnabled()) {
+            log.trace("{}: Set id_token_hint for subject '{}'", getLogPrefix(), 
+                    getSubjectOrDefault(idTokenHint,"not-available"));
+        }
+        
+    }
+    
+    /**
+     * Get the subject from the id_token, else return the default value.
+     * 
+     * @param idToken the id_token to extract the subject from
+     * @param defaultSubject the default subject to return if the subject can not be extracted
+     * 
+     * @return the subject, either from the id_token or the default value
+     */
+    private String getSubjectOrDefault(final JWT idToken, final String defaultSubject) {
+
+        if (idToken instanceof EncryptedJWT) {
+            return defaultSubject;
+        }
+
+        try {
+            final JWTClaimsSet claims = idToken.getJWTClaimsSet();
+            if (claims != null) {
+                return claims.getSubject() != null ? claims.getSubject() : defaultSubject;
+            }
+        } catch (final ParseException e) {
+            // Ignore
+        }
+        return defaultSubject;
+        
+    }
+
+
+}
\ No newline at end of file
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AddLogoutHintHandler.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AddLogoutHintHandler.java
new file mode 100644
index 0000000..c20e4f7
--- /dev/null
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AddLogoutHintHandler.java
@@ -0,0 +1,54 @@
+/*
+ * 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;
+import net.shibboleth.shared.primitive.StringSupport;
+
+/**
+ * A message handler that populates the logout_hint parameter into the logout request.
+ */
+public class AddLogoutHintHandler extends AbstractLogoutRequestParameterValueMessageHandler<String> {
+
+    /** Logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddLogoutHintHandler.class);
+    
+    /** Constructor.*/
+    public AddLogoutHintHandler() {
+        super(String.class);
+    }    
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+        
+        final String logoutHint = getParameterValue(messageContext);
+        if (StringSupport.trimOrNull(logoutHint) == null) {
+            return;
+        }
+        getLogoutRequest().setLogoutHint(logoutHint);
+        log.trace("{}: Set logout_hint to '{}'", getLogPrefix(), logoutHint);
+        
+    }
+    
+
+
+}
\ 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..aaf5e0e
--- /dev/null
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AddPostLogoutRedirectURIHandler.java
@@ -0,0 +1,55 @@
+/*
+ * 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);
+        if (postRedirectUri == null) {
+            return;
+        }
+        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/CreateAuthenticationStateData.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/CreateStateData.java
similarity index 80%
rename from sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/CreateAuthenticationStateData.java
rename to sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/CreateStateData.java
index a560775..7715f1b 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/CreateAuthenticationStateData.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/CreateStateData.java
@@ -24,9 +24,6 @@ import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
-import net.shibboleth.idp.profile.IdPEventIds;
-import net.shibboleth.oidc.profile.config.OIDCSSORelyingPartyConfiguration;
-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;
@@ -34,7 +31,6 @@ import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
 import net.shibboleth.sp.context.StateDataContext;
 import net.shibboleth.sp.ddf.DDF;
-import net.shibboleth.sp.messaging.RemotedHttpServletRequest;
 import net.shibboleth.sp.profile.AbstractApplicationAction;
 import net.shibboleth.sp.profile.SPConstants;
 import net.shibboleth.sp.state.StateData;
@@ -49,10 +45,10 @@ import net.shibboleth.sp.state.StateData;
  * 
  * @event {@link EventIds#INVALID_PROFILE_CTX}
  */
-public class CreateAuthenticationStateData extends AbstractApplicationAction {
+public class CreateStateData extends AbstractApplicationAction {
     
     /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(CreateAuthenticationStateData.class);
+    @Nonnull private final Logger log = LoggerFactory.getLogger(CreateStateData.class);
 
     /** 
      * Lookup strategy for the contents of the state token, as stored in a {@link StateData} or subclass thereof. 
@@ -63,9 +59,6 @@ public class CreateAuthenticationStateData extends AbstractApplicationAction {
     /** Strategy used to locate or create the {@link StateDataContext} to populate. */
     @Nonnull private Function<ProfileRequestContext,StateDataContext> stateDataContextCreationStrategy;
     
-    /** Applicable stashed profile configuration. */
-    @NonnullBeforeExec private OIDCSSORelyingPartyConfiguration profileConfiguration;
-    
     /** The stashed state data context.*/
     @NonnullBeforeExec private StateDataContext stateDataContext;
     
@@ -75,7 +68,7 @@ public class CreateAuthenticationStateData extends AbstractApplicationAction {
     /**
      * Constructor.
      */
-    public CreateAuthenticationStateData() {
+    public CreateStateData() {
         stateDataContextCreationStrategy = new ChildContextLookup<>(StateDataContext.class, true);
     }
     
@@ -120,18 +113,6 @@ public class CreateAuthenticationStateData extends AbstractApplicationAction {
             return false;
         }
         
-        final RelyingPartyContext rpCtx = profileRequestContext.getSubcontext(RelyingPartyContext.class);
-        if (rpCtx != null && rpCtx.getConfiguration() != null &&
-                rpCtx.getProfileConfig() instanceof final OIDCSSORelyingPartyConfiguration 
-                rpConfig) {
-            profileConfiguration = rpConfig;
-        }
-        if (profileConfiguration == null) {
-            log.error("{} Profile configuration not found", getLogPrefix());   
-            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
-            return false;            
-        }
-        
         stateDataContext = stateDataContextCreationStrategy.apply(profileRequestContext);
         
         if (stateDataContext == null) {
@@ -175,14 +156,6 @@ public class CreateAuthenticationStateData extends AbstractApplicationAction {
         
         // Now add target to state
         stateData.setRawResource(target);
-        
-        // Now add client address if enabled
-        if (profileConfiguration.isCheckAddress(profileRequestContext)) {
-            stateData.setClientAddress(
-                    input.getmember(RemotedHttpServletRequest.STRUCTURE_NAME)
-                        .getmember(RemotedHttpServletRequest.REMOTE_ADDR)
-                        .string());
-        }
        
         stateDataContext.setStateData(stateData);
         log.debug("{} Created authentication state data for preservation '{}'", getLogPrefix(), stateData);
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/InitializeOAuth2ClientContext.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeOAuth2ClientContext.java
index 55ca76d..01f6e12 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeOAuth2ClientContext.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeOAuth2ClientContext.java
@@ -28,10 +28,8 @@ import org.slf4j.Logger;
 
 import net.shibboleth.idp.profile.AbstractProfileAction;
 import net.shibboleth.idp.profile.IdPEventIds;
-import net.shibboleth.oidc.profile.config.OIDCAuthenticationRelyingPartyProfileConfiguration;
 import net.shibboleth.oidc.profile.context.OAuth2ClientContext;
 import net.shibboleth.oidc.profile.messaging.context.OIDCPeerEntityContext;
-import net.shibboleth.profile.context.RelyingPartyContext;
 import net.shibboleth.profile.context.navigate.IssuerLookupFunction;
 import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
 import net.shibboleth.shared.logic.Constraint;
@@ -42,14 +40,13 @@ import net.shibboleth.sp.profile.AbstractAgentAction;
 
 /**
  * An {@link AbstractProfileAction action} that resolves the client identifier and redirect URI for the chosen 
- * provider (issuer). 
+ * provider (issuer). This is similar to a 'self' context in the same sense.
  * 
  * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
  * @event {@link IdPEventIds#INVALID_PROFILE_CONFIG}
  * @event {@link EventIds#INVALID_PROFILE_CTX}
  * @post Add the clientId and redirect URI to the {@link OAuth2ClientContext}
  */
-// TODO this mostly pulls in things from the profile config and stores them, it could just come from the profile later
 public class InitializeOAuth2ClientContext extends AbstractAgentAction {
 
     /** Class logger. */
@@ -65,38 +62,20 @@ public class InitializeOAuth2ClientContext extends AbstractAgentAction {
     /** A redirect_uri lookup strategy which can pull out an override redirect_uri from the profile request context.*/
     @Nonnull private  Function<ProfileRequestContext, URI> redirectUriOverrideLookupStrategy;
     
-    /** Lookup function for relying party context. */
-    @Nonnull private Function<ProfileRequestContext,RelyingPartyContext> relyingPartyContextLookupStrategy;
-    
     /** Strategy used to obtain the request issuer value. */
     @Nonnull private Function<ProfileRequestContext,String> issuerLookupStrategy;
     
-    /** Applicable stashed profile configuration. */
-    @NonnullBeforeExec private OIDCAuthenticationRelyingPartyProfileConfiguration profileConfiguration;
-    
     /** 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()));
-        
-        relyingPartyContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class);        
+   
         redirectUriOverrideLookupStrategy = new RedirectUriLookupFunction();
         issuerLookupStrategy = new IssuerLookupFunction();
     }
     
-    /**
-     * Set lookup strategy for relying party context.
-     * 
-     * @param strategy  lookup strategy
-     */
-    public void setRelyingPartyContextLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext,RelyingPartyContext> strategy) {
-        relyingPartyContextLookupStrategy =
-                Constraint.isNotNull(strategy, "RelyingPartyContext lookup strategy cannot be null");
-    }
-    
     /**
      * Set the strategy used to locate the issuer value to use.
      * 
@@ -148,17 +127,6 @@ public class InitializeOAuth2ClientContext extends AbstractAgentAction {
             return false;
         }
         
-        final RelyingPartyContext rpCtx = relyingPartyContextLookupStrategy.apply(profileRequestContext);     
-        if (rpCtx != null && rpCtx.getConfiguration() != null &&
-                rpCtx.getProfileConfig() instanceof final OIDCAuthenticationRelyingPartyProfileConfiguration rpConfig) {
-            profileConfiguration = rpConfig;
-        }
-        if (profileConfiguration == null) {
-            log.error("{} OIDCAuthorizationConfiguration not found", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
-            return false;
-        }
-        
         return true;        
     }
     
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..2fdbdad 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
  * 
@@ -138,8 +138,6 @@ public class InitializeOutboundMessageContext extends AbstractProfileAction {
         final MessageContext msgCtx = new MessageContext();
         profileRequestContext.setOutboundMessageContext(msgCtx);
         
-        //TODO self context for info about RP?
-        
         final OIDCPeerEntityContext outboundPeerContext = msgCtx.ensureSubcontext(OIDCPeerEntityContext.class);
         outboundPeerContext.setIdentifier(peerEntityCtx.getIdentifier());
         
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/LogoutRequestStateForStorageStrategy.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/LogoutRequestStateForStorageStrategy.java
new file mode 100644
index 0000000..c95086f
--- /dev/null
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/LogoutRequestStateForStorageStrategy.java
@@ -0,0 +1,40 @@
+/*
+ * 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 org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.sp.oidc.profile.LogoutRequestStateData;
+import net.shibboleth.sp.state.StateData;
+
+/**
+ *  A {@link Function} that produces {@link StateData} representing logout request state that is required
+ *  for validating the logout response (if there is one). This can be stored in a way that can be recovered later 
+ *  when the response is received.
+ */
+public class LogoutRequestStateForStorageStrategy  extends AbstractIdentifiableInitializableComponent 
+        implements Function<ProfileRequestContext,LogoutRequestStateData> {
+
+    /** {@inheritDoc} */
+    @Override
+    public LogoutRequestStateData apply(final ProfileRequestContext prc) {
+        return new LogoutRequestStateData();
+    }
+
+
+}
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/PrepareAgentResponse.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/PrepareAgentResponse.java
index 1d65ffd..bd2cf7d 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/PrepareAgentResponse.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/PrepareAgentResponse.java
@@ -58,6 +58,27 @@ import net.shibboleth.sp.profile.AbstractTokenConsumerResponseAction;
  */
 public class PrepareAgentResponse extends AbstractTokenConsumerResponseAction {
     
+    /** Parameter for accessing the access_token from opaque data. */
+    @Nonnull @NotEmpty public static final String ACCESS_TOKEN_PARAM = "access_token";
+    
+    /** Parameter for accessing the refresh_token from opaque data. */
+    @Nonnull @NotEmpty public static final String REFRESH_TOKEN_PARAM = "refresh_token";
+    
+    /** Parameter for accessing the id_token from opaque data. */
+    @Nonnull @NotEmpty public static final String ID_TOKEN_PARAM = "id_token";
+    
+    /** Parameter for accessing the token_type from opaque data. */
+    @Nonnull @NotEmpty public static final String TOKEN_TYPE_PARAM = "token_type";
+    
+    /** Parameter for accessing the expires_in from opaque data. */
+    @Nonnull @NotEmpty public static final String EXPIRES_IN_PARAM = "expires_in";
+    
+    /** Parameter for accessing the scope from opaque data. */
+    @Nonnull @NotEmpty public static final String SCOPE_PARAM = "scope";
+    
+    /** Parameter for accessing the issued_token_type from opaque data. */
+    @Nonnull @NotEmpty public static final String ISSUED_TOKEN_TYPE_PARAM = "issued_token_type";
+    
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(PrepareAgentResponse.class);
     
@@ -140,7 +161,7 @@ public class PrepareAgentResponse extends AbstractTokenConsumerResponseAction {
                 try {
                     final String refreshTokenString = refreshToken.getValue();
                     assert refreshTokenString != null;
-                    tokens.addmember("refresh_token").string(conditionallySeal(refreshTokenString));
+                    tokens.addmember(REFRESH_TOKEN_PARAM).string(conditionallySeal(refreshTokenString));
                 } catch (final DataSealerException e) {
                     log.debug("{} Error sealing refresh token for session data", getLogPrefix(), e);
                     // Is not an error, but the refresh token won't be stored.
@@ -153,7 +174,7 @@ public class PrepareAgentResponse extends AbstractTokenConsumerResponseAction {
                 try {
                     final String accessTokenString = accessToken.getValue();
                     assert accessTokenString != null;
-                    tokens.addmember("access_token").string(conditionallySeal(accessTokenString));
+                    tokens.addmember(ACCESS_TOKEN_PARAM).string(conditionallySeal(accessTokenString));
                     accessTokenAdded = true;
                 } catch (final DataSealerException e) {
                     log.debug("{} Error sealing access token for session data", getLogPrefix(), e);
@@ -168,10 +189,10 @@ public class PrepareAgentResponse extends AbstractTokenConsumerResponseAction {
                 final var localAccessToken = accessToken;
                 // Access token can not be null here.
                 assert localAccessToken != null;
-                tokens.addmember("token_type").string(localAccessToken.getType().getValue());
-                tokens.addmember("expires_in").longinteger(localAccessToken.getLifetime());
+                tokens.addmember(TOKEN_TYPE_PARAM).string(localAccessToken.getType().getValue());
+                tokens.addmember(EXPIRES_IN_PARAM).longinteger(localAccessToken.getLifetime());
                 if (localAccessToken.getScope() != null) {
-                    tokens.addmember("scope").string(localAccessToken.getScope()
+                    tokens.addmember(SCOPE_PARAM).string(localAccessToken.getScope()
                             .stream()
                             .map(String::valueOf)
                             .filter(s -> !s.isBlank())
@@ -179,7 +200,7 @@ public class PrepareAgentResponse extends AbstractTokenConsumerResponseAction {
                             .collect(java.util.stream.Collectors.joining(" ")));
                 }
                 if (localAccessToken.getIssuedTokenType() != null) {
-                    tokens.addmember("issued_token_type")
+                    tokens.addmember(ISSUED_TOKEN_TYPE_PARAM)
                     .string(localAccessToken.getIssuedTokenType().getURI().toString());
                 }
             }
@@ -188,7 +209,7 @@ public class PrepareAgentResponse extends AbstractTokenConsumerResponseAction {
                 final String serialisedIdToken = idToken.serialize();
                 if (serialisedIdToken != null) {
                     try {
-                        tokens.addmember("id_token").string(conditionallySeal(serialisedIdToken));
+                        tokens.addmember(ID_TOKEN_PARAM).string(conditionallySeal(serialisedIdToken));
                     } catch (final DataSealerException e) {
                         log.debug("{} Error sealing id_token for session data", getLogPrefix(), e);
                         // Is not an error, but the id_token will not be stored for later use e.g., logout
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/PreserveOAuthState.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/PreserveOAuthState.java
new file mode 100644
index 0000000..f240a3f
--- /dev/null
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/PreserveOAuthState.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 javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import com.nimbusds.oauth2.sdk.id.State;
+import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
+
+import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
+import net.shibboleth.oidc.profile.core.OIDCLogoutRequest;
+import net.shibboleth.oidc.profile.core.StateToken;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.sp.profile.PreserveStateDataAction;
+
+/**
+ * OAuth/OIDC-specific action that processes the state token by setting it as the state value.
+ */
+public class PreserveOAuthState extends PreserveStateDataAction {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(PreserveOAuthState.class);
+    
+    /** The 'state' claim name.*/
+    @Nonnull private static final String STATE_CLAIM = "state";
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void processToken(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull @NotEmpty final String token) {
+        
+        final MessageContext outbound = profileRequestContext.ensureOutboundMessageContext();
+        if (outbound.getMessage() instanceof final OIDCAuthenticationRequest request) {
+            log.trace("{} Setting OAuth2.0 state in authentication request to to '{}", getLogPrefix(), token);
+            request.setStateToken(new StateToken(token, null));
+            
+            // Add to Request Object if exists
+            final ClaimsSet claims = request.getRequestObjectClaimsSet();
+            if (claims != null) {            
+                log.trace("{} Adding OAuth2.0 state to JWT RequestObject", getLogPrefix());
+                claims.setClaim(STATE_CLAIM, token);           
+            }
+            
+        }
+        if (outbound.getMessage() instanceof final OIDCLogoutRequest request) {
+            log.trace("{} Setting OAuth2.0 state in logout request to '{}", getLogPrefix(), token);
+            request.setState(new State(token));
+        }
+    }
+    
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..471cd68
--- /dev/null
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ProcessLogoutInitiatorRequest.java
@@ -0,0 +1,203 @@
+/*
+ * 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.text.ParseException;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import com.nimbusds.jwt.EncryptedJWT;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.JWTParser;
+
+import net.shibboleth.idp.profile.IdPEventIds;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.security.DataSealer;
+import net.shibboleth.shared.security.DataSealerException;
+import net.shibboleth.sp.ddf.DDF;
+import net.shibboleth.sp.oidc.context.OIDCLogoutContext;
+import net.shibboleth.sp.profile.AbstractApplicationAction;
+import net.shibboleth.sp.profile.ConsumerConstants;
+
+/**
+ * Processes a request to potentially initiate a OIDC RP-Initiated logout by examining the input to recover
+ * the required id_token information from the opaque portion of the session created by the token consumer
+ * flow and store in a newly created {@link OIDCLogoutContext}.
+ * 
+ * <p>Assuming the opaque data is present and sufficient, it also creates a {@link RelyingPartyContext}
+ * based on the identity of the original OP that issued the id_token that led to the session.</p>
+ * 
+ * <p>If the data sealer is enabled, the the id_token is first unsealed.</p>
+ *
+ * @post <pre>profileRequestContext.getSubcontext(OIDCLogoutContext.class) != null</pre>
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_MESSAGE}
+ * @event {@link EventIds#UNABLE_TO_DECODE}
+ * @event {@link IdPEventIds#INVALID_RELYING_PARTY_CTX}
+ */
+public class ProcessLogoutInitiatorRequest extends AbstractApplicationAction {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(ProcessLogoutInitiatorRequest.class);
+    
+    /** Creation strategy for {@link RelyingPartyContext}. */
+    @Nonnull private Function<ProfileRequestContext,RelyingPartyContext> relyingPartyContextCreationStrategy;
+    
+    /** The id_token take from the data in the session.*/
+    @NonnullBeforeExec private JWT idToken;
+    
+    /** The relying party name to base the inbound context on. */
+    @NonnullBeforeExec private String relyingPartyId;
+    
+    /** Optional data sealer to use. */
+    @Nullable private DataSealer dataSealer;
+    
+    /** Constructor. */
+    public ProcessLogoutInitiatorRequest() {
+        relyingPartyContextCreationStrategy = new ChildContextLookup<>(RelyingPartyContext.class, true);
+    }
+    
+    /**
+     * Sets {@link DataSealer} to use.
+     * 
+     * @param sealer data sealer
+     */
+    public void setDataSealer(@Nullable final DataSealer sealer) {
+        checkSetterPreconditions();
+        
+        dataSealer = sealer;
+    }
+    
+    /**
+     * Set strategy to create the {@link RelyingPartyContext}.
+     * 
+     * @param strategy creation strategy
+     */
+    public void setRelyingPartyContextCreationStrategy(
+            @Nonnull final Function<ProfileRequestContext,RelyingPartyContext> strategy) {
+        checkSetterPreconditions();
+        
+        relyingPartyContextCreationStrategy =
+                Constraint.isNotNull(strategy, "RelyingPartyContext creation strategy cannot be null");
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }
+        
+        log.debug("{} Evaluating applicability of request to oidc logout initiator flow", getLogPrefix());
+        
+        final DDF input = ensureAgentRequestContext().getInput();
+        if (input == null) {
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+            log.error("{} No input message from agent", getLogPrefix());
+            return false;
+        }
+        
+        // The ID Token should be in JWT compact form, but should be sealed at this stage
+        final String pickled = input.getmember(ConsumerConstants.SESSION_OPAQUE)
+                .getmember(PrepareAgentResponse.ID_TOKEN_PARAM).string();
+        if (pickled == null) {
+            ActionSupport.buildEvent(profileRequestContext, EventIds.UNABLE_TO_DECODE);
+            log.info("{} No encoded id_token in input message", getLogPrefix());
+            return false;
+        }
+        
+        // Attempt to unseal the stored JWT
+        String compactIdToken = pickled;
+        final var localDataSealer = dataSealer;
+        if (localDataSealer != null) {
+            try {
+                compactIdToken = localDataSealer.unwrap(pickled);
+            } catch (final DataSealerException e) {
+                ActionSupport.buildEvent(profileRequestContext, EventIds.UNABLE_TO_DECODE);
+                log.warn("{} id_token could not be unsealed", getLogPrefix());
+                return false; 
+            }
+        }
+        
+        try {
+            idToken = JWTParser.parse(compactIdToken);
+            if (idToken instanceof EncryptedJWT) {
+                // This should not happen, we are in control of what goes into session data
+                ActionSupport.buildEvent(profileRequestContext, EventIds.UNABLE_TO_DECODE);
+                log.warn("{} id_token was still encrypted in session data", getLogPrefix());
+                return false;               
+            }
+            final var localIdToken  = idToken;
+            if (localIdToken != null) {
+                // Extract out the identity of the issuer of the token. This is safe, we've already validated the token 
+                // before we added it to the session, and the token should be sealed in storage.
+                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);
+                    log.info("{} Could not extract issuer (OP) from id_token", getLogPrefix());
+                    return false;
+                }                
+            } else {
+                ActionSupport.buildEvent(profileRequestContext, EventIds.UNABLE_TO_DECODE);
+                log.info("{} Could not extract id_token from session", getLogPrefix());
+                return false;
+            }
+        } catch (final ParseException e) {
+            ActionSupport.buildEvent(profileRequestContext, EventIds.UNABLE_TO_DECODE);
+            log.error("{} Could not parse encoded id_token in input message", getLogPrefix());
+            return false;
+        }
+        
+        return true;
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        
+        final OIDCLogoutContext logoutContext = profileRequestContext.ensureSubcontext(OIDCLogoutContext.class);
+        logoutContext.setIdToken(idToken);
+        
+        final RelyingPartyContext rpContext = relyingPartyContextCreationStrategy.apply(profileRequestContext);
+        if (rpContext == null) {
+            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CTX);
+            log.error("{} Unable to create RelyingPartyContext", getLogPrefix());
+            return;
+        }
+
+        rpContext.setRelyingPartyId(relyingPartyId);
+        log.debug("{} Initialized RelyingPartyContext for {}", getLogPrefix(), relyingPartyId);
+    
+    }
+
+}
diff --git a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/BaseOIDCAuthenticationTest.java b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/BaseOIDCAuthenticationTest.java
index 442e412..80af108 100644
--- a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/BaseOIDCAuthenticationTest.java
+++ b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/BaseOIDCAuthenticationTest.java
@@ -149,6 +149,7 @@ public abstract class BaseOIDCAuthenticationTest extends BaseApplicationActionTe
         assert outboundMsgCtx != null;
         return outboundMsgCtx;
     }
-    
+
+        
 
 }
diff --git a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/CreateAuthenticationStateDataTest.java b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/CreateAuthenticationStateDataTest.java
index 79714d0..93bbc6e 100644
--- a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/CreateAuthenticationStateDataTest.java
+++ b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/CreateAuthenticationStateDataTest.java
@@ -30,19 +30,18 @@ import net.shibboleth.profile.context.RelyingPartyContext;
 import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.sp.context.StateDataContext;
 import net.shibboleth.sp.ddf.DDF;
-import net.shibboleth.sp.messaging.RemotedHttpServletRequest;
 import net.shibboleth.sp.oidc.profile.AuthenticationRequestStateData;
 import net.shibboleth.sp.oidc.testing.TestConstants;
 import net.shibboleth.sp.profile.SPConstants;
 import net.shibboleth.sp.profile.impl.BaseApplicationActionTest;
 
 /**
- * Tests for {@link CreateAuthenticationStateData}.
+ * Tests for {@link CreateStateData}.
  */
 public class CreateAuthenticationStateDataTest extends BaseApplicationActionTest {
     
     /** The action being tested. */
-    private CreateAuthenticationStateData action;
+    private CreateStateData action;
     
     /** The RelyingPartyContext to be used in the tests. */
     private RelyingPartyContext partyContext;    
@@ -65,7 +64,7 @@ public class CreateAuthenticationStateDataTest extends BaseApplicationActionTest
     public void beforeMethod() throws ComponentInitializationException {
         super.beforeMethod();
         
-        action = new CreateAuthenticationStateData();
+        action = new CreateStateData();
 
         
         partyContext = prc.ensureSubcontext(RelyingPartyContext.class);
@@ -123,19 +122,6 @@ public class CreateAuthenticationStateDataTest extends BaseApplicationActionTest
         ActionTestingSupport.assertProceedEvent(event);
         assertNull(authnState.getResource());
     }
-   
-    @Test
-    public void testSuccess_CheckAddress() throws ComponentInitializationException {
-        input.addmember(RemotedHttpServletRequest.STRUCTURE_NAME).addmember(RemotedHttpServletRequest.REMOTE_ADDR)
-            .string("192.168.1.1");
-        rpConfig.setCheckAddress(true);
-        action.initialize();
-
-        final Event event = action.execute(src);
-        ActionTestingSupport.assertProceedEvent(event);
-        assertEquals(authnState.getResource(), TestConstants.RESOURCE_URL);
-        assertEquals(authnState.getClientAddress(), "192.168.1.1");
-    }
     
     @Test
     public void testSuccess_OldStateHasResource() throws ComponentInitializationException {
diff --git a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/ProcessLogoutInitiatorRequestTest.java b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/ProcessLogoutInitiatorRequestTest.java
new file mode 100644
index 0000000..7c97620
--- /dev/null
+++ b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/ProcessLogoutInitiatorRequestTest.java
@@ -0,0 +1,206 @@
+/*
+ * 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 static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.fail;
+
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.security.credential.BasicCredential;
+import org.opensaml.security.credential.Credential;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.webflow.execution.Event;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWT;
+
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.security.DataSealer;
+import net.shibboleth.shared.security.impl.BasicKeystoreKeyStrategy;
+import net.shibboleth.shared.spring.resource.ResourceHelper;
+import net.shibboleth.sp.context.AgentRequestContext;
+import net.shibboleth.sp.ddf.DDF;
+import net.shibboleth.sp.oidc.testing.TestHelper;
+import net.shibboleth.sp.profile.ConsumerConstants;
+
+/**
+ * Tests for {@link ProcessLogoutInitiatorRequest}.
+ */
+public class ProcessLogoutInitiatorRequestTest extends BaseOIDCAuthenticationTest {
+
+    /** Issuer to use.*/
+    private static final String ISS = "https://op.example.com";
+    
+    /** Action to test.*/
+    private ProcessLogoutInitiatorRequest action;
+    
+    /** Create a basic shared secret credential to create a MAC.*/
+    private Credential macCredential;
+    
+    /** Data sealer to use if required.*/
+    private DataSealer sealer;
+    
+    @SuppressWarnings("null")
+    @Override
+    @BeforeMethod
+    public void beforeMethod() throws ComponentInitializationException {
+        super.beforeMethod();
+        
+        action = new ProcessLogoutInitiatorRequest();
+
+        KeyGenerator keyGen;
+        try {
+            keyGen = KeyGenerator.getInstance("HmacSHA256");
+        } catch (final NoSuchAlgorithmException e) {
+            throw new ComponentInitializationException("Can not create MAC key for token signing");
+        }
+        keyGen.init(256);        
+        final SecretKey key = keyGen.generateKey();
+        macCredential = new BasicCredential(key);        
+        
+        final ClassPathResource keystoreResource = 
+                new ClassPathResource("/net/shibboleth/sp/oidc/profile/impl/SealerKeyStore.jks");
+        final ClassPathResource versionResource = 
+                new ClassPathResource("/net/shibboleth/sp/oidc/profile/impl/SealerKeyStore.kver");
+        
+        final BasicKeystoreKeyStrategy strategy = new BasicKeystoreKeyStrategy();
+        strategy.setKeyAlias("secret");
+        strategy.setKeyPassword("kpassword");
+        strategy.setKeystorePassword("password");
+        strategy.setKeystoreResource(ResourceHelper.of(keystoreResource));
+        strategy.setKeyVersionResource(ResourceHelper.of(versionResource));
+
+        sealer = new DataSealer();
+        sealer.setKeyStrategy(strategy);
+
+        try {
+            strategy.initialize();
+            sealer.initialize();
+        } catch (final ComponentInitializationException e) {
+            fail(e.getMessage());
+        }
+    }
+
+
+    @Test
+    public void testValidIdToken() throws Exception {
+        action.initialize();
+        final JWT signedIdToken = 
+                TestHelper.createSignedIDToken(ISS, "jdoe", macCredential);
+        
+        final DDF input = TestHelper.buildLogoutInitiatorDDFInput(signedIdToken.serialize(), 
+                PrepareAgentResponse.ID_TOKEN_PARAM);
+
+        final var agentCtx = prc.ensureSubcontext(AgentRequestContext.class);
+        agentCtx.setInput(input);
+
+        final Event event = action.execute(src);
+        assertNull(event);
+        final RelyingPartyContext rpc = prc.getSubcontext(RelyingPartyContext.class);
+        assertNotNull(rpc);
+        assert rpc != null;
+        assertEquals(rpc.getRelyingPartyId(), ISS);
+    }
+    
+    @Test
+    public void testValidSealedToken() throws Exception {
+        action.setDataSealer(sealer);
+        action.initialize();
+        final JWT signedIdToken = 
+                TestHelper.createSignedIDToken(ISS, "jdoe", macCredential);
+        final String idTokenSerialised = signedIdToken.serialize();        
+        final DDF input = TestHelper.buildLogoutInitiatorDDFInput(sealer.wrap(idTokenSerialised), 
+                PrepareAgentResponse.ID_TOKEN_PARAM);
+
+        final var agentCtx = prc.ensureSubcontext(AgentRequestContext.class);
+        agentCtx.setInput(input);
+
+        final Event event = action.execute(src);
+        assertNull(event);
+        final RelyingPartyContext rpc = prc.getSubcontext(RelyingPartyContext.class);
+        assertNotNull(rpc);
+        assert rpc != null;
+        assertEquals(rpc.getRelyingPartyId(), ISS);
+    }
+
+    @Test
+    public void testMissingIdToken() throws ComponentInitializationException {
+        action.initialize();
+        final DDF root = new DDF(null).structure();
+        root.addmember(ConsumerConstants.SESSION_OPAQUE);
+
+        final var agentCtx = prc.ensureSubcontext(AgentRequestContext.class);
+        agentCtx.setInput(root);
+
+        final Event event = action.execute(src);
+        assertNotNull(event);
+        assert event != null;
+        assertEquals(event.getId(), EventIds.UNABLE_TO_DECODE);
+    }
+
+    @Test
+    public void testInvalidTokenFormat() throws ComponentInitializationException {
+        action.initialize();
+        final String invalidToken = "not.a.jwt";
+
+        final DDF input = TestHelper.buildLogoutInitiatorDDFInput(invalidToken,  PrepareAgentResponse.ID_TOKEN_PARAM);
+
+        final var agentCtx = prc.ensureSubcontext(AgentRequestContext.class);
+        agentCtx.setInput(input);
+
+        final Event event = action.execute(src);
+        assertNotNull(event);
+        assert event != null;
+        assertEquals(event.getId(), EventIds.UNABLE_TO_DECODE);
+    }
+
+    @Test
+    public void testNoIssuer() throws Exception {
+        action.initialize();
+        final JWT signedIdToken = 
+                TestHelper.createSignedIDToken(null, "jdoe", macCredential);
+
+        final DDF input = TestHelper.buildLogoutInitiatorDDFInput(signedIdToken.serialize()
+                , PrepareAgentResponse.ID_TOKEN_PARAM);
+
+        final var agentCtx = prc.ensureSubcontext(AgentRequestContext.class);
+        agentCtx.setInput(input);
+
+        final Event event = action.execute(src);
+        assertNotNull(event);
+        assert event != null;
+        assertEquals(event.getId(), EventIds.UNABLE_TO_DECODE);
+    }
+    
+    @Test
+    public void testNoDDF() throws Exception {
+        action.initialize();
+        final Event event = action.execute(src);
+        assertNotNull(event);
+        assert event != null;
+        assertEquals(event.getId(), EventIds.INVALID_MESSAGE);
+    }
+
+}
+
diff --git a/sp-oidc-impl/src/test/resources/net/shibboleth/sp/oidc/profile/impl/SealerKeyStore.jks b/sp-oidc-impl/src/test/resources/net/shibboleth/sp/oidc/profile/impl/SealerKeyStore.jks
new file mode 100644
index 0000000..147d92b
Binary files /dev/null and b/sp-oidc-impl/src/test/resources/net/shibboleth/sp/oidc/profile/impl/SealerKeyStore.jks differ
diff --git a/sp-oidc-impl/src/test/resources/net/shibboleth/sp/oidc/profile/impl/SealerKeyStore.kver b/sp-oidc-impl/src/test/resources/net/shibboleth/sp/oidc/profile/impl/SealerKeyStore.kver
new file mode 100644
index 0000000..c232bd3
--- /dev/null
+++ b/sp-oidc-impl/src/test/resources/net/shibboleth/sp/oidc/profile/impl/SealerKeyStore.kver
@@ -0,0 +1 @@
+CurrentVersion = 2

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


More information about the commits mailing list