apereo/cas · error

Provided refresh token

Error message

Provided refresh token [{}] does not belong to client [{}]

What it means

The validator verifies token-to-client ownership: the OAuth20RefreshToken's clientId must match (case-insensitively) the clientId presented in the token request. A mismatch means the client is trying to refresh a token issued to a different client, so the validator warns and returns false.

Solutions

  1. Ensure each client refreshes only with tokens issued to its own clientId; clear the client's stored token and re-run the authorization flow.
  2. Check for shared token storage or copy-pasted tokens across environments/apps.
  3. Confirm the client_id in the request matches the one used when the refresh token was issued.
  4. Regenerate the refresh token by authenticating the user again with the correct client.

Example fix

// before
POST token: grant_type=refresh_token&client_id=clientB&refresh_token=<token-for-clientA>
// after
POST token: grant_type=refresh_token&client_id=clientA&refresh_token=<token-for-clientA>
Defensive patterns

Strategy: validation

Validate before calling

// client-side check before refreshing
if (storedToken.clientId !== currentClientId) {
  discardStoredToken(); // must re-authenticate
}

Type guard

function tokenBelongsToClient(token, clientId) {
  return typeof token?.clientId === 'string' &&
    token.clientId.toLowerCase() === clientId.toLowerCase();
}

Prevention

When it happens

Trigger: Client B presents a refresh token originally issued to client A, typically because multiple OAuth clients share a token store/config or a copy-paste/misconfiguration sends the wrong credentials alongside the token.

Common situations: Two applications accidentally configured with the same refresh-token cache; staging and production clients swapped client secrets; token persisted from a previous deployment with a different clientId; client_id parameter edited while reusing a stored token.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08). Data as JSON: /api/errors/f6e37cb614fa3448. Report an issue: GitHub.

Appendix: source

Thrown at support/cas-server-support-oauth-core-api/src/main/java/org/apereo/cas/support/oauth/validator/token/OAuth20RefreshTokenGrantTypeTokenRequestValidator.java:71

        }

        LOGGER.debug("Received grant type [{}] with client id [{}]", grantType, clientId);
        val registeredService = OAuth20Utils.getRegisteredOAuthServiceByClientId(
            configurationContext.getServicesManager(), clientId);
        val audit = AuditableContext.builder()
            .registeredService(registeredService)
            .build();
        val accessResult = configurationContext.getRegisteredServiceAccessStrategyEnforcer().execute(audit);
        accessResult.throwExceptionIfNeeded();

        if (!isGrantTypeSupportedBy(Objects.requireNonNull(registeredService), grantType)) {
            LOGGER.warn("Requested grant type [{}] is not authorized by service definition [{}]",
                grantType, Objects.requireNonNull(registeredService).getServiceId());
            return false;
        }

        if (refreshToken != null && !Strings.CI.equals(refreshToken.getClientId(), clientId)) {
            LOGGER.warn("Provided refresh token [{}] does not belong to client [{}]", refreshToken.getId(), clientId);
            return false;
        }

        return true;
    }

    @Override
    protected OAuth20GrantTypes getGrantType() {
        return OAuth20GrantTypes.REFRESH_TOKEN;
    }
}

View on GitHub (pinned to e7288fc434)