[java-idp-oidc] 02/02: JOIDC-127 - Include sid claim in id_token

Henri Mikkonen henri.mikkonen at iki.fi
Tue Sep 20 12:33:53 UTC 2022


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

hjmikkon pushed a commit to branch main
in repository java-idp-oidc.

View the commit online:
http://git.shibboleth.net/view/?p=java-idp-oidc.git;a=commit;h=d5964bb9e04bc3ae993a27710c96bb194a121f7b

commit d5964bb9e04bc3ae993a27710c96bb194a121f7b
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Tue Sep 20 15:31:55 2022 +0300

    JOIDC-127 - Include sid claim in id_token
    
    https://shibboleth.atlassian.net/browse/JOIDC-127
    
    The sid value is generated by the authorize endpoint for end-user flows, and by the token
    endpoint for the client_credentials grant. The value is stored to OIDCAuthenticationResponseContext and
    encoded to the token claims sets (authz code, accesa and refresh tokens).
    
    The generation strategy can be configured with idp.oidc.SessionIdentifierGenerationStrategy -property, it defaults to
    shibboleth.DefaultIdentifierGenerationStrategy.
---
 .../context/OIDCAuthenticationResponseContext.java |  30 ++++-
 .../TokenRequestSessionIdLookupFunction.java       |  41 +++++++
 .../op/token/support/AccessTokenClaimsSet.java     |   1 +
 .../op/token/support/RefreshTokenClaimsSet.java    |   1 +
 .../oidc/op/token/support/TokenClaimsSet.java      |  38 +++++-
 .../op/oauth2/profile/impl/BuildAccessToken.java   |   4 +-
 .../oidc/op/profile/impl/AddIDTokenShell.java      |   6 +
 .../SetAuthorizationCodeToResponseContext.java     |   1 +
 .../impl/SetSessionIdToResponseContext.java        |  75 ++++++++++++
 .../flows/oidc/abstract/oidc-abstract-beans.xml    |  13 +-
 .../idp/flows/oidc/authorize/authorize-beans.xml   |   4 +
 .../idp/flows/oidc/authorize/authorize-flow.xml    |   1 +
 .../idp/flows/oidc/token/token-beans.xml           |   7 ++
 .../shibboleth/idp/flows/oidc/token/token-flow.xml |   2 +
 .../idp/plugin/oidc/op/conf/oidc.properties        |   3 +
 .../oauth2/profile/impl/BuildAccessTokenTest.java  |  75 ++++++++++++
 .../oidc/op/profile/flow/AuthorizeFlowTest.java    | 112 ++++++++++++++++-
 .../plugin/oidc/op/profile/flow/TokenFlowTest.java | 128 +++++++++++++++++++-
 .../oidc/op/profile/impl/AddIDTokenShellTest.java  |  17 +++
 .../SetAuthorizationCodeToResponseContextTest.java |  24 ++++
 .../impl/SetSessionIdToResponseContextTest.java    | 133 +++++++++++++++++++++
 21 files changed, 708 insertions(+), 8 deletions(-)

diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/OIDCAuthenticationResponseContext.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/OIDCAuthenticationResponseContext.java
index 94e54224..83744498 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/OIDCAuthenticationResponseContext.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/OIDCAuthenticationResponseContext.java
@@ -127,7 +127,10 @@ public class OIDCAuthenticationResponseContext extends BaseContext {
 
     /** Mapped requested claims from the Userinfo set. */
     @Nullable private AttributesMapContainer mappedUserinfoRequestedClaims;
-    
+
+    /** Session identifier. */
+    @Nullable private String sessionId;
+
     /** Constructor. */
     public OIDCAuthenticationResponseContext() {
         validatedAudience = new ArrayList<>();
@@ -556,5 +559,28 @@ public class OIDCAuthenticationResponseContext extends BaseContext {
     public void setRequestObject(@Nullable final JWT obj) {
         requestObject = obj;
     }
-    
+
+    /**
+     * Get the session identifier.
+     * 
+     * @return session identifier
+     * 
+     * @since 3.3.0
+     */
+    @Nullable
+    public String getSessionId() {
+        return sessionId;
+    }
+
+    /**
+     * Set the session identifier.
+     * 
+     * @param sid session identifier
+     * 
+     * @since 3.3.0
+     */
+    public void setSessionId(@Nullable final String sid) {
+        sessionId = sid;
+    }
+
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/TokenRequestSessionIdLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/TokenRequestSessionIdLookupFunction.java
new file mode 100644
index 00000000..fa5871d1
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/TokenRequestSessionIdLookupFunction.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.profile.context.navigate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
+
+/**
+ * For Token and UserInfo end points.
+ * 
+ * A function that returns session id claim via a lookup function. This lookup locates id from token (Authorization
+ * Code / Access Token) for token request handling. If id is not available, null is returned.
+ * 
+ * @since 3.3.0
+ */
+public class TokenRequestSessionIdLookupFunction extends AbstractTokenClaimsLookupFunction<String> {
+
+    /** {@inheritDoc} */
+    @Override @Nullable
+    String doLookup(@Nonnull final TokenClaimsSet tokenClaims) {
+        return tokenClaims.getSessionIdentifier();
+    }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/AccessTokenClaimsSet.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/AccessTokenClaimsSet.java
index f39f75da..9aefa336 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/AccessTokenClaimsSet.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/AccessTokenClaimsSet.java
@@ -231,6 +231,7 @@ public final class AccessTokenClaimsSet extends TokenClaimsSet {
             setConsentedClaims(existing.getConsentedClaims());
             setConsentEnabled(existing.isConsentEnabled());
             setRootTokenIdentifier(existing.getRootTokenIdentifier());
+            setSessionIdentifier(existing.getSessionIdentifier());
         }
         
         /**
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/RefreshTokenClaimsSet.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/RefreshTokenClaimsSet.java
index b060659b..fe151743 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/RefreshTokenClaimsSet.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/RefreshTokenClaimsSet.java
@@ -146,6 +146,7 @@ public final class RefreshTokenClaimsSet extends TokenClaimsSet {
             setDlClaimsUI(existing.getUserinfoDeliveryClaims());
             setConsentedClaims(existing.getConsentedClaims());
             setConsentEnabled(existing.isConsentEnabled());
+            setSessionIdentifier(existing.getSessionIdentifier());
         }
         
         /**
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/TokenClaimsSet.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/TokenClaimsSet.java
index ad63c0f1..f1cd8073 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/TokenClaimsSet.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/TokenClaimsSet.java
@@ -136,6 +136,9 @@ public class TokenClaimsSet {
     /** Identifier for the root token in the chain. */
     @Nonnull @NotEmpty public static final String KEY_ROOT_JTI = "root_jti";
 
+    /** Identifier for the session id. */
+    @Nonnull @NotEmpty public static final String KEY_SESSION_ID = "sid";
+
     /** Claims set for the claim. */
     @Nullable private JWTClaimsSet tokenClaimsSet;
 
@@ -642,6 +645,21 @@ public class TokenClaimsSet {
         return (String) tokenClaimsSet.getClaim(KEY_ROOT_JTI);
     }
 
+    /**
+     * Get the session identifier.
+     * 
+     * @return the session identifier.
+     * 
+     * @since 3.3.0
+     */
+    @Nullable public String getSessionIdentifier() {
+        Constraint.isNotNull(tokenClaimsSet, "JWTClaimsSet cannot be null");
+        if (tokenClaimsSet.getClaim(KEY_SESSION_ID) == null) {
+            return null;
+        }
+        return (String) tokenClaimsSet.getClaim(KEY_SESSION_ID);
+    }
+
     /**
      * Abstract builder to extend builders from that are instantiating claims sets extending TokenClaimsSet.
      * 
@@ -720,6 +738,9 @@ public class TokenClaimsSet {
         /** Root token identifier. */
         @Nullable protected String rootTokenId;
         
+        /** Session identifier. */
+        @Nullable protected String sessionId;
+
         /** Default constructor. */
         protected Builder() {
             audience = Collections.emptyList();
@@ -769,7 +790,8 @@ public class TokenClaimsSet {
                     .claim(KEY_CONSENTED_CLAIMS, consentedClaims)
                     .claim(KEY_CODE_CHALLENGE, codeChallenge)
                     .claim(KEY_CONSENT_ENABLED, consentEnabled)
-                    .claim(KEY_ROOT_JTI, rootTokenId);
+                    .claim(KEY_ROOT_JTI, rootTokenId)
+                    .claim(KEY_SESSION_ID, sessionId);
             
             customClaims.forEach((n,v) -> {
                 if (n != null) {
@@ -1143,6 +1165,20 @@ public class TokenClaimsSet {
             return this;
         }
 
+        /**
+         * Set session identifier.
+         * 
+         * @param id session identifier
+         * 
+         * @return the builder
+         * 
+         * @since 3.3.0
+         */
+        public Builder<T> setSessionIdentifier(@Nullable final String id) {
+            sessionId = id;
+            return this;
+        }
+
         /**
          * Builds claims set.
          * 
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/BuildAccessToken.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/BuildAccessToken.java
index ea65d84f..ad82eb9e 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/BuildAccessToken.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/BuildAccessToken.java
@@ -459,6 +459,7 @@ public class BuildAccessToken extends AbstractOIDCResponseAction {
             // Add additional bits.
             builder.setAudience(responseCtx.getAudience());
             builder.setJWTID(idGenerator);
+            builder.setSessionIdentifier(responseCtx.getSessionId());
             // Set root token identifier to contain jit from the claims set used for building the new token
             if (StringSupport.trimOrNull(tokenClaimsSet.getRootTokenIdentifier()) == null) {
                 builder.setRootTokenIdentifier(tokenClaimsSet.getID());
@@ -483,7 +484,8 @@ public class BuildAccessToken extends AbstractOIDCResponseAction {
                     .setDlClaims(claims)
                     .setDlClaimsUI(claimsUI)
                     .setConsentedClaims(consented)
-                    .setConsentEnabled(consentEnabledPredicate.test(profileRequestContext));
+                    .setConsentEnabled(consentEnabledPredicate.test(profileRequestContext))
+                    .setSessionIdentifier(responseCtx.getSessionId());
 
             if (authenticationRequest != null) {
                 builder
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddIDTokenShell.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddIDTokenShell.java
index ab4950e8..8e7672c2 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddIDTokenShell.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddIDTokenShell.java
@@ -36,6 +36,7 @@ import net.shibboleth.oidc.profile.config.navigate.AudienceRestrictionsLookupFun
 import net.shibboleth.oidc.profile.config.navigate.IDTokenLifetimeLookupFunction;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
 
 import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.context.ProfileRequestContext;
@@ -46,6 +47,7 @@ import com.nimbusds.oauth2.sdk.id.Audience;
 import com.nimbusds.oauth2.sdk.id.Issuer;
 import com.nimbusds.oauth2.sdk.id.Subject;
 import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet;
+import com.nimbusds.openid.connect.sdk.claims.SessionID;
 
 /**
  * Action that creates a {@link IDTokenClaimsSet} object shell, and sets it to work context
@@ -162,6 +164,10 @@ public class AddIDTokenShell extends AbstractOIDCResponseAction {
         final Date exp = Date.from(Instant.now().plus(lifetime)); 
         final IDTokenClaimsSet idToken = new IDTokenClaimsSet(new Issuer(issuerId),
                 new Subject(getOidcResponseContext().getSubject()), aud, exp, new Date());
+        final String sid = getOidcResponseContext().getSessionId();
+        if (StringSupport.trimOrNull(sid) != null) {
+            idToken.setSessionID(new SessionID(sid));
+        }
         log.debug("{} Setting ID token shell to response context {}", getLogPrefix(),
                 idToken.toJSONObject().toJSONString());
         getOidcResponseContext().setIDToken(idToken);
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetAuthorizationCodeToResponseContext.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetAuthorizationCodeToResponseContext.java
index 15400a08..dbd9fb0c 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetAuthorizationCodeToResponseContext.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetAuthorizationCodeToResponseContext.java
@@ -360,6 +360,7 @@ public class SetAuthorizationCodeToResponseContext extends AbstractOIDCAuthentic
                 .setDlClaimsUI(claimsUI)
                 .setConsentedClaims(consented)
                 .setConsentEnabled(consentEnabledPredicate.test(profileRequestContext))
+                .setSessionIdentifier(responseCtx.getSessionId())
                 .build();
 
         if (manipulationStrategy != null) {
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetSessionIdToResponseContext.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetSessionIdToResponseContext.java
new file mode 100644
index 00000000..913c4e40
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetSessionIdToResponseContext.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.profile.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
+import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.TokenRequestSessionIdLookupFunction;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Action that locates session id using configurable lookup strategy. Located session id is set to
+ * {@link OIDCAuthenticationResponseContext}.
+ **/
+public class SetSessionIdToResponseContext extends AbstractOIDCResponseAction {
+
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(SetSessionIdToResponseContext.class);
+
+    /** Strategy used to obtain the session identifier. */
+    @Nonnull private Function<ProfileRequestContext, String> sessionIdLookupStrategy;
+
+    /**
+     * Constructor.
+     */
+    public SetSessionIdToResponseContext() {
+        sessionIdLookupStrategy = new TokenRequestSessionIdLookupFunction();
+    }
+
+    /**
+     * Set the strategy used to locate session identifier.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setSessionIdLookupStrategy(@Nonnull final Function<ProfileRequestContext, String> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        sessionIdLookupStrategy = Constraint.isNotNull(strategy, "Session ID lookup strategy cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        final String sessionId = sessionIdLookupStrategy.apply(profileRequestContext);
+        if (StringSupport.trimOrNull(sessionId) == null) {
+            log.debug("{} Session id value did not exist", getLogPrefix());
+        } else {
+            log.debug("{} Setting session id value to {}", getLogPrefix(), sessionId);
+            getOidcResponseContext().setSessionId(sessionId);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract/oidc-abstract-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract/oidc-abstract-beans.xml
index f2b30adc..5ad5361f 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract/oidc-abstract-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract/oidc-abstract-beans.xml
@@ -100,5 +100,16 @@
 
     <bean id="FlowStartPopulateAuditContext" parent="shibboleth.AbstractPopulateAuditContext"
         p:fieldExtractors="#{getObject('shibboleth.FlowStartAuditExtractors') ?: getObject('shibboleth.DefaultFlowStartAuditExtractors')}" />
-    
+
+    <bean id="SessionIdGenerationStrategy" parent="shibboleth.Functions.Scripted" 
+        p:customObject="#{getObject('%{idp.oidc.SessionIdentifierGenerationStrategy:shibboleth.DefaultIdentifierGenerationStrategy}'.trim())}">
+        <constructor-arg>
+            <value>
+            <![CDATA[
+                custom.generateIdentifier();
+            ]]>
+            </value>
+        </constructor-arg>
+    </bean>
+
 </beans>
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
index a686307d..20f96972 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
@@ -41,6 +41,10 @@
     <bean id="SetRequestedSubjectToResponseContext"
         class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SetRequestedSubjectToResponseContext" scope="prototype" />
 
+    <bean id="SetSessionIdToResponseContext"
+        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SetSessionIdToResponseContext" scope="prototype"
+        p:sessionIdLookupStrategy-ref="SessionIdGenerationStrategy" />
+
     <bean id="SetRequestedClaimsToResponseContext"
         class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SetRequestedClaimsToResponseContext" scope="prototype"
         p:transcoderRegistry-ref="shibboleth.AttributeRegistryService" />
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
index db319735..51f782e3 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
@@ -51,6 +51,7 @@
         <evaluate expression="ValidateCodeChallenge" />
         <evaluate expression="SetRequestedClaimsToResponseContext" />
         <evaluate expression="SetRequestedSubjectToResponseContext" />
+        <evaluate expression="SetSessionIdToResponseContext" />
         <evaluate expression="'proceed'" />
         <transition on="proceed" to="PopulateClientStorageLoadContext" />
     </action-state>
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
index b6e1fd2b..1ce78d1b 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
@@ -134,6 +134,13 @@
         </property>
     </bean>
 
+    <bean id="SetSessionIdToResponseContext"
+        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SetSessionIdToResponseContext" scope="prototype"
+        p:sessionIdLookupStrategy-ref="SessionIdGenerationStrategy" />
+
+    <bean id="SetSessionIdFromAuthzCodeToResponseContext"
+        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SetSessionIdToResponseContext" scope="prototype" />
+
     <!-- Common grant handling.  -->
 
     <bean id="ValidateScope" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateScope" scope="prototype"
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-flow.xml
index c14e9c4b..fb4f16c0 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-flow.xml
@@ -60,6 +60,7 @@
         <evaluate expression="SetSubjectFromAuthzCodeToResponseContext" />
         <evaluate expression="ValidateScope" />
         <evaluate expression="ValidateAudience" />
+        <evaluate expression="SetSessionIdFromAuthzCodeToResponseContext" />
         <evaluate expression="'proceed'" />
         
         <transition on="proceed" to="CheckTraditionalGrantForAudience" />
@@ -79,6 +80,7 @@
         <evaluate expression="SetSubjectFromSubjectContextToResponseContext" />
         <evaluate expression="ValidateScope" />
         <evaluate expression="ValidateAudience" />
+        <evaluate expression="SetSessionIdToResponseContext" />
         
         <evaluate expression="'proceed'" />
         <transition on="proceed" to="LookupAudienceMetadata" />
diff --git a/idp-oidc-extension-impl/src/main/resources/net/shibboleth/idp/plugin/oidc/op/conf/oidc.properties b/idp-oidc-extension-impl/src/main/resources/net/shibboleth/idp/plugin/oidc/op/conf/oidc.properties
index 1df268c2..776e5ab0 100644
--- a/idp-oidc-extension-impl/src/main/resources/net/shibboleth/idp/plugin/oidc/op/conf/oidc.properties
+++ b/idp-oidc-extension-impl/src/main/resources/net/shibboleth/idp/plugin/oidc/op/conf/oidc.properties
@@ -96,6 +96,9 @@ idp.oidc.subject.salt = this_too_should_be_ch4ng3d
 # Bean used for extracting login_hint from the authentication request. The default function parses login_hint as is.
 #idp.oidc.LoginHintLookupStrategy = DefaultRequestLoginHintLookupFunction
 
+# Bean used for generating session identifiers, stored as sid claim in the id_token and claims sets.
+#idp.oidc.SessionIdentifierGenerationStrategy = shibboleth.DefaultIdentifierGenerationStrategy
+
 # Bean used for creating SPSessions needed for SLO. By default builds protocol-independent BasicSPSession, as SLO is not yet supported.
 #idp.oidc.SPSessionCreationStrategy = DefaultSPSessionCreationStrategy
 
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/BuildAccessTokenTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/BuildAccessTokenTest.java
index e888d10e..1f67c907 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/BuildAccessTokenTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/BuildAccessTokenTest.java
@@ -117,6 +117,28 @@ public class BuildAccessTokenTest extends BaseOIDCResponseActionTest {
                 Collections.singletonList("https://rp.example.org"), null);
     }
 
+    /**
+     * Basic success case.
+     * 
+     * @throws ParseException 
+     * @throws DataSealerException 
+     * @throws ComponentInitializationException 
+     * @throws NoSuchAlgorithmException
+     */
+    @Test
+    public void testOpaqueSuccessSid() throws ParseException, DataSealerException, ComponentInitializationException,
+            NoSuchAlgorithmException {
+
+        initAction(null, null);
+        respCtx.setSessionId("mockSid");
+        
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertProceedEvent(event);
+        
+        verifyClaims(respCtx.getSubcontext(AccessTokenContext.class), new Scope(),
+                Collections.singletonList("https://rp.example.org"), null, "mockSid");
+    }
+
     /**
      * Basic success case with custom claims.
      * 
@@ -159,6 +181,28 @@ public class BuildAccessTokenTest extends BaseOIDCResponseActionTest {
                 Collections.singletonList("https://rp.example.org"), null);
     }
 
+    /**
+     * Basic success case, direct reuse of requested scope/audience.
+     * 
+     * @throws ParseException 
+     * @throws ComponentInitializationException 
+     * @throws NoSuchAlgorithmException
+     * @throws DataSealerException 
+     */
+    @Test
+    public void testJWTSuccessSid() throws ParseException, ComponentInitializationException, NoSuchAlgorithmException,
+            DataSealerException {
+
+        initAction("JWT", null);
+        
+        respCtx.setSessionId("mockSid");
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertProceedEvent(event);
+        
+        verifyClaims(respCtx.getSubcontext(AccessTokenContext.class), new Scope(),
+                Collections.singletonList("https://rp.example.org"), null, "mockSid");
+    }
+
     /**
      * Basic success case, direct reuse of requested scope/audience.
      * 
@@ -219,6 +263,27 @@ public class BuildAccessTokenTest extends BaseOIDCResponseActionTest {
     private void verifyClaims(@Nonnull final AccessTokenContext ctx, @Nonnull final Scope scope,
             @Nonnull @NonnullElements final Collection<String> audiences, final Map<String, Object> customClaims)
             throws NoSuchAlgorithmException, ParseException, DataSealerException, ComponentInitializationException {
+        verifyClaims(ctx, scope, audiences, customClaims, null);
+    }
+
+    /**
+     * Verify access token's claims.
+     * 
+     * @param ctx access token context
+     * @param scope scope to check for
+     * @param audiences audiences to check for
+     * @param customClaims custom claims to check for, may be null
+     * @param sid session id to check for, may be null
+     * 
+     * @throws ComponentInitializationException 
+     * @throws DataSealerException 
+     * @throws ParseException 
+     * @throws NoSuchAlgorithmException 
+     */
+    private void verifyClaims(@Nonnull final AccessTokenContext ctx, @Nonnull final Scope scope,
+            @Nonnull @NonnullElements final Collection<String> audiences, final Map<String, Object> customClaims,
+            @Nullable final String sid)
+            throws NoSuchAlgorithmException, ParseException, DataSealerException, ComponentInitializationException {
         
         assertEquals(ctx.getLifetime(), Duration.ofMinutes(10));
         
@@ -236,6 +301,11 @@ public class BuildAccessTokenTest extends BaseOIDCResponseActionTest {
             assertEquals(at.getSubject(), clientId);
             assertEquals(at.getPrincipal(), "jdoe");
             verifyCustomClaims(at.getClaimsSet(), customClaims);
+            if (sid == null) {
+                Assert.assertNull(at.getSessionIdentifier());
+            } else {
+                Assert.assertEquals(at.getSessionIdentifier(), sid);
+            }
         } else if (ctx.getJWT() != null) {
             final JWTClaimsSet claims = ctx.getJWT().getJWTClaimsSet();
             assertNotNull(claims);
@@ -252,6 +322,11 @@ public class BuildAccessTokenTest extends BaseOIDCResponseActionTest {
                     JWTClaimsSet.parse(getDataSealer().unwrap(claims.getStringClaim(TokenClaimsSet.KEY_SEALED_FOR_OP)));
             assertEquals(unsealedClaims.getStringClaim(TokenClaimsSet.KEY_USER_PRINCIPAL), "jdoe");
             verifyCustomClaims(claims, customClaims);
+            if (sid == null) {
+                Assert.assertNull(claims.getStringClaim("sid"));
+            } else {
+                Assert.assertEquals(claims.getStringClaim("sid"), sid);
+            }
         } else {
             throw new RuntimeException("No token found");
         }
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java
index 32e41a43..16c84fa4 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java
@@ -99,6 +99,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNull(successResponse.getIDToken());
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertNotNull(getSidFromAuthorizeCodeClaimsSet(successResponse));
         Assert.assertNull(successResponse.getIssuer());
     }
 
@@ -118,6 +119,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNull(successResponse.getIDToken());
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertNotNull(getSidFromAuthorizeCodeClaimsSet(successResponse));
         Assert.assertNull(successResponse.getIssuer());
     }
 
@@ -137,6 +139,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNull(successResponse.getIDToken());
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertNotNull(getSidFromAuthorizeCodeClaimsSet(successResponse));
         Assert.assertNotNull(successResponse.getIssuer());
         Assert.assertEquals(successResponse.getIssuer().getValue(), issuer);
     }
@@ -193,6 +196,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         final AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
         Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
         Assert.assertNotNull(successResponse.getIDToken());
+        Assert.assertNotNull(getSidFromIDToken(successResponse));
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNull(successResponse.getAuthorizationCode());
         Assert.assertNull(successResponse.getIssuer());
@@ -212,6 +216,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         final AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
         Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
         Assert.assertNotNull(successResponse.getIDToken());
+        Assert.assertNotNull(getSidFromIDToken(successResponse));
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNull(successResponse.getAuthorizationCode());
         Assert.assertNotNull(successResponse.getIssuer());
@@ -232,6 +237,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         final AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
         Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
         Assert.assertNotNull(successResponse.getIDToken());
+        Assert.assertNotNull(getSidFromIDToken(successResponse));
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNull(successResponse.getAuthorizationCode());
         Assert.assertNull(successResponse.getIssuer());
@@ -251,7 +257,11 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         final AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
         Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
         Assert.assertNotNull(successResponse.getIDToken());
+        final String sid1 = getSidFromIDToken(successResponse);
+        Assert.assertNotNull(sid1);
         Assert.assertNotNull(successResponse.getAccessToken());
+        final String sid2 = getSidFromOpaqueAccessTokenClaimsSet(successResponse);
+        Assert.assertEquals(sid1, sid2);
         Assert.assertNull(successResponse.getAuthorizationCode());
         
         final AccessTokenClaimsSet token =
@@ -275,7 +285,11 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         final AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
         Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
         Assert.assertNotNull(successResponse.getIDToken());
+        final String sid1 = getSidFromIDToken(successResponse);
+        Assert.assertNotNull(sid1);
         Assert.assertNotNull(successResponse.getAccessToken());
+        final String sid2 = getSidFromOpaqueAccessTokenClaimsSet(successResponse);
+        Assert.assertEquals(sid1, sid2);
         Assert.assertNull(successResponse.getAuthorizationCode());
         
         final AccessTokenClaimsSet token =
@@ -300,7 +314,11 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         final AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
         Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
         Assert.assertNotNull(successResponse.getIDToken());
+        final String sid1 = getSidFromIDToken(successResponse);
+        Assert.assertNotNull(sid1);
         Assert.assertNotNull(successResponse.getAccessToken());
+        final String sid2 = getSidFromJWTAccessTokenClaimsSet(successResponse);
+        Assert.assertEquals(sid1, sid2);
         Assert.assertNull(successResponse.getAuthorizationCode());
         Assert.assertNull(successResponse.getIssuer());
 
@@ -361,8 +379,12 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         final AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
         Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
         Assert.assertNotNull(successResponse.getIDToken());
+        final String sid1 = getSidFromIDToken(successResponse);
+        Assert.assertNotNull(sid1);
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        final String sid2 = getSidFromAuthorizeCodeClaimsSet(successResponse);
+        Assert.assertEquals(sid1, sid2);
         Assert.assertNull(successResponse.getIssuer());
     }
 
@@ -380,8 +402,12 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         final AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
         Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
         Assert.assertNotNull(successResponse.getIDToken());
+        final String sid1 = getSidFromIDToken(successResponse);
+        Assert.assertNotNull(sid1);
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        final String sid2 = getSidFromAuthorizeCodeClaimsSet(successResponse);
+        Assert.assertEquals(sid1, sid2);
         Assert.assertNotNull(successResponse.getIssuer());
         Assert.assertEquals(successResponse.getIssuer().getValue(), issuer);
     }
@@ -399,9 +425,12 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         final AuthenticationResponse responseMessage = parseSuccessResponse(result, AuthenticationResponse.class);
         final AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
         Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
-        Assert.assertNotNull(successResponse.getIDToken());
+        final String sid1 = getSidFromIDToken(successResponse);
+        Assert.assertNotNull(sid1);
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        final String sid2 = getSidFromAuthorizeCodeClaimsSet(successResponse);
+        Assert.assertEquals(sid1, sid2);
         Assert.assertNull(successResponse.getIssuer());
     }
 
@@ -435,6 +464,10 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNull(successResponse.getIDToken());
         Assert.assertNotNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+
+        final String sid1 = getSidFromOpaqueAccessTokenClaimsSet(successResponse);
+        final String sid2 = getSidFromAuthorizeCodeClaimsSet(successResponse);
+        Assert.assertEquals(sid1, sid2);
         
         final AccessTokenClaimsSet token =
                 AccessTokenClaimsSet.parse(successResponse.getAccessToken().getValue(), getDataSealer());
@@ -462,6 +495,10 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNotNull(successResponse.getAuthorizationCode());
         Assert.assertNull(successResponse.getIssuer());
 
+        final String sid1 = getSidFromJWTAccessTokenClaimsSet(successResponse);
+        final String sid2 = getSidFromAuthorizeCodeClaimsSet(successResponse);
+        Assert.assertEquals(sid1, sid2);
+
         final JWTClaimsSet token =  SignedJWT.parse(successResponse.getAccessToken().getValue()).getJWTClaimsSet();
         Assert.assertEquals(token.getAudience(), List.of(resource, issuer));
         Assert.assertNotNull(token.getStringClaim("eduPersonScopedAffiliation"));
@@ -485,6 +522,10 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNotNull(successResponse.getAuthorizationCode());
         Assert.assertNull(successResponse.getIssuer());
 
+        final String sid1 = getSidFromOpaqueAccessTokenClaimsSet(successResponse);
+        final String sid2 = getSidFromAuthorizeCodeClaimsSet(successResponse);
+        Assert.assertEquals(sid1, sid2);
+
         final AccessTokenClaimsSet token =
                 AccessTokenClaimsSet.parse(successResponse.getAccessToken().getValue(), getDataSealer());
         Assert.assertEquals(token.getAudience(), Collections.singletonList(issuer));
@@ -510,6 +551,12 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNotNull(successResponse.getAuthorizationCode());
         Assert.assertNull(successResponse.getIssuer());
 
+        final String sid1 = getSidFromOpaqueAccessTokenClaimsSet(successResponse);
+        final String sid2 = getSidFromAuthorizeCodeClaimsSet(successResponse);
+        final String sid3 = getSidFromIDToken(successResponse);
+        Assert.assertEquals(sid1, sid2);
+        Assert.assertEquals(sid2, sid3);
+
         final IDTokenClaimsSet idToken = new IDTokenClaimsSet(successResponse.getIDToken().getJWTClaimsSet());
         Assert.assertNotNull(idToken);
         Assert.assertNotNull(idToken.getStringClaim("custom_id_token_claim"));
@@ -542,6 +589,12 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNotNull(successResponse.getAuthorizationCode());
         Assert.assertNull(successResponse.getIssuer());
 
+        final String sid1 = getSidFromJWTAccessTokenClaimsSet(successResponse);
+        final String sid2 = getSidFromAuthorizeCodeClaimsSet(successResponse);
+        final String sid3 = getSidFromIDToken(successResponse);
+        Assert.assertEquals(sid1, sid2);
+        Assert.assertEquals(sid2, sid3);
+
         final JWTClaimsSet token =  SignedJWT.parse(successResponse.getAccessToken().getValue()).getJWTClaimsSet();
         Assert.assertEquals(token.getAudience(), List.of(resource, issuer));
         Assert.assertNotNull(token.getStringClaim("eduPersonScopedAffiliation"));
@@ -576,6 +629,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNull(successResponse.getIDToken());
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertNotNull(getSidFromAuthorizeCodeClaimsSet(successResponse));
     }
 
     @Test
@@ -622,6 +676,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNull(successResponse.getIDToken());
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertNotNull(getSidFromAuthorizeCodeClaimsSet(successResponse));
     }
 
     @Test
@@ -668,6 +723,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNull(successResponse.getIDToken());
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertNotNull(getSidFromAuthorizeCodeClaimsSet(successResponse));
     }
 
     @Test
@@ -721,6 +777,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNull(successResponse.getIDToken());
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertNotNull(getSidFromAuthorizeCodeClaimsSet(successResponse));
         Assert.assertNull(successResponse.getIssuer());
 
         final AuthorizeCodeClaimsSet code = 
@@ -751,6 +808,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNull(successResponse.getIDToken());
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertNotNull(getSidFromAuthorizeCodeClaimsSet(successResponse));
         Assert.assertNull(successResponse.getIssuer());
 
         final AuthorizeCodeClaimsSet code = 
@@ -781,6 +839,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNull(successResponse.getIDToken());
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertNotNull(getSidFromAuthorizeCodeClaimsSet(successResponse));
         Assert.assertNull(successResponse.getIssuer());
 
         final AuthorizeCodeClaimsSet code = 
@@ -806,6 +865,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNull(successResponse.getIDToken());
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertNotNull(getSidFromAuthorizeCodeClaimsSet(successResponse));
         Assert.assertNull(successResponse.getIssuer());
     }
     
@@ -857,6 +917,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNull(successResponse.getIDToken());
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertNotNull(getSidFromAuthorizeCodeClaimsSet(successResponse));
         Assert.assertNull(successResponse.getIssuer());
     }
 
@@ -905,6 +966,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNull(successResponse.getIDToken());
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertNotNull(getSidFromAuthorizeCodeClaimsSet(successResponse));
         Assert.assertNull(successResponse.getIssuer());
 
         final AuthorizeCodeClaimsSet code = 
@@ -987,6 +1049,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNull(successResponse.getIDToken());
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertNotNull(getSidFromAuthorizeCodeClaimsSet(successResponse));
         Assert.assertNull(successResponse.getIssuer());
     }
 
@@ -1034,6 +1097,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNull(successResponse.getIDToken());
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertNotNull(getSidFromAuthorizeCodeClaimsSet(successResponse));
         Assert.assertNull(successResponse.getIssuer());
 
         final AuthorizeCodeClaimsSet code = 
@@ -1085,6 +1149,52 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertEquals(errorResponse.getIssuer().getValue(), issuer);
     }
 
+    protected String getSidFromAuthorizeCodeClaimsSet(final AuthenticationSuccessResponse successResponse) {
+        Assert.assertNotNull(successResponse.getAuthorizationCode());
+        final AuthorizeCodeClaimsSet claims;
+        try {
+            claims = AuthorizeCodeClaimsSet.parse(successResponse.getAuthorizationCode().getValue(), getDataSealer());
+            Assert.assertNotNull(claims.getSessionIdentifier());
+            return claims.getSessionIdentifier();
+        } catch (ParseException | DataSealerException e) {
+            return null;
+        }
+    }
+
+    protected String getSidFromOpaqueAccessTokenClaimsSet(final AuthenticationSuccessResponse successResponse) {
+        Assert.assertNotNull(successResponse.getAccessToken());
+        final AccessTokenClaimsSet claims;
+        try {
+            claims = AccessTokenClaimsSet.parse(successResponse.getAccessToken().getValue(), getDataSealer());
+            Assert.assertNotNull(claims.getSessionIdentifier());
+            return claims.getSessionIdentifier();
+        } catch (ParseException | DataSealerException e) {
+            return null;
+        }
+    }
+
+    protected String getSidFromJWTAccessTokenClaimsSet(final AuthenticationSuccessResponse successResponse) {
+        Assert.assertNotNull(successResponse.getAccessToken());
+        try {
+            final JWT jwt = SignedJWT.parse(successResponse.getAccessToken().getValue());
+            final JWTClaimsSet claims = jwt.getJWTClaimsSet();
+            Assert.assertNotNull(claims.getStringClaim("sid"));
+            return claims.getStringClaim("sid");
+        } catch (ParseException e) {
+            return null;
+        }
+    }
+
+    protected String getSidFromIDToken(final AuthenticationSuccessResponse successResponse) {
+        final JWT idToken = successResponse.getIDToken();
+        Assert.assertNotNull(idToken);
+        try {
+            return idToken.getJWTClaimsSet().getStringClaim("sid");
+        } catch (ParseException e) {
+            return null;
+        }
+    }
+
     @AfterMethod
     public void removeMetadata() throws IOException {
         removeMetadata(storageService, clientId);
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java
index 8e53ef2e..277a9c30 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java
@@ -18,6 +18,7 @@
 package net.shibboleth.idp.plugin.oidc.op.profile.flow;
 
 import java.io.IOException;
+import java.net.URI;
 import java.text.ParseException;
 import java.time.Duration;
 import java.time.Instant;
@@ -45,6 +46,7 @@ import com.nimbusds.oauth2.sdk.Scope;
 import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
 import com.nimbusds.oauth2.sdk.auth.ClientSecretJWT;
 import com.nimbusds.oauth2.sdk.auth.JWTAuthentication;
+import com.nimbusds.oauth2.sdk.id.ClientID;
 import com.nimbusds.oauth2.sdk.pkce.CodeChallenge;
 import com.nimbusds.oauth2.sdk.pkce.CodeChallengeMethod;
 import com.nimbusds.oauth2.sdk.pkce.CodeVerifier;
@@ -58,10 +60,12 @@ import net.minidev.json.JSONObject;
 import net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateGrantTest;
 import net.shibboleth.idp.plugin.oidc.op.storage.RevocationCacheContexts;
 import net.shibboleth.idp.plugin.oidc.op.token.support.AccessTokenClaimsSet;
+import net.shibboleth.idp.plugin.oidc.op.token.support.AuthorizeCodeClaimsSet;
 import net.shibboleth.idp.plugin.oidc.op.token.support.RefreshTokenClaimsSet;
 import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
 import net.shibboleth.utilities.java.support.collection.Pair;
 import net.shibboleth.utilities.java.support.security.DataSealerException;
+import net.shibboleth.utilities.java.support.security.impl.SecureRandomIdentifierGenerationStrategy;
 
 /**
  * Unit tests for the token flow.
@@ -191,8 +195,26 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         Assert.assertNotNull(response.getTokens().getAccessToken());
         Assert.assertNotNull(response.getTokens().getRefreshToken());
         Assert.assertNotNull(response.getOIDCTokens().getIDToken());
+        Assert.assertNull(getSidFromAccessToken(response.getTokens().getAccessToken()));
+        Assert.assertNull(getSidFromRefreshToken(response.getTokens().getRefreshToken()));
+        Assert.assertNull(getSidFromJWT(response.getOIDCTokens().getIDToken()));
     }
 
+    @Test
+    public void testValidGrantWithSid() throws Exception {
+        final String sid = idGenerator.generateIdentifier();
+        initializeGrantAndRequest(clientId, createRequestParameters(redirectUri, "authorization_code",
+                buildAuthorizationCodeWithSid(clientId, sid), clientId));
+        storeConsent(storageService, "jdoe", clientId, "mail");
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        final OIDCTokenResponse response = parseSuccessResponse(result, OIDCTokenResponse.class);
+        Assert.assertNotNull(response.getTokens().getAccessToken());
+        Assert.assertNotNull(response.getTokens().getRefreshToken());
+        Assert.assertNotNull(response.getOIDCTokens().getIDToken());
+        Assert.assertEquals(getSidFromAccessToken(response.getTokens().getAccessToken()), sid);
+        Assert.assertEquals(getSidFromRefreshToken(response.getTokens().getRefreshToken()), sid);
+        Assert.assertEquals(getSidFromJWT(response.getOIDCTokens().getIDToken()), sid);
+    }
     @Test
     public void testValidGrantNonMatchingRedirectURI() throws Exception {
         initializeGrantAndRequest(clientId, createRequestParameters(redirectUri + "wrong", "authorization_code",
@@ -345,6 +367,8 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         final AccessToken accessToken = response.getTokens().getAccessToken();
         Assert.assertNotNull(accessToken);
         Assert.assertNotNull(response.getOIDCTokens().getIDToken());
+        Assert.assertNull(getSidFromAccessToken(accessToken));
+        Assert.assertNull(getSidFromJWT(response.getOIDCTokens().getIDToken()));
         validateConsentFromAccessToken(response, false);
     }
     
@@ -357,6 +381,8 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         final AccessToken accessToken = response.getTokens().getAccessToken();
         Assert.assertNotNull(accessToken);
         Assert.assertNotNull(response.getOIDCTokens().getIDToken());
+        Assert.assertNull(getSidFromAccessToken(accessToken));
+        Assert.assertNull(getSidFromJWT(response.getOIDCTokens().getIDToken()));
         validateConsentFromAccessToken(response, true);
     }
     
@@ -387,7 +413,11 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
     protected String buildAuthorizationCode(final String clientId) throws Exception {
         return buildAuthorizationCode(clientId, null);
     }
-    
+
+    protected String buildAuthorizationCodeWithSid(final String clientId, final String sid) throws Exception {
+        return buildAuthorizationCodeWithSid(clientId, null, null, null, null, sid);
+    }
+
     protected String buildAuthorizationCode(final String clientId, final String verifier) throws Exception {
         return buildAuthorizationCode(clientId, verifier, null, null, null);
     }
@@ -399,7 +429,36 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
                 redirectUri, verifier, deliveryClaims, deliveryClaimsIDToken, deliveryClaimsUserInfo, 
                 "openid profile email offline_access").toString();
     }
-    
+
+    protected String buildAuthorizationCodeWithSid(final String clientId, final String verifier,
+            final JSONObject deliveryClaims, final JSONObject deliveryClaimsIDToken,
+            final JSONObject deliveryClaimsUserInfo, final String sid) throws Exception {
+        final AuthorizeCodeClaimsSet.Builder builder = new AuthorizeCodeClaimsSet.Builder();
+        builder.setJWTID(new SecureRandomIdentifierGenerationStrategy())
+            .setClientID(new ClientID(clientId))
+            .setIssuer("https://op.example.org")
+            .setPrincipal("jdoe")
+            .setSubject("mock")
+            .setIssuedAt(Instant.now())
+            .setExpiresAt(Instant.now().plusSeconds(100))
+            .setAuthenticationTime(Instant.now())
+            .setRedirectURI(new URI(redirectUri))
+            .setScope(scope)
+            .setSessionIdentifier(sid);
+
+        if (deliveryClaims != null) {
+            builder.setDlClaims(new ClaimsSet(deliveryClaims));
+        }
+        if (deliveryClaimsIDToken != null) {
+            builder.setDlClaimsID(new ClaimsSet(deliveryClaimsIDToken));
+        }
+        if (deliveryClaimsUserInfo != null) {
+            builder.setDlClaimsUI(new ClaimsSet(deliveryClaimsUserInfo));
+        }
+
+        return builder.build().serialize(getDataSealer());
+    }
+
     protected String buildLegacyAuthorizationCode(final String clientId, final String... consentedClaims)
             throws Exception {
         final String json = buildJsonForLegacyToken("jdoe", clientId, Scope.parse("openid email"), "ac",
@@ -426,6 +485,19 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         return new RefreshToken(rtClaims.serialize(new ValidateGrantTest().getDataSealer())).getValue();
     }
 
+    protected String buildRefreshTokenWithSid(final String clientId, final String id, final String rootId,
+            final Collection<String> aud, final String sid, final String... consentedClaims) throws Exception {
+        final TokenClaimsSet acClaims = ValidateGrantTest.buildTokenClaimsSet(clientId, "https://op.example.org", "jdoe", "mock", redirectUri,
+                null, null, null, null, scope.toString(), aud);
+        final RefreshTokenClaimsSet rtClaims = new RefreshTokenClaimsSet.Builder(acClaims, Instant.now(),
+                Instant.now().plus(Duration.ofHours(1)))
+                .setRootTokenIdentifier(rootId)
+                .setJWTID(id)
+                .setSessionIdentifier(sid)
+                .build();
+        return new RefreshToken(rtClaims.serialize(new ValidateGrantTest().getDataSealer())).getValue();
+    }
+
     @Test
     public void testValidSecretJWT() throws Exception {
         final ClientSecretJWT clientAuth = buildSecretJwtAuth(clientSecret);
@@ -654,6 +726,27 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         final AccessToken accessToken = response.getTokens().getAccessToken();
         Assert.assertNotNull(accessToken);
         Assert.assertNotNull(response.getOIDCTokens().getIDToken());
+        Assert.assertNull(getSidFromAccessToken(accessToken));
+        Assert.assertNull(getSidFromJWT(response.getOIDCTokens().getIDToken()));
+        validateConsentFromAccessToken(response, false);
+        Assert.assertFalse(revocationCache.isRevoked(RevocationCacheContexts.SINGLE_ACCESS_OR_REFRESH_TOKENS, id));
+        Assert.assertFalse(revocationCache.isRevoked(RevocationCacheContexts.AUTHORIZATION_CODE, rootId));
+    }
+
+    @Test
+    public void testValidRefreshTokenGrantWithSid() throws Exception {
+        final String id = idGenerator.generateIdentifier();
+        final String rootId = idGenerator.generateIdentifier();
+        final String sid = idGenerator.generateIdentifier();
+        initializeGrantAndRequest(clientId, createRequestParameters(redirectUri, "refresh_token",
+                buildRefreshTokenWithSid(clientId, id, rootId, null, sid), clientId));
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        final OIDCTokenResponse response = parseSuccessResponse(result, OIDCTokenResponse.class);
+        final AccessToken accessToken = response.getTokens().getAccessToken();
+        Assert.assertNotNull(accessToken);
+        Assert.assertNotNull(response.getOIDCTokens().getIDToken());
+        Assert.assertEquals(getSidFromAccessToken(accessToken), sid);
+        Assert.assertEquals(getSidFromJWT(response.getOIDCTokens().getIDToken()), sid);
         validateConsentFromAccessToken(response, false);
         Assert.assertFalse(revocationCache.isRevoked(RevocationCacheContexts.SINGLE_ACCESS_OR_REFRESH_TOKENS, id));
         Assert.assertFalse(revocationCache.isRevoked(RevocationCacheContexts.AUTHORIZATION_CODE, rootId));
@@ -825,4 +918,35 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         Assert.assertNotNull(response.getOIDCTokens().getIDToken());
     }
 
+    protected String getSidFromAccessToken(final AccessToken accessToken) {
+        Assert.assertNotNull(accessToken.getValue());
+        final AccessTokenClaimsSet claims;
+        try {
+            claims = AccessTokenClaimsSet.parse(accessToken.getValue(), getDataSealer());
+            return claims.getSessionIdentifier();
+        } catch (ParseException | DataSealerException e) {
+            return null;
+        }
+    }
+
+    protected String getSidFromRefreshToken(final RefreshToken refreshToken) {
+        Assert.assertNotNull(refreshToken.getValue());
+        final RefreshTokenClaimsSet claims;
+        try {
+            claims = RefreshTokenClaimsSet.parse(refreshToken.getValue(), getDataSealer());
+            return claims.getSessionIdentifier();
+        } catch (ParseException | DataSealerException e) {
+            return null;
+        }
+    }
+
+    protected String getSidFromJWT(final JWT jwt) {
+        Assert.assertNotNull(jwt);
+        try {
+            return jwt.getJWTClaimsSet().getStringClaim("sid");
+        } catch (ParseException e) {
+            return null;
+        }
+    }
+
 }
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddIDTokenShellTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddIDTokenShellTest.java
index 283399e5..91129692 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddIDTokenShellTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddIDTokenShellTest.java
@@ -26,6 +26,7 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import com.nimbusds.oauth2.sdk.id.Audience;
+import com.nimbusds.openid.connect.sdk.claims.SessionID;
 
 /** {@link AddIDTokenShell} unit test. */
 public class AddIDTokenShellTest extends BaseOIDCResponseActionTest {
@@ -52,6 +53,22 @@ public class AddIDTokenShellTest extends BaseOIDCResponseActionTest {
         ActionTestingSupport.assertProceedEvent(event);
         Assert.assertTrue(respCtx.getIDToken().getAudience().contains(new Audience(request.getClientID())));
         Assert.assertEquals(respCtx.getIDToken().getSubject().getValue(), subject);
+        Assert.assertNull(respCtx.getIDToken().getSessionID());
+    }
 
+    /**
+     * Test that id token shell is generate with sid.
+     * 
+     * @throws ComponentInitializationException
+     */
+    @Test
+    public void testSuccessWithSid() throws ComponentInitializationException {
+        respCtx.setSessionId("mockSid");
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertProceedEvent(event);
+        Assert.assertTrue(respCtx.getIDToken().getAudience().contains(new Audience(request.getClientID())));
+        Assert.assertEquals(respCtx.getIDToken().getSubject().getValue(), subject);
+        Assert.assertEquals(respCtx.getIDToken().getSessionID(), new SessionID("mockSid"));
     }
+
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetAuthorizationCodeToResponseContextTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetAuthorizationCodeToResponseContextTest.java
index a206fe4b..3441dad6 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetAuthorizationCodeToResponseContextTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetAuthorizationCodeToResponseContextTest.java
@@ -91,6 +91,30 @@ public class SetAuthorizationCodeToResponseContextTest extends BaseOIDCResponseA
         final AuthorizeCodeClaimsSet ac =
                 AuthorizeCodeClaimsSet.parse(respCtx.getAuthorizationCode().getValue(), getDataSealer());
         Assert.assertNotNull(ac);
+        Assert.assertNull(ac.getSessionIdentifier());
+    }
+
+    /**
+     * Basic success case with session identifier.
+     * 
+     * @throws ComponentInitializationException 
+     * @throws NoSuchAlgorithmException 
+     * @throws URISyntaxException 
+     * @throws ParseException 
+     * @throws DataSealerException 
+     */
+    @Test
+    public void testSuccessWithSid() throws ComponentInitializationException, NoSuchAlgorithmException,
+            URISyntaxException, ParseException, DataSealerException {
+        init();
+        respCtx.setSessionId("mockSid");
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertProceedEvent(event);
+        Assert.assertNotNull(respCtx.getAuthorizationCode());
+        final AuthorizeCodeClaimsSet ac =
+                AuthorizeCodeClaimsSet.parse(respCtx.getAuthorizationCode().getValue(), getDataSealer());
+        Assert.assertNotNull(ac);
+        Assert.assertEquals(ac.getSessionIdentifier(), "mockSid");
     }
 
     @Test
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetSessionIdToResponseContextTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetSessionIdToResponseContextTest.java
new file mode 100644
index 00000000..525138fe
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetSessionIdToResponseContextTest.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.profile.impl;
+
+import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.TokenRequestSessionIdLookupFunction;
+import net.shibboleth.idp.plugin.oidc.op.token.support.AccessTokenClaimsSet;
+import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
+import net.shibboleth.idp.profile.testing.ActionTestingSupport;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.UnmodifiableComponentException;
+import net.shibboleth.utilities.java.support.logic.ConstraintViolationException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.NoSuchAlgorithmException;
+import java.time.Instant;
+import java.util.function.Function;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.webflow.execution.Event;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import com.nimbusds.oauth2.sdk.Scope;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+
+/** {@link SetSessionIdToResponseContext} unit test. */
+public class SetSessionIdToResponseContextTest extends BaseOIDCResponseActionTest {
+
+    /** Action. */
+    private SetSessionIdToResponseContext action;
+
+    private void init() throws ComponentInitializationException {
+        init(new TokenRequestSessionIdLookupFunction());
+    }
+    
+    private void init(final Function<ProfileRequestContext, String> strategy)
+            throws ComponentInitializationException {
+        action = new SetSessionIdToResponseContext();
+        action.setSessionIdLookupStrategy(strategy);
+        action.initialize();
+    }
+
+    /**
+     * Test that action throws error when null strategy is set.
+     */
+    @Test(expectedExceptions = ConstraintViolationException.class)
+    public void testNoStrategy() {
+        action = new SetSessionIdToResponseContext();
+        action.setSessionIdLookupStrategy(null);
+    }
+
+    /**
+     * Test that action throws error when strategy is being set when already initialized.
+     * 
+     * @throws NoSuchAlgorithmException 
+     * @throws ComponentInitializationException 
+     */
+    @Test(expectedExceptions = UnmodifiableComponentException.class)
+    public void testInitialized() throws NoSuchAlgorithmException, ComponentInitializationException {
+        init();
+        action.setSessionIdLookupStrategy(new TokenRequestSessionIdLookupFunction());
+    }
+
+    /**
+     * Test that action copies session id to response ctx.
+     * 
+     * @throws ComponentInitializationException 
+     * @throws URISyntaxException 
+     */
+    @Test
+    public void testSuccessWithoutValue() throws ComponentInitializationException, URISyntaxException {
+        init();
+        final TokenClaimsSet claims = new AccessTokenClaimsSet.Builder()
+                .setJWTID(idGenerator)
+                .setClientID(new ClientID())
+                .setIssuer("issuer")
+                .setPrincipal("userPrin")
+                .setSubject("subject")
+                .setIssuedAt(Instant.now())
+                .setExpiresAt(Instant.now())
+                .setAuthenticationTime(Instant.now())
+                .setRedirectURI(new URI("http://example.com"))
+                .setScope(new Scope())
+                .build();
+        respCtx.setAuthorizationGrantClaimsSet(claims);
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertProceedEvent(event);
+        Assert.assertNull(respCtx.getSessionId());
+    }
+
+    /**
+     * Test that action copies session id to response ctx.
+     * 
+     * @throws ComponentInitializationException 
+     * @throws URISyntaxException 
+     */
+    @Test
+    public void testSuccessWithValue() throws ComponentInitializationException, URISyntaxException {
+        init();
+        final TokenClaimsSet claims = new AccessTokenClaimsSet.Builder()
+                .setJWTID(idGenerator)
+                .setClientID(new ClientID())
+                .setIssuer("issuer")
+                .setPrincipal("userPrin")
+                .setSubject("subject")
+                .setIssuedAt(Instant.now())
+                .setExpiresAt(Instant.now())
+                .setAuthenticationTime(Instant.now())
+                .setRedirectURI(new URI("http://example.com"))
+                .setScope(new Scope())
+                .setSessionIdentifier("mockSid")
+                .build();
+        respCtx.setAuthorizationGrantClaimsSet(claims);
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertProceedEvent(event);
+        Assert.assertEquals(respCtx.getSessionId(), "mockSid");
+    }
+
+}
\ No newline at end of file

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


More information about the commits mailing list