Another-Minecraft-Chat-Client icon indicating copy to clipboard operation
Another-Minecraft-Chat-Client copied to clipboard

Support Server type

Open SeanWalk opened this issue 3 years ago • 13 comments

Is this offline server or cracked server only? Can't find any authentication place.

SeanWalk avatar Feb 10 '22 05:02 SeanWalk

Currently joining Online servers is not supported. I plan to implement it at some point in the future, but I don't own a Minecraft account, so I can't test if it actually works.

Sorry for the inconvenience!

Defective4 avatar Feb 10 '22 09:02 Defective4

Mojang has APIs for getting Auth Tokens (Way to login to server) wiki.vg/Mojang_API

TheTwoBoom avatar Apr 08 '22 15:04 TheTwoBoom

I know @TwoB00m, but I have no way to test if it works, because I don't own a Minecraft account

Defective4 avatar Apr 10 '22 13:04 Defective4

You can try to implement TheAltening Alt token, the token are free and valid for 15min @Defective4 TheAltening has also got a API

TheTwoBoom avatar Apr 10 '22 13:04 TheTwoBoom

Thank you @TwoB00m, your answer actually helped me a lot and hopefully I will be able to release a new update soon, this time with fully working authentication.

Defective4 avatar Apr 26 '22 15:04 Defective4

Mojang authentication is now supported in release v1.7.0, however it needs to be tested.

Defective4 avatar Apr 27 '22 08:04 Defective4

But many have Microsoft Minecraft Accounts...... It Mojang Login doesn't help

TheTwoBoom avatar Apr 27 '22 17:04 TheTwoBoom

Yeah, that's why I didn't close this issue yet. I have to do further testing to finally implement Microsoft authentication. Hopefully I will be able to add this soon

Defective4 avatar Apr 27 '22 17:04 Defective4

https://mojang-api-docs.netlify.app/authentication/msa.html

TheTwoBoom avatar Apr 28 '22 04:04 TheTwoBoom

Here's an tutorial for Microsoft authentication

TheTwoBoom avatar Apr 28 '22 04:04 TheTwoBoom

I literally created a remote auth code in java, here's the code (and the refresh token is included to keep the account logged in)

import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.internal.JavaVersion;

public class ClientLogin extends TimerTask {
	private final Timer authcodeTimer = new Timer();
	private String deviceCode;
	private String refreshToken;
	private String clientId = "389b1b32-b5d5-43b2-bddc-84ce938d6737"; // token from https://github.com/microsoft/Office365APIEditor
	
	private UUID getOfflineUUID(String name) {
		String value = "OfflinePlayer:" + name;
		return UUID.nameUUIDFromBytes(value.getBytes(StandardCharsets.UTF_8));
	}
	
	private String readFile(File file) throws IOException {
		BufferedReader reader = new BufferedReader(new FileReader(file));
		String ln = reader.readLine();
		String dt = "";
		while (ln != null) {
			dt += ln;
			ln = reader.readLine();
		}
		reader.close();
		return dt;
	}
	
	private void writeFile(File file, String data) throws IOException {
		BufferedWriter writer = new BufferedWriter(new FileWriter(file));
		if (!file.exists()) file.createNewFile();
		writer.write(data);
		writer.close();
	}
	
	public UserInfo getUserInfo() {
		JsonObject userInfo = null;
		try {
			userInfo = JsonParser.parseString(readFile(new File("userInfo.json"))).getAsJsonObject();
			
		}
		catch (IOException e) {
			e.printStackTrace();
		}
		return new UserInfo(userInfo.get("id").getAsString(), userInfo.get("name").getAsString(), userInfo.get("access_token").getAsString());
	}

	public void logOut() throws IOException {
		writeFile(new File("userInfo.json"), "{}");
	}
	
	public void setPlayerName(String name) throws IOException {
		JsonObject creds = new JsonObject();
		creds.addProperty("refresh_token", "");
		creds.addProperty("access_token", "");
		creds.addProperty("name", name);
		creds.addProperty("id", getOfflineUUID(name).toString());
		writeFile(new File("userInfo.json"), creds.toString());
	}
	
	public void refreshToken() throws IOException {
		JsonObject profile = JsonParser.parseString(readFile(new File("userInfo.json"))).getAsJsonObject();
		JsonElement rt = profile.get("refresh_token");
		if (rt != null) {
			URL url = new URL("https://login.microsoftonline.com/consumers/oauth2/v2.0/token");
			HttpURLConnection http = (HttpURLConnection)  url.openConnection();
			http.setRequestMethod("POST");
			String data = "client_id=" + clientId + "&grant_type=refresh_token&refresh_token=" + rt.getAsString();
			http.setDoOutput(true);
			http.setRequestProperty("User-Agent", "java/" + JavaVersion.getMajorJavaVersion());
			http.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
			http.setRequestProperty("Content-Length", String.valueOf(data.length()));
			http.getOutputStream().write(data.getBytes(StandardCharsets.UTF_8));
			BufferedReader reader = new BufferedReader(new InputStreamReader(http.getInputStream()));
			String ln = reader.readLine();
			String dt = "";
			while (ln != null) {
				dt += ln;
				ln = reader.readLine();
			}
			http.disconnect();
			JsonObject resData = JsonParser.parseString(dt).getAsJsonObject();
			refreshToken = resData.get("refresh_token").getAsString();
			authenticateWithXBL(resData.get("access_token").getAsString());
		}
		else {
			throw new IOException("The item 'refresh_token' is missing.");
		}
	}
	
	public void doRemoteAuth() {
		try {
			URL url = new URL("https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode");
			HttpURLConnection http = (HttpURLConnection)  url.openConnection();
			http.setRequestMethod("POST");
			String data = "client_id=" + clientId + "&scope=XboxLive.signin%20offline_access";
			http.setDoOutput(true);
			http.setRequestProperty("User-Agent", "java/" + JavaVersion.getMajorJavaVersion());
			http.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
			http.setRequestProperty("Content-Length", String.valueOf(data.length()));
			http.getOutputStream().write(data.getBytes(StandardCharsets.UTF_8));
			BufferedReader reader = new BufferedReader(new InputStreamReader(http.getInputStream()));
			String ln = reader.readLine();
			String dt = "";
			while (ln != null) {
				dt += ln;
				ln = reader.readLine();
			}
			http.disconnect();
			JsonObject deviceCodeAuth = JsonParser.parseString(dt).getAsJsonObject();
			System.out.println("[msa] First time signing in. Please authenticate now:");
			System.out.println("To sign in, use a web browser to open the page " + deviceCodeAuth.get("verification_uri").getAsString() + " and enter the code " + deviceCodeAuth.get("user_code").getAsString() + " to authenticate.");
			deviceCode = deviceCodeAuth.get("device_code").getAsString();
			authcodeTimer.schedule(this, deviceCodeAuth.get("interval").getAsInt() * 1000, deviceCodeAuth.get("interval").getAsInt() * 1000);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private void getMinecraftJavaProfile(String tokenType, String accessToken) throws IOException {
		URL url = new URL("https://api.minecraftservices.com/minecraft/profile");
		HttpURLConnection http = (HttpURLConnection) url.openConnection();
		http.setDoOutput(true);
		http.setRequestProperty("User-Agent", "java/" + JavaVersion.getMajorJavaVersion());
		http.setRequestProperty("Authorization", tokenType + " " + accessToken);
		BufferedReader reader = new BufferedReader(new InputStreamReader(http.getInputStream()));
		String ln = reader.readLine();
		String dt = "";
		while (ln != null) {
			dt += ln;
			ln = reader.readLine();
		}
		JsonObject resObject = JsonParser.parseString(dt).getAsJsonObject();
		String name = resObject.get("name").getAsString();
		String id = resObject.get("id").getAsString();
		JsonObject creds = new JsonObject();
		creds.addProperty("refresh_token", refreshToken);
		creds.addProperty("access_token", accessToken);
		creds.addProperty("name", name);
		creds.addProperty("id", id);
		writeFile(new File("userInfo.json"), creds.toString());
	}
	
	private void checkGameOwnership(String tokenType, String accessToken) throws IOException {
		URL url = new URL("https://api.minecraftservices.com/entitlements/mcstore");
		HttpURLConnection http = (HttpURLConnection) url.openConnection();
		http.setDoOutput(true);
		http.setRequestProperty("User-Agent", "java/" + JavaVersion.getMajorJavaVersion());
		http.setRequestProperty("Authorization", tokenType + " " + accessToken);
		BufferedReader reader = new BufferedReader(new InputStreamReader(http.getInputStream()));
		String ln = reader.readLine();
		String dt = "";
		while (ln != null) {
			dt += ln;
			ln = reader.readLine();
		}
		boolean hasGameMinecraft = false;
		boolean hasProductMinecraft = false;
		JsonObject resObject = JsonParser.parseString(dt).getAsJsonObject();
		JsonArray items = resObject.get("items").getAsJsonArray();
		for (JsonElement e : items) {
			if (e.getAsJsonObject().get("name").getAsString().equals("product_minecraft")) hasProductMinecraft = true;
			if (e.getAsJsonObject().get("name").getAsString().equals("game_minecraft")) hasGameMinecraft = true;
		}
		if (hasProductMinecraft && hasGameMinecraft) getMinecraftJavaProfile(tokenType, accessToken);
		else System.err.println("This user doesn't have the game. You can buy it at Minecraft.net");
	}
	
	private void authenticateWithMinecraftJava(String userHash, String XSTSToken) throws IOException {
		URL url = new URL("https://api.minecraftservices.com/authentication/login_with_xbox");
		HttpURLConnection http = (HttpURLConnection) url.openConnection();
		http.setDoOutput(true);
		JsonObject requestData = new JsonObject();
		requestData.addProperty("identityToken", "XBL3.0 x=" + userHash + ";" + XSTSToken);
		http.setRequestMethod("POST");
		http.setRequestProperty("User-Agent", "java/" + JavaVersion.getMajorJavaVersion());
		http.setRequestProperty("Content-Type", "application/json");
		http.setRequestProperty("Content-Length", String.valueOf(requestData.toString().length()));
		http.getOutputStream().write(requestData.toString().getBytes(StandardCharsets.UTF_8));
		BufferedReader reader = new BufferedReader(new InputStreamReader(http.getInputStream()));
		String ln = reader.readLine();
		String dt = "";
		while (ln != null) {
			dt += ln;
			ln = reader.readLine();
		}
		JsonObject resObject = JsonParser.parseString(dt).getAsJsonObject();
		checkGameOwnership(resObject.get("token_type").getAsString(), resObject.get("access_token").getAsString());
	}
	
	private void authenticateWithXSTS(String XBLToken) throws IOException {
		URL url = new URL("https://xsts.auth.xboxlive.com/xsts/authorize");
		HttpURLConnection http = (HttpURLConnection) url.openConnection();
		http.setDoOutput(true);
		http.setRequestMethod("POST");
		JsonObject requestData = new JsonObject();
		JsonObject properties = new JsonObject();
		JsonArray userTokens = new JsonArray();
		properties.addProperty("SandboxId", "RETAIL");
		userTokens.add(XBLToken);
		properties.add("UserTokens", userTokens);
		requestData.add("Properties", properties);
		requestData.addProperty("RelyingParty", "rp://api.minecraftservices.com/");
		requestData.addProperty("TokenType", "JWT");
		http.setRequestProperty("User-Agent", "java/" + JavaVersion.getMajorJavaVersion());
		http.setRequestProperty("Content-Type", "application/json");
		http.setRequestProperty("Content-Length", String.valueOf(requestData.toString().length()));
		http.getOutputStream().write(requestData.toString().getBytes(StandardCharsets.UTF_8));
		BufferedReader reader = new BufferedReader(new InputStreamReader(http.getInputStream()));
		String ln = reader.readLine();
		String dt = "";
		while (ln != null) {
			dt += ln;
			ln = reader.readLine();
		}
		JsonObject resObject = JsonParser.parseString(dt).getAsJsonObject();
		authenticateWithMinecraftJava(resObject.get("DisplayClaims").getAsJsonObject().get("xui").getAsJsonArray().get(0).getAsJsonObject().get("uhs").getAsString(), resObject.get("Token").getAsString());
	}
	
	private void authenticateWithXBL(String accessToken) throws IOException {
		URL url = new URL("https://user.auth.xboxlive.com/user/authenticate");
		HttpURLConnection http = (HttpURLConnection) url.openConnection();
		http.setDoOutput(true);
		http.setRequestMethod("POST");
		JsonObject requestData = new JsonObject();
		JsonObject properties = new JsonObject();
		requestData.add("Properties", properties);
		properties.addProperty("AuthMethod", "RPS");
		properties.addProperty("SiteName", "user.auth.xboxlive.com");
		properties.addProperty("RpsTicket", "d=" + accessToken);
		requestData.addProperty("RelyingParty", "http://auth.xboxlive.com");
		requestData.addProperty("TokenType", "JWT");
		http.setRequestProperty("User-Agent", "java/" + JavaVersion.getMajorJavaVersion());
		http.setRequestProperty("Content-Type", "application/json");
		http.setRequestProperty("Content-Length", String.valueOf(requestData.toString().length()));
		http.getOutputStream().write(requestData.toString().getBytes(StandardCharsets.UTF_8));
		BufferedReader reader = new BufferedReader(new InputStreamReader(http.getInputStream()));
		String ln = reader.readLine();
		String dt = "";
		while (ln != null) {
			dt += ln;
			ln = reader.readLine();
		}
		JsonObject resObject = JsonParser.parseString(dt).getAsJsonObject();
		authenticateWithXSTS(resObject.get("Token").getAsString());
	}

	@Override
	public void run() {
		try {
			URL url = new URL("https://login.microsoftonline.com/consumers/oauth2/v2.0/token");
			HttpURLConnection http = (HttpURLConnection)  url.openConnection();
			http.setRequestMethod("POST");
			String data = "grant_type=urn:ietf:params:oauth:grant-type:device_code&client_id=" + clientId + "&device_code=" + deviceCode;
			http.setDoOutput(true);
			http.setRequestProperty("User-Agent", "java/" + JavaVersion.getMajorJavaVersion());
			http.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
			http.setRequestProperty("Content-Length", String.valueOf(data.length()));
			http.getOutputStream().write(data.getBytes(StandardCharsets.UTF_8));
			String dt = "";
			if (http.getResponseCode() >= 400) {
				BufferedReader errorStreamReader = new BufferedReader(new InputStreamReader(http.getErrorStream()));
				String ln = errorStreamReader.readLine();
				while (ln != null) {
					dt += ln;
					ln = errorStreamReader.readLine();
				}
				JsonObject errdata = JsonParser.parseString(dt).getAsJsonObject();
				String err = errdata.get("error").getAsString();
				if (err == "authorization_declined") authcodeTimer.cancel();
				else if (err == "expired_token") {
					System.out.println("Code expired");
					authcodeTimer.cancel();
				}
			}
			else {
				BufferedReader reader = new BufferedReader(new InputStreamReader(http.getInputStream()));
				String ln = reader.readLine();
				while (ln != null) {
					dt += ln;
					ln = reader.readLine();
				}
				authcodeTimer.cancel();
				System.out.println("[msa] Signed in with Microsoft");
				JsonObject resObject = JsonParser.parseString(dt).getAsJsonObject();
				refreshToken = resObject.get("refresh_token").getAsString();
				authenticateWithXBL(resObject.get("access_token").getAsString());
			}
			http.disconnect();
		}
		catch (IOException e) {
			e.printStackTrace();
		}
	}
}

And UserInfo.java:

public class UserInfo {
	public String id;
	public String name;
	public String accessToken;
	
	public UserInfo(String _id, String _name, String _accessToken) {
		id = _id;
		name = _name;
		accessToken = _accessToken;
	}
}

gabe4278 avatar Jul 06 '22 16:07 gabe4278

Thank you @gabe4278! I will try out your code as soon as I can and if possible I will add this into my client if you agree. Of course I will give you an appropriate credit :)

Defective4 avatar Jul 06 '22 18:07 Defective4

The auth server in the code are legit, and the code looks OK

TheTwoBoom avatar Jul 06 '22 18:07 TheTwoBoom

The Microsoft authentication is finally added in Release v1.11.0!

Defective4 avatar Apr 04 '23 14:04 Defective4