-
-
Save AbhayAysola/103eb10c13d0fb5f349d5a317258a3ab to your computer and use it in GitHub Desktop.
Minecraft Microsoft Auth
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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