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
- Avoid triggering refresh and this check concurrently on the same session; let refresh complete first
- Confirm split-tokens tenant properties are consistent so both injections come from the same token state
- Ensure no custom augmentor rewrites TokenCredential refresh tokens without updating the @RefreshToken injection source
- 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
- Serialize token refresh operations per session
- Keep split-tokens config identical across tenants
- Do not mutate TokenCredential refresh tokens in augmentors
- Verify refresh-token injection after any OIDC extension upgrade
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
- SecurityIdentity must have a RoutingContext attribute
- This method must not be invoked
- Access token values are not equal
- Required ID token is not returned in the refresh token grant
- Refresh token can only be used with the refresh token grant
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/4197672e746a46a8.
Report an issue: GitHub.