{"record":{"id":"9041bfad2859dd53","repo":"HMCL-dev/HMCL","slug":"client-token-changed-from","errorCode":null,"errorMessage":"Client token changed from ","messagePattern":"Client token changed from ","errorType":"exception","errorClass":"AuthenticationException","httpStatus":null,"severity":"error","filePath":"HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilService.java","lineNumber":207,"sourceCode":"            byte[] decodedBinary;\n            try {\n                decodedBinary = Base64.getDecoder().decode(encodedTextures);\n            } catch (IllegalArgumentException e) {\n                throw new ServerResponseMalformedException(e);\n            }\n            TextureResponse texturePayload = fromJson(new String(decodedBinary, UTF_8), TextureResponse.class);\n            return Optional.ofNullable(texturePayload.textures);\n        } else {\n            return Optional.empty();\n        }\n    }\n\n    private static YggdrasilSession handleAuthenticationResponse(String responseText, String clientToken) throws AuthenticationException {\n        AuthenticationResponse response = fromJson(responseText, AuthenticationResponse.class);\n        handleErrorMessage(response);\n\n        if (!clientToken.equals(response.clientToken))\n            throw new AuthenticationException(\"Client token changed from \" + clientToken + \" to \" + response.clientToken);\n\n        return new YggdrasilSession(\n                response.clientToken,\n                response.accessToken,\n                response.selectedProfile,\n                response.availableProfiles == null ? null : unmodifiableList(response.availableProfiles),\n                response.user == null ? null : response.user.properties());\n    }\n\n    private static void requireEmpty(String response) throws AuthenticationException {\n        if (StringUtils.isBlank(response))\n            return;\n\n        handleErrorMessage(fromJson(response, ErrorResponse.class));\n    }\n\n    private static void handleErrorMessage(ErrorResponse response) throws AuthenticationException {\n        if (!StringUtils.isBlank(response.error)) {","sourceCodeStart":189,"sourceCodeEnd":225,"githubUrl":"https://github.com/HMCL-dev/HMCL/blob/24702dc5a0214034f4c27166d5fd30cad08cec19/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilService.java#L189-L225","documentation":"YggdrasilService.handleAuthenticationResponse throws AuthenticationException when the clientToken in the server's authentication/refresh/validate response differs from the one HMCL sent. Yggdrasil requires the server to echo the client token; a mismatch means the server state no longer matches the local client state.","triggerScenarios":"Any authenticate/refresh/validate round-trip where the response JSON's clientToken differs from the request's, typically after the auth server was reset, migrated, or the client token was regenerated.","commonSituations":"Auth server (or authlib-injector backend) restarted with a fresh client token store; switching servers while reusing cached sessions; two HMCL instances/other launchers invalidating each other's tokens on the same account.","solutions":["Perform a full re-authentication (authenticate with username/password) instead of refresh/validate, which re-establishes a matching client token.","Verify you are talking to the same yggdrasil server the original token was issued by.","Clear stored tokens/sessions for that account and log in again; avoid sharing one account across multiple launchers simultaneously."],"exampleFix":"// before\nservice.validate(accessToken); // AuthenticationException: client token changed\n// after\ntry {\n    service.validate(accessToken);\n} catch (AuthenticationException e) {\n    YggdrasilSession s = service.authenticate(username, password); // full re-login\n}","handlingStrategy":"retry","validationCode":"String echoed = parseClientTokenFromResponse(responseText);\nif (echoed != null && !expectedClientToken.equals(echoed)) {\n    // skip validate/refresh; go straight to full authenticate\n}","typeGuard":null,"tryCatchPattern":"try {\n    service.validate(accessToken);\n} catch (AuthenticationException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"Client token changed\")) {\n        // full re-login to resynchronize the client token\n        service.authenticate(username, password);\n    }\n}","preventionTips":["Don't reuse accounts across multiple launchers/instances simultaneously.","Re-authenticate after the auth server is restarted or migrated.","Always authenticate against the same server the tokens were issued by."],"tags":["authentication","yggdrasil","token-mismatch"],"backgroundTag":"authentication-required","analyzedSha":"24702dc5a0214034f4c27166d5fd30cad08cec19","analyzedAt":"2026-09-10T12:36:46.680Z","contentChangedAt":"2026-09-10T12:36:46.680Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}