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