Include audience in refresh_token

Florian Ritterhoff florian.ritterhoff at hm.edu
Sat Aug 13 05:27:15 UTC 2022


Hello,

with reference to the latest discussion on the users mailing list 
regarding the OIDC/OAuth2 resource & audience topic, I debugged the code 
a little and checked the rfc8707. If I understand the RFC correctly, by 
using the refresh_token it should be possible to get a new access_token 
with a specific audience. In my opinion, the previously used audience of 
the authorization_code has to be encoded in the refresh_token for this.

 From my point of view, the code was missing the inclusion of the 
granted audience so reusing the audience was impossible?

I created a little patch and a little test for this use case and would 
like to submit it.

Thanks

Florian Ritterhoff

-- 
Florian Ritterhoff - Fakultät für Informatik und Mathematik
Hochschule München University of Applied Sciences
Lothstr. 64, 80335 München
-------------- next part --------------
From 01d868af1122eeb9d57f9e124e1205b2e0be011f Mon Sep 17 00:00:00 2001
From: Florian Ritterhoff <florian.ritterhoff at hm.edu>
Date: Sat, 13 Aug 2022 07:25:00 +0200
Subject: [PATCH] Include audience in refresh_token

---
 .../token/support/RefreshTokenClaimsSet.java  |  1 +
 .../oidc/op/profile/flow/TokenFlowTest.java   | 33 +++++++++++++++----
 .../op/profile/impl/ValidateGrantTest.java    | 14 ++++++--
 3 files changed, 38 insertions(+), 10 deletions(-)

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 682363e9..b060659b 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
@@ -140,6 +140,7 @@ public final class RefreshTokenClaimsSet extends TokenClaimsSet {
             setNonce(existing.getNonce());
             setRedirectURI(existing.getRedirectURI());
             setScope(existing.getScope());
+            setAudience(existing.getAudience());
             setClaimsRequest(existing.getClaimsRequest());
             setDlClaims(existing.getDeliveryClaims());
             setDlClaimsUI(existing.getUserinfoDeliveryClaims());
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 ec57697f..a2f42e2b 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
@@ -21,7 +21,9 @@ import java.io.IOException;
 import java.text.ParseException;
 import java.time.Duration;
 import java.time.Instant;
+import java.util.Collection;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.opensaml.storage.RevocationCache;
@@ -412,10 +414,10 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         return new RefreshToken(getDataSealer().wrap(json, Instant.now().plusSeconds(30))).getValue();
     }
 
-    protected String buildRefreshToken(final String clientId, final String id, final String rootId,
+    protected String buildRefreshToken(final String clientId, final String id, final String rootId, final Collection<String> aud,
             final String... consentedClaims) throws Exception {
         final TokenClaimsSet acClaims = ValidateGrantTest.buildTokenClaimsSet(clientId, "https://op.example.org", "jdoe", "mock", redirectUri,
-                null, null, null, null, scope.toString());
+                null, null, null, null, scope.toString(), aud);
         final RefreshTokenClaimsSet rtClaims = new RefreshTokenClaimsSet.Builder(acClaims, Instant.now(),
                 Instant.now().plus(Duration.ofHours(1)))
                 .setRootTokenIdentifier(rootId)
@@ -646,7 +648,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         final String id = idGenerator.generateIdentifier();
         final String rootId = idGenerator.generateIdentifier();
         initializeGrantAndRequest(clientId, createRequestParameters(redirectUri, "refresh_token",
-                buildRefreshToken(clientId, id, rootId), clientId));
+                buildRefreshToken(clientId, id, rootId, null), clientId));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         final OIDCTokenResponse response = parseSuccessResponse(result, OIDCTokenResponse.class);
         final AccessToken accessToken = response.getTokens().getAccessToken();
@@ -657,12 +659,29 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         Assert.assertFalse(revocationCache.isRevoked(RevocationCacheContexts.AUTHORIZATION_CODE, rootId));
     }
 
+    @Test
+    public void testValidRefreshTokenGrantWithResource() throws Exception {
+        final String id = idGenerator.generateIdentifier();
+        final String rootId = idGenerator.generateIdentifier();
+        initializeGrantAndRequest(clientId, createRequestParameters(redirectUri, "refresh_token",
+                buildRefreshToken(clientId, id, rootId, List.of("https://rp.example.org")), 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.assertTrue(unwrapAccessToken(response).getClaimsSet().getAudience().contains("https://rp.example.org"));
+        Assert.assertNotNull(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 testValidRefreshTokenGrantRefreshTokenRotation() throws Exception {
         final String id = idGenerator.generateIdentifier();
         final String rootId = idGenerator.generateIdentifier();
         initializeGrantAndRequest(clientIdRefreshTokenRotation, createRequestParameters(redirectUri, "refresh_token",
-                buildRefreshToken(clientIdRefreshTokenRotation, id, rootId), clientIdRefreshTokenRotation));
+                buildRefreshToken(clientIdRefreshTokenRotation, id, rootId, null), clientIdRefreshTokenRotation));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         final OIDCTokenResponse response = parseSuccessResponse(result, OIDCTokenResponse.class);
         final AccessToken accessToken = response.getTokens().getAccessToken();
@@ -679,7 +698,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         final String rootId = idGenerator.generateIdentifier();
         Assert.assertTrue(revocationCache.revoke(RevocationCacheContexts.SINGLE_ACCESS_OR_REFRESH_TOKENS, id));
         initializeGrantAndRequest(clientIdRefreshTokenRotation, createRequestParameters(redirectUri, "refresh_token",
-                buildRefreshToken(clientIdRefreshTokenRotation, id, rootId), clientIdRefreshTokenRotation));
+                buildRefreshToken(clientIdRefreshTokenRotation, id, rootId, null), clientIdRefreshTokenRotation));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         assertErrorCode(result, OAuth2Error.INVALID_GRANT_CODE);
         Assert.assertTrue(revocationCache.isRevoked(RevocationCacheContexts.SINGLE_ACCESS_OR_REFRESH_TOKENS, id));
@@ -692,7 +711,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         final String rootId = idGenerator.generateIdentifier();
         Assert.assertTrue(revocationCache.revoke(RevocationCacheContexts.AUTHORIZATION_CODE, rootId));
         initializeGrantAndRequest(clientIdRefreshTokenRotation, createRequestParameters(redirectUri, "refresh_token",
-                buildRefreshToken(clientIdRefreshTokenRotation, id, rootId), clientIdRefreshTokenRotation));
+                buildRefreshToken(clientIdRefreshTokenRotation, id, rootId, null), clientIdRefreshTokenRotation));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         assertErrorCode(result, OAuth2Error.INVALID_GRANT_CODE);
         Assert.assertTrue(revocationCache.isRevoked(RevocationCacheContexts.AUTHORIZATION_CODE, rootId));
@@ -703,7 +722,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         final String id = idGenerator.generateIdentifier();
         Assert.assertTrue(revocationCache.revoke(RevocationCacheContexts.AUTHORIZATION_CODE, id));
         initializeGrantAndRequest(clientIdRefreshTokenRotation, createRequestParameters(redirectUri, "refresh_token",
-                buildRefreshToken(clientIdRefreshTokenRotation, id, null), clientIdRefreshTokenRotation));
+                buildRefreshToken(clientIdRefreshTokenRotation, id, null, null), clientIdRefreshTokenRotation));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         assertErrorCode(result, OAuth2Error.INVALID_GRANT_CODE);
         Assert.assertTrue(revocationCache.isRevoked(RevocationCacheContexts.AUTHORIZATION_CODE, id));
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrantTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrantTest.java
index 76e91a6e..34110234 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrantTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrantTest.java
@@ -34,6 +34,7 @@ import java.net.URI;
 import java.security.NoSuchAlgorithmException;
 import java.time.Duration;
 import java.time.Instant;
+import java.util.Collection;
 import java.util.function.Function;
 
 import org.mockito.Mockito;
@@ -158,15 +159,21 @@ public class ValidateGrantTest extends BaseOIDCResponseActionTest {
             final String userPrincipal, final String sub, final String callbackUrl, final String codeChallenge,
             final JSONObject deliveryClaims, final JSONObject deliveryClaimsIDToken,
             final JSONObject deliveryClaimsUserInfo, final String scope) throws Exception {
+        return buildAuthorizationCode(clientId, issuer, userPrincipal, sub, callbackUrl,
+                codeChallenge, deliveryClaims, deliveryClaimsIDToken, deliveryClaimsUserInfo, scope, null);
+    }
+    static public AuthorizationCode buildAuthorizationCode(final String clientId, final String issuer,
+                                                           final String userPrincipal, final String sub, final String callbackUrl, final String codeChallenge,
+                                                           final JSONObject deliveryClaims, final JSONObject deliveryClaimsIDToken,
+                                                           final JSONObject deliveryClaimsUserInfo, final String scope, final Collection<String> aud) throws Exception {
         final TokenClaimsSet acClaims = buildTokenClaimsSet(clientId, issuer, userPrincipal, sub, callbackUrl,
-                codeChallenge, deliveryClaims, deliveryClaimsIDToken, deliveryClaimsUserInfo, scope);
+                codeChallenge, deliveryClaims, deliveryClaimsIDToken, deliveryClaimsUserInfo, scope, aud);
         return new AuthorizationCode(acClaims.serialize(new ValidateGrantTest().getDataSealer()));
     }
-
     static public TokenClaimsSet buildTokenClaimsSet(final String clientId, final String issuer,
             final String userPrincipal, final String sub, final String callbackUrl, final String codeChallenge,
             final JSONObject deliveryClaims, final JSONObject deliveryClaimsIDToken,
-            final JSONObject deliveryClaimsUserInfo, final String scope) throws Exception {
+            final JSONObject deliveryClaimsUserInfo, final String scope, final Collection<String> aud) throws Exception {
         final Instant now = Instant.now();
         final AuthorizeCodeClaimsSet.Builder builder = new AuthorizeCodeClaimsSet.Builder();
         builder.setJWTID(new SecureRandomIdentifierGenerationStrategy())
@@ -178,6 +185,7 @@ public class ValidateGrantTest extends BaseOIDCResponseActionTest {
             .setExpiresAt(now.plusSeconds(100))
             .setAuthenticationTime(now)
             .setRedirectURI(new URI(callbackUrl))
+            .setAudience(aud)
             .setScope(scope == null ? new Scope() : Scope.parse(scope));
 
         if (codeChallenge != null) {
-- 
2.35.0.windows.1



More information about the dev mailing list