[java-plugin-shibd-oidc] 01/09: JSHIBDOIDC-22 - Store off iss, sid in opaque session data to support logout
Codeberg
noreply at shibboleth.net
Mon Jul 13 16:24:16 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch dev/JSHIBDOIDC-28
in repository java-plugin-shibd-oidc.
View the commit online:
https://codeberg.org/Shibboleth/java-plugin-shibd-oidc/commit/0009de6119f723540033d458bc69f47116d86f39
commit 0009de6119f723540033d458bc69f47116d86f39
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Wed Jun 17 14:46:06 2026 +0100
JSHIBDOIDC-22 - Store off iss, sid in opaque session data to support
logout
- Store off the full id_token b64 compact serialised, which can be
later used to extract everything required for logout
https://shibboleth.atlassian.net/browse/JSHIBDOIDC-22
---
.../sp/oidc/profile/impl/PrepareAgentResponse.java | 77 ++++++++++++++--------
.../profile/impl/PrepareAgentResponseTest.java | 9 +++
2 files changed, 60 insertions(+), 26 deletions(-)
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 e19561d..1d65ffd 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
@@ -26,12 +26,14 @@ import org.opensaml.profile.context.ProfileRequestContext;
import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
import org.slf4j.Logger;
+import com.nimbusds.jwt.JWT;
import com.nimbusds.oauth2.sdk.token.AccessToken;
import com.nimbusds.oauth2.sdk.token.RefreshToken;
import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
import net.shibboleth.oidc.profile.context.AccessTokenResponseContext;
import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
import net.shibboleth.shared.logic.Constraint;
import net.shibboleth.shared.primitive.LoggerFactory;
import net.shibboleth.shared.security.DataSealer;
@@ -128,44 +130,36 @@ public class PrepareAgentResponse extends AbstractTokenConsumerResponseAction {
if (tokenResponse != null) {
final AccessToken accessToken = tokenResponse.getTokens().getAccessToken();
final RefreshToken refreshToken = tokenResponse.getTokens().getRefreshToken();
+ final JWT idToken = tokenResponse.getOIDCTokens().getIDToken();
- log.debug("{} Storing access and refresh tokens in session data", getLogPrefix());
+ log.debug("{} Storing id_token, access and refresh tokens in session data", getLogPrefix());
final DDF tokens = new DDF("tokens").structure();
- final DataSealer localDataSealer = dataSealer;
-
// Add the refresh token if it exists
if (refreshToken != null && refreshToken.getValue() != null) {
- if (localDataSealer != null) {
- try {
- final String refreshTokenString = refreshToken.getValue();
- assert refreshTokenString != null;
- tokens.addmember("refresh_token").string(localDataSealer.wrap(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.
- }
- } else {
- tokens.addmember("refresh_token").string(refreshToken.getValue());
+ try {
+ final String refreshTokenString = refreshToken.getValue();
+ assert refreshTokenString != null;
+ tokens.addmember("refresh_token").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.
}
+
}
boolean accessTokenAdded = false;
if (accessToken != null && accessToken.getValue() != null) {
- if (localDataSealer != null) {
- try {
- final String accessTokenString = accessToken.getValue();
- assert accessTokenString != null;
- tokens.addmember("access_token").string(localDataSealer.wrap(accessTokenString));
- accessTokenAdded = true;
- } catch (final DataSealerException e) {
- log.debug("{} Error sealing access token for session data", getLogPrefix(), e);
- // Is not an error, but the access token and its attributes won't be stored.
- }
- } else {
- tokens.addmember("access_token").string(accessToken.getValue());
+ try {
+ final String accessTokenString = accessToken.getValue();
+ assert accessTokenString != null;
+ tokens.addmember("access_token").string(conditionallySeal(accessTokenString));
accessTokenAdded = true;
+ } catch (final DataSealerException e) {
+ log.debug("{} Error sealing access token for session data", getLogPrefix(), e);
+ // Is not an error, but the access token and its attributes won't be stored.
}
+
}
// Only add the attributes of an access_token if one exists
@@ -190,9 +184,40 @@ public class PrepareAgentResponse extends AbstractTokenConsumerResponseAction {
}
}
+ if (idToken != null) {
+ final String serialisedIdToken = idToken.serialize();
+ if (serialisedIdToken != null) {
+ try {
+ tokens.addmember("id_token").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
+ }
+ }
+ }
+
return tokens;
}
return null;
}
+
+ /**
+ * If the data sealer is configured, use it to seal the token value. If configured to, failure to seal a token
+ * results in an exception being thrown. If a sealer is not configured, the token is passed back untouched.
+ *
+ * @param token the token to seal or pass through
+ * @return the sealed token, or the original token passed-through
+ */
+ @Nonnull private String conditionallySeal(@Nonnull @NotEmpty final String token) throws DataSealerException {
+
+ final DataSealer localDataSealer = dataSealer;
+ if (localDataSealer != null) {
+ return localDataSealer.wrap(token);
+
+ } else {
+ log.warn("{} DataSealer not configured, storing tokens in plaintext", getLogPrefix());
+ }
+ return token;
+ }
}
diff --git a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/PrepareAgentResponseTest.java b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/PrepareAgentResponseTest.java
index abac2f4..e81506b 100644
--- a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/PrepareAgentResponseTest.java
+++ b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/PrepareAgentResponseTest.java
@@ -112,6 +112,8 @@ public class PrepareAgentResponseTest extends BaseApplicationActionTest{
final DDF accessTokenDdf = tokens.getmember("access_token");
assertFalse(accessTokenDdf.isnull());
assertEquals(accessTokenDdf.string(),"access123");
+ final DDF idTokenDdf = tokens.getmember("id_token");
+ assertFalse(idTokenDdf.isnull());
}
@Test
@@ -119,6 +121,8 @@ public class PrepareAgentResponseTest extends BaseApplicationActionTest{
final DataSealer sealer = Mockito.mock(DataSealer.class);
Mockito.when(sealer.wrap("access123")).thenReturn("sealedAccess");
Mockito.when(sealer.wrap("refresh123")).thenReturn("sealedRefresh");
+ // This is the b64 compact JSON serialisation of the very basic JWT used in the response.
+ Mockito.when(sealer.wrap("eyJhbGciOiJub25lIn0.e30.")).thenReturn("sealedIdToken");
action.setDataSealer(sealer);
action.initialize();
@@ -152,6 +156,9 @@ public class PrepareAgentResponseTest extends BaseApplicationActionTest{
final DDF accessTokenDdf = tokens.getmember("access_token");
assertFalse(accessTokenDdf.isnull());
assertEquals(accessTokenDdf.string(),"sealedAccess");
+ final DDF idTokenDdf = tokens.getmember("id_token");
+ assertFalse(idTokenDdf.isnull());
+ assertEquals(idTokenDdf.string(),"sealedIdToken");
}
@SuppressWarnings("null")
@@ -192,6 +199,8 @@ public class PrepareAgentResponseTest extends BaseApplicationActionTest{
assertTrue(refreshTokenDdf.isempty());
final DDF accessTokenDdf = tokens.getmember("access_token");
assertTrue(accessTokenDdf.isempty());
+ final DDF idTokenDdf = tokens.getmember("id_token");
+ assertTrue(idTokenDdf.isempty());
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list