From ee24cc9dc738713da3720bc6257380ba98b8809c Mon Sep 17 00:00:00 2001 From: YuriyZ Date: Thu, 27 Oct 2022 17:10:40 +0300 Subject: [PATCH] fix(jans-auth-server): introduced TokenExchangeService Native SSO #2518 --- .../token/ws/rs/TokenCreatorService.java | 96 ++++++++++++ .../token/ws/rs/TokenExchangeService.java | 145 ++++++++++++++++++ .../token/ws/rs/TokenInternalService.java | 46 ------ .../token/ws/rs/TokenRestWebServiceImpl.java | 135 ++-------------- 4 files changed, 258 insertions(+), 164 deletions(-) create mode 100644 jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenCreatorService.java create mode 100644 jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenExchangeService.java delete mode 100644 jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenInternalService.java diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenCreatorService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenCreatorService.java new file mode 100644 index 00000000000..aabaed31fbd --- /dev/null +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenCreatorService.java @@ -0,0 +1,96 @@ +package io.jans.as.server.token.ws.rs; + +import com.google.common.base.Strings; +import io.jans.as.common.model.common.User; +import io.jans.as.common.model.registration.Client; +import io.jans.as.common.service.AttributeService; +import io.jans.as.model.common.GrantType; +import io.jans.as.model.common.ScopeConstants; +import io.jans.as.model.configuration.AppConfiguration; +import io.jans.as.model.error.ErrorResponseFactory; +import io.jans.as.model.token.TokenErrorResponseType; +import io.jans.as.server.model.common.AbstractAuthorizationGrant; +import io.jans.as.server.model.common.AuthorizationGrant; +import io.jans.as.server.model.common.ExecutionContext; +import io.jans.as.server.model.common.RefreshToken; +import io.jans.as.server.service.external.ExternalUpdateTokenService; +import io.jans.as.server.service.external.context.ExternalUpdateTokenContext; +import jakarta.ejb.Stateless; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.apache.commons.lang.BooleanUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; + +import java.util.Arrays; + +import static org.apache.commons.lang.BooleanUtils.isTrue; + +/** + * @author Yuriy Z + */ +@Stateless +@Named +public class TokenCreatorService { + + @Inject + private Logger log; + + @Inject + private AppConfiguration appConfiguration; + + @Inject + private ExternalUpdateTokenService externalUpdateTokenService; + + @Inject + private ErrorResponseFactory errorResponseFactory; + + @Inject + private AttributeService attributeService; + + public boolean isRefreshTokenAllowed(Client client, String requestedScope, AbstractAuthorizationGrant grant) { + if (isTrue(appConfiguration.getForceOfflineAccessScopeToEnableRefreshToken()) && !grant.getScopes().contains(ScopeConstants.OFFLINE_ACCESS) && !Strings.nullToEmpty(requestedScope).contains(ScopeConstants.OFFLINE_ACCESS)) { + return false; + } + return Arrays.asList(client.getGrantTypes()).contains(GrantType.REFRESH_TOKEN); + } + + @Nullable + public RefreshToken createRefreshToken(@NotNull ExecutionContext executionContext, @NotNull String scope) { + final AuthorizationGrant grant = executionContext.getGrant(); + if (!isRefreshTokenAllowed(executionContext.getClient(), scope, grant)) { + return null; + } + + checkUser(grant); + + final ExternalUpdateTokenContext context = new ExternalUpdateTokenContext(executionContext.getHttpRequest(), grant, executionContext.getClient(), appConfiguration, attributeService); + context.setExecutionContext(executionContext); + + final int refreshTokenLifetimeInSeconds = externalUpdateTokenService.getRefreshTokenLifetimeInSeconds(context); + if (refreshTokenLifetimeInSeconds > 0) { + return grant.createRefreshToken(executionContext, refreshTokenLifetimeInSeconds); + } + return grant.createRefreshToken(executionContext); + } + + private void checkUser(AuthorizationGrant authorizationGrant) { + if (BooleanUtils.isFalse(appConfiguration.getCheckUserPresenceOnRefreshToken())) { + return; + } + + final User user = authorizationGrant.getUser(); + if (user == null || "inactive".equalsIgnoreCase(user.getStatus())) { + log.trace("The user associated with this grant is not found or otherwise with status=inactive."); + throw new WebApplicationException(error(400, TokenErrorResponseType.INVALID_GRANT, "The user associated with this grant is not found or otherwise with status=inactive.").build()); + } + } + + public Response.ResponseBuilder error(int status, TokenErrorResponseType type, String reason) { + return Response.status(status).type(MediaType.APPLICATION_JSON_TYPE).entity(errorResponseFactory.errorAsJson(type, reason)); + } +} diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenExchangeService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenExchangeService.java new file mode 100644 index 00000000000..1ae725a7923 --- /dev/null +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenExchangeService.java @@ -0,0 +1,145 @@ +package io.jans.as.server.token.ws.rs; + +import io.jans.as.common.model.common.User; +import io.jans.as.common.model.registration.Client; +import io.jans.as.common.model.session.SessionId; +import io.jans.as.common.service.AttributeService; +import io.jans.as.model.configuration.AppConfiguration; +import io.jans.as.model.token.JsonWebResponse; +import io.jans.as.server.model.audit.OAuth2AuditLog; +import io.jans.as.server.model.common.*; +import io.jans.as.server.model.token.HandleTokenFactory; +import io.jans.as.server.service.SessionIdService; +import io.jans.as.server.service.external.ExternalUpdateTokenService; +import io.jans.as.server.service.external.context.ExternalUpdateTokenContext; +import jakarta.ejb.Stateless; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.BooleanUtils; +import org.json.JSONException; +import org.json.JSONObject; +import org.slf4j.Logger; + +import java.util.List; +import java.util.function.Function; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.jans.as.model.config.Constants.OPENID; +import static io.jans.as.model.config.Constants.TOKEN_TYPE_ACCESS_TOKEN; +import static org.apache.commons.lang.BooleanUtils.isTrue; + +/** + * @author Yuriy Z + */ +@Stateless +@Named +public class TokenExchangeService { + + @Inject + private Logger log; + + @Inject + private AppConfiguration appConfiguration; + + @Inject + private SessionIdService sessionIdService; + + @Inject + private TokenRestWebServiceValidator tokenRestWebServiceValidator; + + @Inject + private AuthorizationGrantList authorizationGrantList; + + @Inject + private ExternalUpdateTokenService externalUpdateTokenService; + + @Inject + private TokenCreatorService tokenCreatorService; + + @Inject + private AttributeService attributeService; + + public String rotateDeviceSecret(SessionId sessionId, String actorToken) { + if (BooleanUtils.isFalse(appConfiguration.getRotateDeviceSecret())) { + return null; + } + + String newDeviceSecret = HandleTokenFactory.generateDeviceSecret(); + + final List deviceSecrets = sessionId.getDeviceSecrets(); + deviceSecrets.remove(actorToken); + deviceSecrets.add(newDeviceSecret); + + sessionIdService.updateSessionId(sessionId, false); + + return newDeviceSecret; + } + + public JSONObject processTokenExchange(String scope, Function idTokenPreProcessing, ExecutionContext executionContext) { + final HttpServletRequest httpRequest = executionContext.getHttpRequest(); + final Client client = executionContext.getClient(); + final OAuth2AuditLog auditLog = executionContext.getAuditLog(); + + final String audience = httpRequest.getParameter("audience"); + final String subjectToken = httpRequest.getParameter("subject_token"); + final String subjectTokenType = httpRequest.getParameter("subject_token_type"); + final String actorToken = httpRequest.getParameter("actor_token"); + final String actorTokenType = httpRequest.getParameter("actor_token_type"); + + tokenRestWebServiceValidator.validateAudience(audience, auditLog); + tokenRestWebServiceValidator.validateSubjectTokenType(subjectTokenType, auditLog); + tokenRestWebServiceValidator.validateActorTokenType(actorTokenType, auditLog); + tokenRestWebServiceValidator.validateActorToken(actorToken, auditLog); + + final SessionId sessionId = sessionIdService.getSessionByDeviceSecret(actorToken); + tokenRestWebServiceValidator.validateSessionForTokenExchange(sessionId, actorToken, auditLog); + checkNotNull(sessionId); // it checked by validator, added it only to relax static bugs checker + + tokenRestWebServiceValidator.validateSubjectToken(subjectToken, sessionId, auditLog); + + TokenExchangeGrant tokenExchangeGrant = authorizationGrantList.createTokenExchangeGrant(new User(), client); + tokenExchangeGrant.setSessionDn(sessionId.getDn()); + + executionContext.setGrant(tokenExchangeGrant); + + scope = tokenExchangeGrant.checkScopesPolicy(scope); + + AccessToken accessToken = tokenExchangeGrant.createAccessToken(executionContext); // create token after scopes are checked + + IdToken idToken = null; + if (isTrue(appConfiguration.getOpenidScopeBackwardCompatibility()) && tokenExchangeGrant.getScopes().contains(OPENID)) { + boolean includeIdTokenClaims = Boolean.TRUE.equals( + appConfiguration.getLegacyIdTokenClaims()); + + ExternalUpdateTokenContext context = new ExternalUpdateTokenContext(httpRequest, tokenExchangeGrant, client, appConfiguration, attributeService); + + executionContext.setIncludeIdTokenClaims(includeIdTokenClaims); + executionContext.setPreProcessing(idTokenPreProcessing); + executionContext.setPostProcessor(externalUpdateTokenService.buildModifyIdTokenProcessor(context)); + + idToken = tokenExchangeGrant.createIdToken( + null, null, null, null, null, executionContext); + } + + RefreshToken reToken = tokenCreatorService.createRefreshToken(executionContext, scope); + + executionContext.getAuditLog().updateOAuth2AuditLog(tokenExchangeGrant, true); + + String rotatedDeviceSecret = rotateDeviceSecret(sessionId, actorToken); + + JSONObject jsonObj = new JSONObject(); + try { + TokenRestWebServiceImpl.fillJsonObject(jsonObj, accessToken, accessToken.getTokenType(), accessToken.getExpiresIn(), reToken, scope, idToken); + jsonObj.put("issued_token_type", TOKEN_TYPE_ACCESS_TOKEN); + if (StringUtils.isNotBlank(rotatedDeviceSecret)) { + jsonObj.put("device_secret", rotatedDeviceSecret); + } + } catch (JSONException e) { + log.error(e.getMessage(), e); + } + + return jsonObj; + } +} diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenInternalService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenInternalService.java deleted file mode 100644 index c550677983e..00000000000 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenInternalService.java +++ /dev/null @@ -1,46 +0,0 @@ -package io.jans.as.server.token.ws.rs; - -import io.jans.as.common.model.session.SessionId; -import io.jans.as.model.configuration.AppConfiguration; -import io.jans.as.server.model.token.HandleTokenFactory; -import io.jans.as.server.service.SessionIdService; -import jakarta.ejb.Stateless; -import jakarta.inject.Inject; -import jakarta.inject.Named; -import org.apache.commons.lang3.BooleanUtils; -import org.slf4j.Logger; - -import java.util.List; - -/** - * @author Yuriy Z - */ -@Stateless -@Named -public class TokenInternalService { - - @Inject - private Logger log; - - @Inject - private AppConfiguration appConfiguration; - - @Inject - private SessionIdService sessionIdService; - - public String rotateDeviceSecret(SessionId sessionId, String actorToken) { - if (BooleanUtils.isFalse(appConfiguration.getRotateDeviceSecret())) { - return null; - } - - String newDeviceSecret = HandleTokenFactory.generateDeviceSecret(); - - final List deviceSecrets = sessionId.getDeviceSecrets(); - deviceSecrets.remove(actorToken); - deviceSecrets.add(newDeviceSecret); - - sessionIdService.updateSessionId(sessionId, false); - - return newDeviceSecret; - } -} diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenRestWebServiceImpl.java b/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenRestWebServiceImpl.java index 6de092a904d..9bfa3f17e6c 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenRestWebServiceImpl.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenRestWebServiceImpl.java @@ -13,7 +13,10 @@ import io.jans.as.common.model.session.SessionId; import io.jans.as.common.service.AttributeService; import io.jans.as.model.authorize.CodeVerifier; -import io.jans.as.model.common.*; +import io.jans.as.model.common.BackchannelTokenDeliveryMode; +import io.jans.as.model.common.FeatureFlagType; +import io.jans.as.model.common.GrantType; +import io.jans.as.model.common.TokenType; import io.jans.as.model.configuration.AppConfiguration; import io.jans.as.model.crypto.binding.TokenBindingMessage; import io.jans.as.model.error.ErrorResponseFactory; @@ -52,9 +55,7 @@ import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response.ResponseBuilder; import jakarta.ws.rs.core.SecurityContext; -import org.apache.commons.lang.BooleanUtils; import org.apache.commons.lang.StringUtils; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.json.JSONException; import org.json.JSONObject; @@ -62,11 +63,9 @@ import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; -import java.util.Arrays; import java.util.Date; import java.util.function.Function; -import static com.google.common.base.Preconditions.checkNotNull; import static io.jans.as.model.config.Constants.*; import static org.apache.commons.lang.BooleanUtils.isFalse; import static org.apache.commons.lang.BooleanUtils.isTrue; @@ -136,7 +135,10 @@ public class TokenRestWebServiceImpl implements TokenRestWebService { private TokenRestWebServiceValidator tokenRestWebServiceValidator; @Inject - private TokenInternalService tokenInternalService; + private TokenExchangeService tokenExchangeService; + + @Inject + private TokenCreatorService tokenCreatorService; @Override public Response requestAccessToken(String grantType, String code, @@ -205,7 +207,8 @@ public Response requestAccessToken(String grantType, String code, } else if (gt == GrantType.DEVICE_CODE) { return processDeviceCodeGrantType(executionContext, deviceCode, scope); } else if (gt == GrantType.TOKEN_EXCHANGE) { - return processTokenExchange(scope, idTokenPreProcessing, executionContext); + final JSONObject responseJson = tokenExchangeService.processTokenExchange(scope, idTokenPreProcessing, executionContext); + return response(Response.ok().entity(responseJson.toString()), auditLog); } } catch (WebApplicationException e) { throw e; @@ -217,72 +220,6 @@ public Response requestAccessToken(String grantType, String code, throw new WebApplicationException(tokenRestWebServiceValidator.error(400, TokenErrorResponseType.UNSUPPORTED_GRANT_TYPE, "Unsupported Grant Type.").build()); } - private Response processTokenExchange(String scope, Function idTokenPreProcessing, ExecutionContext executionContext) { - final HttpServletRequest httpRequest = executionContext.getHttpRequest(); - final Client client = executionContext.getClient(); - final OAuth2AuditLog auditLog = executionContext.getAuditLog(); - - final String audience = httpRequest.getParameter("audience"); - final String subjectToken = httpRequest.getParameter("subject_token"); - final String subjectTokenType = httpRequest.getParameter("subject_token_type"); - final String actorToken = httpRequest.getParameter("actor_token"); - final String actorTokenType = httpRequest.getParameter("actor_token_type"); - - tokenRestWebServiceValidator.validateAudience(audience, auditLog); - tokenRestWebServiceValidator.validateSubjectTokenType(subjectTokenType, auditLog); - tokenRestWebServiceValidator.validateActorTokenType(actorTokenType, auditLog); - tokenRestWebServiceValidator.validateActorToken(actorToken, auditLog); - - final SessionId sessionId = sessionIdService.getSessionByDeviceSecret(actorToken); - tokenRestWebServiceValidator.validateSessionForTokenExchange(sessionId, actorToken, auditLog); - checkNotNull(sessionId); // it checked by validator, added it only to relax static bugs checker - - tokenRestWebServiceValidator.validateSubjectToken(subjectToken, sessionId, auditLog); - - TokenExchangeGrant tokenExchangeGrant = authorizationGrantList.createTokenExchangeGrant(new User(), client); - tokenExchangeGrant.setSessionDn(sessionId.getDn()); - - executionContext.setGrant(tokenExchangeGrant); - - scope = tokenExchangeGrant.checkScopesPolicy(scope); - - AccessToken accessToken = tokenExchangeGrant.createAccessToken(executionContext); // create token after scopes are checked - - IdToken idToken = null; - if (isTrue(appConfiguration.getOpenidScopeBackwardCompatibility()) && tokenExchangeGrant.getScopes().contains(OPENID)) { - boolean includeIdTokenClaims = Boolean.TRUE.equals( - appConfiguration.getLegacyIdTokenClaims()); - - ExternalUpdateTokenContext context = new ExternalUpdateTokenContext(httpRequest, tokenExchangeGrant, client, appConfiguration, attributeService); - - executionContext.setIncludeIdTokenClaims(includeIdTokenClaims); - executionContext.setPreProcessing(idTokenPreProcessing); - executionContext.setPostProcessor(externalUpdateTokenService.buildModifyIdTokenProcessor(context)); - - idToken = tokenExchangeGrant.createIdToken( - null, null, null, null, null, executionContext); - } - - RefreshToken reToken = createRefreshToken(executionContext, scope); - - executionContext.getAuditLog().updateOAuth2AuditLog(tokenExchangeGrant, true); - - String rotatedDeviceSecret = tokenInternalService.rotateDeviceSecret(sessionId, actorToken); - - JSONObject jsonObj = new JSONObject(); - try { - fillJsonObject(jsonObj, accessToken, accessToken.getTokenType(), accessToken.getExpiresIn(), reToken, scope, idToken); - jsonObj.put("issued_token_type", TOKEN_TYPE_ACCESS_TOKEN); - if (StringUtils.isNotBlank(rotatedDeviceSecret)) { - jsonObj.put("device_secret", rotatedDeviceSecret); - } - } catch (JSONException e) { - log.error(e.getMessage(), e); - } - - return response(Response.ok().entity(jsonObj.toString()), executionContext.getAuditLog()); - } - private Response processROPC(String username, String password, String scope, GrantType gt, Function idTokenPreProcessing, ExecutionContext executionContext) throws SearchException { boolean authenticated = false; User user = null; @@ -317,7 +254,7 @@ private Response processROPC(String username, String password, String scope, Gra } } - RefreshToken reToken = createRefreshToken(executionContext, scope); + RefreshToken reToken = tokenCreatorService.createRefreshToken(executionContext, scope); scope = resourceOwnerPasswordCredentialsGrant.checkScopesPolicy(scope); @@ -399,7 +336,7 @@ private Response processRefreshTokenGrant(String scope, String refreshToken, Fun RefreshToken reToken = null; if (isFalse(appConfiguration.getSkipRefreshTokenDuringRefreshing())) { if (isTrue(appConfiguration.getRefreshTokenExtendLifetimeOnRotation())) { - reToken = createRefreshToken(executionContext, scope); // extend lifetime + reToken = tokenCreatorService.createRefreshToken(executionContext, scope); // extend lifetime } else { log.trace("Create refresh token with fixed (not extended) lifetime taken from previous refresh token."); @@ -456,7 +393,7 @@ private Response processAuthorizationCode(String code, String scope, String code authorizationCodeGrant.setIsCachedWithNoPersistence(false); authorizationCodeGrant.save(); - RefreshToken reToken = createRefreshToken(executionContext, scope); + RefreshToken reToken = tokenCreatorService.createRefreshToken(executionContext, scope); scope = authorizationCodeGrant.checkScopesPolicy(scope); @@ -526,37 +463,6 @@ private User authenticateUser(String username, String password, ExecutionContext return user; } - private void checkUser(AuthorizationGrant authorizationGrant) { - if (BooleanUtils.isFalse(appConfiguration.getCheckUserPresenceOnRefreshToken())) { - return; - } - - final User user = authorizationGrant.getUser(); - if (user == null || "inactive".equalsIgnoreCase(user.getStatus())) { - log.trace("The user associated with this grant is not found or otherwise with status=inactive."); - throw new WebApplicationException(error(400, TokenErrorResponseType.INVALID_GRANT, "The user associated with this grant is not found or otherwise with status=inactive.").build()); - } - } - - @Nullable - private RefreshToken createRefreshToken(@NotNull ExecutionContext executionContext, @NotNull String scope) { - final AuthorizationGrant grant = executionContext.getGrant(); - if (!isRefreshTokenAllowed(executionContext.getClient(), scope, grant)) { - return null; - } - - checkUser(grant); - - final ExternalUpdateTokenContext context = new ExternalUpdateTokenContext(executionContext.getHttpRequest(), grant, executionContext.getClient(), appConfiguration, attributeService); - context.setExecutionContext(executionContext); - - final int refreshTokenLifetimeInSeconds = externalUpdateTokenService.getRefreshTokenLifetimeInSeconds(context); - if (refreshTokenLifetimeInSeconds > 0) { - return grant.createRefreshToken(executionContext, refreshTokenLifetimeInSeconds); - } - return grant.createRefreshToken(executionContext); - } - /** * Processes token request for device code grant type. * @@ -577,7 +483,7 @@ private Response processDeviceCodeGrantType(ExecutionContext executionContext, f throw new WebApplicationException(response(error(400, TokenErrorResponseType.INVALID_GRANT, REASON_CLIENT_NOT_AUTHORIZED), executionContext.getAuditLog())); } - RefreshToken refToken = createRefreshToken(executionContext, scope); + RefreshToken refToken = tokenCreatorService.createRefreshToken(executionContext, scope); AccessToken accessToken = deviceCodeGrant.createAccessToken(executionContext); @@ -636,13 +542,6 @@ private Response processDeviceCodeGrantType(ExecutionContext executionContext, f } } - private boolean isRefreshTokenAllowed(Client client, String requestedScope, AbstractAuthorizationGrant grant) { - if (isTrue(appConfiguration.getForceOfflineAccessScopeToEnableRefreshToken()) && !grant.getScopes().contains(ScopeConstants.OFFLINE_ACCESS) && !Strings.nullToEmpty(requestedScope).contains(ScopeConstants.OFFLINE_ACCESS)) { - return false; - } - return Arrays.asList(client.getGrantTypes()).contains(GrantType.REFRESH_TOKEN); - } - private void validatePKCE(AuthorizationCodeGrant grant, String codeVerifier, OAuth2AuditLog oAuth2AuditLog) { log.trace("PKCE validation, code_verifier: {}, code_challenge: {}, method: {}", codeVerifier, grant.getCodeChallenge(), grant.getCodeChallengeMethod()); @@ -712,7 +611,7 @@ public String getJSonResponse(AccessToken accessToken, TokenType tokenType, return jsonObj.toString(); } - public void fillJsonObject(JSONObject jsonObj, AccessToken accessToken, TokenType tokenType, + public static void fillJsonObject(JSONObject jsonObj, AccessToken accessToken, TokenType tokenType, Integer expiresIn, RefreshToken refreshToken, String scope, IdToken idToken) { jsonObj.put("access_token", accessToken.getCode()); // Required @@ -748,7 +647,7 @@ private Response processCIBA(String scope, String authReqId, Function