quarkusio/quarkus · error · OIDCException

Refresh token values are not equal

Error message

Refresh token values are not equal

What it means

Invariant check shared by the refresh-token endpoints via doGetRefreshToken. When an access-token check is required and a refresh token is present, the injected TokenCredential's refresh token must equal the injected @RefreshToken value; otherwise the OIDC extension injected inconsistent refresh-token representations and OIDCException is thrown.

Source

Thrown at integration-tests/oidc-code-flow/src/main/java/io/quarkus/it/keycloak/ProtectedResource.java:262

        return "tenant-split-tokens:" + getAccessToken();
    }

    @GET
    @Path("access/tenant-split-id-refresh-token")
    public String getAccessIdRefreshTokenSplitTokens() {
        return "tenant-split-id-refresh-token:" + getAccessToken();
    }

    @GET
    @Path("refresh")
    public String getRefreshToken() {
        return doGetRefreshToken(true);
    }

    private String doGetRefreshToken(boolean refreshWithAccessTokenCheckRequired) {
        if (refreshWithAccessTokenCheckRequired && refreshToken.getToken() != null
                && !accessTokenCredential.getRefreshToken().getToken().equals(refreshToken.getToken())) {
            throw new OIDCException("Refresh token values are not equal");
        }
        if (refreshToken.getToken() != null && !refreshToken.getToken().isEmpty()) {
            String message = "RT injected";
            String listenerMessage = routingContext.get("listener-message");
            if (listenerMessage != null) {
                message += ("(" + listenerMessage + ")");
            }
            return message;
        } else {
            return "no refresh";
        }
    }

    @GET
    @Path("refresh/tenant-idtoken-only")
    public String getRefreshTokenIdTokenOnly() {
        return "tenant-idtoken-only:" + getRefreshToken();
    }

View on GitHub (pinned to e1c734241f)

Solutions

  1. Avoid triggering refresh and this check concurrently on the same session; let refresh complete first
  2. Confirm split-tokens tenant properties are consistent so both injections come from the same token state
  3. Ensure no custom augmentor rewrites TokenCredential refresh tokens without updating the @RefreshToken injection source
  4. If stock config reproduces it, inspect OidcIdentityProvider refresh-token attribute population for a regression

Example fix

// before
if (!accessTokenCredential.getRefreshToken().getToken().equals(refreshToken.getToken())) {
    throw new OIDCException("Refresh token values are not equal");
}
// after (null-safe guard first)
String injected = accessTokenCredential.getRefreshToken() == null ? null : accessTokenCredential.getRefreshToken().getToken();
if (refreshToken.getToken() != null && injected != null && !injected.equals(refreshToken.getToken())) {
    throw new OIDCException("Refresh token values are not equal");
}
Defensive patterns

Strategy: validation

Validate before calling

if (refreshToken.getToken() != null && accessTokenCredential.getRefreshToken() != null && !accessTokenCredential.getRefreshToken().getToken().equals(refreshToken.getToken())) { throw new IllegalStateException("refresh token mismatch"); }

Type guard

boolean refreshTokensConsistent(RefreshToken rt, TokenCredential cred) { return rt.getToken() == null || cred == null || cred.getRefreshToken() == null || rt.getToken().equals(cred.getRefreshToken().getToken()); }

Try / catch

try { return doGetRefreshToken(true); } catch (OIDCException e) { log.warnf("refresh token mismatch: %s", e.getMessage()); reauthenticate(); }

Prevention

When it happens

Trigger: A refresh-token endpoint calls doGetRefreshToken(true) while refreshToken.getToken() != null and accessTokenCredential.getRefreshToken().getToken() differs from refreshToken.getToken().

Common situations: A concurrent token refresh replaced one representation but not the other; split-token tenant configuration causing one endpoint to see a refreshed token and the other a stale one; custom augmentation rewriting TokenCredential but not the @RefreshToken injection; regression in OidcIdentityProvider refresh-token propagation.

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/4197672e746a46a8. Report an issue: GitHub.