HMCL-dev/HMCL · error · ServerResponseMalformedException

uhs mismatched

Error message

uhs mismatched

What it means

getUhs extracts the user hash (uhs) from the XSTS displayClaims.xui[0].uhs field. When an existingUhs from a prior authentication is known, the new uhs must match; otherwise ServerResponseMalformedException('uhs mismatched') is thrown, since a different uhs means the tokens now belong to a different Xbox user.

Solutions

  1. Remove the account from HMCL and log in again so uhs, refresh token, and session are all captured from the same identity
  2. Ensure only one HMCL instance manages the account file at a time (avoid concurrent writes)
  3. Verify you are signing in with the same Microsoft account that originally created the entry
  4. If you intentionally changed accounts, delete the old entry first instead of reusing stored tokens

Example fix

// before: refreshing stale tokens across an account switch
account.logIn();
// after: detect uhs mismatch and force clean re-login
try {
    account.logIn();
} catch (ServerResponseMalformedException e) {
    if (e.getMessage().contains("uhs mismatched")) {
        accounts.removeAccount(account);
        accounts.createAccount(Accounts.OAUTH_MICROSOFT).logIn();
    } else {
        throw e;
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-compare stored uhs if you persist it
String storedUhs = getStoredUhs();
if (storedUhs != null && !storedUhs.equals(freshUhs)) {
    scheduleCleanReauth(); // don't reuse old refresh token
}

Try / catch

try {
    account.logIn();
} catch (ServerResponseMalformedException e) {
    if ("uhs mismatched".equals(e.getMessage())) {
        accounts.removeAccount(account);
        accounts.createAccount(Accounts.OAUTH_MICROSOFT).logIn();
    } else throw e;
}

Prevention

When it happens

Trigger: authenticateViaLiveAccessToken/refresh exchanges tokens with XSTS and the returned uhs differs from the uhs stored on the account; the Microsoft account linked to the Xbox profile changed; stored uhs was written by a different login than the refresh token being used.

Common situations: User switched Microsoft accounts but HMCL reused the old refresh token; Xbox account re-linked to another Microsoft account; concurrent logins from two HMCL instances on different accounts overwrote each other's storage; Xbox profile migrated.

Related errors


AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10). Data as JSON: /api/errors/39e3c2d025d8903c. Report an issue: GitHub.

Appendix: source

Thrown at HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java:112

        } catch (JsonParseException e) {
            throw new ServerResponseMalformedException(e);
        }
    }

    private String getUhs(XBoxLiveAuthenticationResponse response, String existingUhs) throws AuthenticationException {
        if (response.errorCode != 0) {
            throw new XboxAuthorizationException(response.errorCode, response.redirectUrl);
        }

        if (response.displayClaims == null || response.displayClaims.xui == null || response.displayClaims.xui.size() == 0 || !response.displayClaims.xui.get(0).containsKey("uhs")) {
            LOG.warning("Unrecognized xbox authorization response " + GSON.toJson(response));
            throw new NoXuiException();
        }

        String uhs = (String) response.displayClaims.xui.get(0).get("uhs");
        if (existingUhs != null) {
            if (!Objects.equals(uhs, existingUhs)) {
                throw new ServerResponseMalformedException("uhs mismatched");
            }
        }
        return uhs;
    }

    private MicrosoftSession authenticateViaLiveAccessToken(String liveAccessToken, String liveRefreshToken) throws IOException, JsonParseException, AuthenticationException {
        String uhs;
        XBoxLiveAuthenticationResponse xboxResponse, minecraftXstsResponse;
        try {
            // Authenticate with XBox Live
            xboxResponse = HttpRequest
                    .POST("https://user.auth.xboxlive.com/user/authenticate")
                    .json(mapOf(
                            pair("Properties",
                                    mapOf(pair("AuthMethod", "RPS"), pair("SiteName", "user.auth.xboxlive.com"),
                                            pair("RpsTicket", "d=" + liveAccessToken))),
                            pair("RelyingParty", "http://auth.xboxlive.com"), pair("TokenType", "JWT")))
                    .retry(5)

View on GitHub (pinned to 24702dc5a0)