Skip to content

Instantly share code, notes, and snippets.

@AbhayAysola
Created March 8, 2023 12:20
Show Gist options
  • Save AbhayAysola/103eb10c13d0fb5f349d5a317258a3ab to your computer and use it in GitHub Desktop.
Save AbhayAysola/103eb10c13d0fb5f349d5a317258a3ab to your computer and use it in GitHub Desktop.
Minecraft Microsoft Auth
use std::{
collections::HashMap,
io::{self, Write},
};
use open;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use serde_json::json;
use url_builder::URLBuilder;
/// the client id that @LynithDev gave
const CLIENT_ID: &str = "00000000402b5328";
/// The response from authenticating with Microsoft OAuth flow
#[derive(Deserialize, Serialize)]
struct AuthorizationTokenResponse {
/// The type of token for authentication
token_type: String,
/// The scope we have access to
scope: String,
/// Seconds until the authentication token expires
expires_in: u32,
/// The authentication token itself
access_token: String,
/// The token used for refreshing access
refresh_token: String,
/// user id?
user_id: String,
foci: String,
}
/// The response from Xbox when authenticating with a Microsoft token
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "PascalCase")]
struct XboxLiveAuthenticationResponse {
/// An ISO-8601 timestamp of when the token was issued
issue_instant: String,
/// An ISO-8601 timestamp of when the token expires
not_after: String,
/// The xbox authentication token to use
token: String,
/// An object that contains a vec of `uhs` objects
/// Looks like { "xui": [{"uhs": "xbl_token"}] }
display_claims: HashMap<String, Vec<HashMap<String, String>>>,
}
/// The response from Minecraft when attempting to authenticate with an xbox token
#[derive(Deserialize, Serialize, Debug)]
struct MinecraftAuthenticationResponse {
/// Some UUID of the account
username: String,
/// The minecraft JWT access token
access_token: String,
/// The type of access token
token_type: String,
/// How many seconds until the token expires
expires_in: u32,
}
#[derive(Serialize, Deserialize, Debug)]
struct MinecraftEntitlementsItemResponse {
name: String,
signature: String,
}
/// The response from Minecraft to check if user owns the game
#[derive(Serialize, Deserialize, Debug)]
struct MinecraftEntitlementsResponse {
/// If the items list is not empty it means the user owns the game
items: Vec<MinecraftEntitlementsItemResponse>,
/// jwt signature
signature: String,
keyId: String,
}
/// The response from Minecraft when attempting to retrieve a users profile
#[derive(Serialize, Deserialize, Debug)]
struct MinecraftProfileResponse {
/// The UUID of the account
id: String,
/// The name of the user
name: String,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
// step 1: attempt to login to microsoft account (OAuth flow)
// requires authorization from the user
// let req: reqwest::RequestBuilder = client
// .get("https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize")
// .query(&[
// ("client_id", CLIENT_ID),
// ("response_type", "code"),
// ("scope", "XboxLive.signin offline_access"),
// ]);
let mut ub = URLBuilder::new();
ub.set_protocol("http")
.set_host("login.microsoftonline.com/consumers/oauth2/v2.0/authorize")
.add_param("client_id", CLIENT_ID)
.add_param("response_type", "code")
.add_param("scope", "XboxLive.signin offline_access")
.add_param("redirect_uri", "https://login.live.com/oauth20_desktop.srf");
open::that(ub.build())?;
println!("Opened url in browser, please enter the token generated");
// retrieve the code from them the user
let code = {
let mut buffer = String::new();
print!("Authorization code: ");
io::stdout().flush()?;
io::stdin().read_line(&mut buffer)?;
buffer
};
// step 2: convert authorization code into authorization token
let authorization_token = client
.post("https://login.live.com/oauth20_token.srf")
.form(&vec![
("client_id", CLIENT_ID),
("code", &code),
("grant_type", "authorization_code"),
("redirect_uri", "https://login.live.com/oauth20_desktop.srf"),
("scope", "XboxLive.signin offline_access"),
])
.send()
.await?
.json::<AuthorizationTokenResponse>()
.await?;
println!("Access token: {:?}", &authorization_token.access_token);
// step 3: authenticate with xbox live
let xbox_authenticate_json = json!({
"Properties": {
"AuthMethod": "RPS",
"SiteName": "user.auth.xboxlive.com",
"RpsTicket": &format!("d={}", authorization_token.access_token)
},
"RelyingParty": "http://auth.xboxlive.com",
"TokenType": "JWT"
});
println!("{}", xbox_authenticate_json);
let xbox_resp: XboxLiveAuthenticationResponse = client
.post("https://user.auth.xboxlive.com/user/authenticate")
.json(&xbox_authenticate_json)
.header("Accept", "application/json")
.send()
.await?
.json()
.await?;
let xbox_token = &xbox_resp.token;
let user_hash = &xbox_resp.display_claims["xui"][0]["uhs"];
println!("");
println!("{:#?}", xbox_resp);
// step 4: convert xbox token into xbox security token
let xbox_security_token_resp: XboxLiveAuthenticationResponse = client
.post("https://xsts.auth.xboxlive.com/xsts/authorize")
.json(&json!({
"Properties": {
"SandboxId": "RETAIL",
"UserTokens": [xbox_token]
},
"RelyingParty": "rp://api.minecraftservices.com/",
"TokenType": "JWT"
}))
.send()
.await?
.json()
.await?;
let xbox_security_token = &xbox_security_token_resp.token;
println!("{:#?}", xbox_security_token_resp);
// step 5: authenticate with minecraft
let minecraft_resp: MinecraftAuthenticationResponse = client
.post("https://api.minecraftservices.com/authentication/login_with_xbox")
.json(&json!({
"identityToken":
format!(
"XBL3.0 x={user_hash};{xsts_token}",
user_hash = user_hash,
xsts_token = xbox_security_token
)
}))
.send()
.await?
.json()
.await?;
let minecraft_token = &minecraft_resp.access_token;
println!("{:#?}", minecraft_resp);
// step 5.5: check if user owns minecraft
let minecraft_entitlements: MinecraftEntitlementsResponse = client
.get("https://api.minecraftservices.com/entitlements/mcstore")
.bearer_auth(minecraft_token)
.send()
.await?
.json()
.await?;
println!("{:?}", minecraft_entitlements);
if minecraft_entitlements.items.is_empty() {
panic!("You do not own minecraft!");
}
// step 6: retrieve the users profile using the minecraft token
let minecraft_profile_resp: MinecraftProfileResponse = client
.get("http://api.minecraftservices.com/minecraft/profile")
.bearer_auth(minecraft_token)
.send()
.await?
.json()
.await?;
println!("{:#?}", minecraft_profile_resp);
Ok(())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment