Skip to content

Commit

Permalink
feat: reworked authentication and fixed typo
Browse files Browse the repository at this point in the history
  • Loading branch information
KernelFreeze committed Oct 24, 2023
1 parent e00c93d commit 57ea697
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 56 deletions.
24 changes: 15 additions & 9 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[package]
name = "minecraft-msa-auth"
version = "0.3.0"
version = "0.4.0"
edition = "2021"
authors = ["Celeste Peláez <celeste@eufonia.studio>"]
description = "A library for authenticating with Microsoft accounts to access Minecraft services."
description = "A library for authenticating with Microsoft accounts to access online Minecraft services."
repository = "https://github.com/KernelFreeze/minecraft-msa-auth"
categories = ["authentication"]
rust-version = "1.67.1"
Expand All @@ -12,12 +12,18 @@ license = "MIT OR Apache-2.0"
[dependencies]
getset = "0.1.2"
nutype = { version = "0.3.1", features = ["serde"] }
reqwest = { version = "0.11.14", default-features = false, features = ["json"] }
serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.93"
thiserror = "1.0.38"
reqwest = { version = "0.11.22", default-features = false, features = ["json"] }
serde = { version = "1.0.189", features = ["derive"] }
serde_json = "1.0.107"
thiserror = "1.0.50"

[dev-dependencies]
oauth2 = { version = "4.3.0", default-features = false, features = ["reqwest", "rustls-tls"] }
reqwest = { version = "0.11.14", default-features = false, features = ["rustls-tls", "json"] }
tokio = { version = "1.25.0", features = ["full"] }
oauth2 = { version = "4.4.2", default-features = false, features = [
"reqwest",
"rustls-tls",
] }
reqwest = { version = "0.11.22", default-features = false, features = [
"rustls-tls",
"json",
] }
tokio = { version = "1.33.0", features = ["full"] }
108 changes: 61 additions & 47 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ use std::fmt::Debug;

use getset::{CopyGetters, Getters};
use nutype::nutype;
use reqwest::{Client as HttpClient, Response, StatusCode};
use reqwest::Client;
use serde::{Deserialize, Serialize};
use serde_json::json;
use thiserror::Error;

const MINECRAFT_LOGIN_WITH_XBOX: &str = "https://api.minecraftservices.com/authentication/login_with_xbox";
const XBOX_USER_AUTHERNITATE: &str = "https://user.auth.xboxlive.com/user/authenticate";
const XBOX_USER_AUTHENTICATE: &str = "https://user.auth.xboxlive.com/user/authenticate";
const XBOX_XSTS_AUTHORIZE: &str = "https://xsts.auth.xboxlive.com/xsts/authorize";

/// Represents a Minecraft access token
Expand All @@ -86,18 +86,21 @@ pub enum MinecraftTokenType {
/// Represents an error that can occur when authenticating with Minecraft.
#[derive(Error, Debug)]
pub enum MinecraftAuthorizationError {
#[error("Http error: {0}")]
Http(StatusCode, String),

/// An error occurred while sending the request
#[error(transparent)]
Reqwest(#[from] reqwest::Error),

/// Claims were missing from the response
#[error("missing claims from response")]
MissingClaims,
}

/// The response from Minecraft when attempting to authenticate with an xbox
/// token
#[derive(Deserialize, Serialize, Debug, Getters, CopyGetters, Clone)]
pub struct MinecraftAuthenticationResponse {
/// Some UUID of the account. Not the player's UUID
/// UUID of the Xbox account.
/// Please note that this is not the Minecraft player's UUID
#[getset(get = "pub")]
username: String,

Expand Down Expand Up @@ -129,12 +132,13 @@ struct XboxLiveAuthenticationResponse {
/// The flow for authenticating with a Microsoft access token and getting a
/// Minecraft access token.
pub struct MinecraftAuthorizationFlow {
http_client: HttpClient,
http_client: Client,
}

impl MinecraftAuthorizationFlow {
/// Creates a new [MinecraftAuthorizationFlow].
pub const fn new(http_client: HttpClient) -> Self {
/// Creates a new [MinecraftAuthorizationFlow] using the given
/// [Client].
pub const fn new(http_client: Client) -> Self {
Self { http_client }
}

Expand All @@ -144,28 +148,31 @@ impl MinecraftAuthorizationFlow {
pub async fn exchange_microsoft_token(
&self, microsoft_access_token: impl AsRef<str>,
) -> Result<MinecraftAuthenticationResponse, MinecraftAuthorizationError> {
let xbox_authenticate_json = json!({
"Properties": {
"AuthMethod": "RPS",
"SiteName": "user.auth.xboxlive.com",
"RpsTicket": &format!("d={}", microsoft_access_token.as_ref())
},
"RelyingParty": "http://auth.xboxlive.com",
"TokenType": "JWT"
});
let (xbox_token, user_hash) = self.xbox_token(microsoft_access_token).await?;
let xbox_security_token = self.xbox_security_token(xbox_token).await?;

let response = self
.http_client
.post(XBOX_USER_AUTHERNITATE)
.json(&xbox_authenticate_json)
.post(MINECRAFT_LOGIN_WITH_XBOX)
.json(&json!({
"identityToken":
format!(
"XBL3.0 x={user_hash};{xsts_token}",
user_hash = user_hash,
xsts_token = xbox_security_token.token
)
}))
.send()
.await?;
let response = error_for_status(response).await?;
let xbox_resp: XboxLiveAuthenticationResponse = response.json().await?;
response.error_for_status_ref()?;

let xbox_token = &xbox_resp.token;
let user_hash = &xbox_resp.display_claims["xui"][0]["uhs"];
let response = response.json().await?;
Ok(response)
}

async fn xbox_security_token(
&self, xbox_token: String,
) -> Result<XboxLiveAuthenticationResponse, MinecraftAuthorizationError> {
let response = self
.http_client
.post(XBOX_XSTS_AUTHORIZE)
Expand All @@ -179,34 +186,41 @@ impl MinecraftAuthorizationFlow {
}))
.send()
.await?;
let response = error_for_status(response).await?;
response.error_for_status_ref()?;
let xbox_security_token_resp: XboxLiveAuthenticationResponse = response.json().await?;
Ok(xbox_security_token_resp)
}

let xbox_security_token = &xbox_security_token_resp.token;
async fn xbox_token(
&self, microsoft_access_token: impl AsRef<str>,
) -> Result<(String, String), MinecraftAuthorizationError> {
let xbox_authenticate_json = json!({
"Properties": {
"AuthMethod": "RPS",
"SiteName": "user.auth.xboxlive.com",
"RpsTicket": &format!("d={}", microsoft_access_token.as_ref())
},
"RelyingParty": "http://auth.xboxlive.com",
"TokenType": "JWT"
});
let response = self
.http_client
.post(MINECRAFT_LOGIN_WITH_XBOX)
.json(&json!({
"identityToken":
format!(
"XBL3.0 x={user_hash};{xsts_token}",
user_hash = user_hash,
xsts_token = xbox_security_token
)
}))
.post(XBOX_USER_AUTHENTICATE)
.json(&xbox_authenticate_json)
.send()
.await?;
let response = error_for_status(response).await?;
let minecraft_resp: MinecraftAuthenticationResponse = response.json().await?;
Ok(minecraft_resp)
}
}

async fn error_for_status(response: Response) -> Result<Response, MinecraftAuthorizationError> {
let status = response.status();
if !status.is_success() {
let response = response.text().await?;
return Err(MinecraftAuthorizationError::Http(status, response));
response.error_for_status_ref()?;
let xbox_resp: XboxLiveAuthenticationResponse = response.json().await?;
let xbox_token = xbox_resp.token;
let user_hash = xbox_resp
.display_claims
.get("xui")
.ok_or(MinecraftAuthorizationError::MissingClaims)?
.get(0)

Check warning on line 219 in src/lib.rs

View workflow job for this annotation

GitHub Actions / Lint with Clippy

cargo-clippy: accessing first element with `xbox_resp .display_claims .get("xui") .ok_or(MinecraftAuthorizationError::MissingClaims)?.get(0)`

warning: accessing first element with `xbox_resp .display_claims .get("xui") .ok_or(MinecraftAuthorizationError::MissingClaims)?.get(0)` --> src/lib.rs:215:25 | 215 | let user_hash = xbox_resp | _________________________^ 216 | | .display_claims 217 | | .get("xui") 218 | | .ok_or(MinecraftAuthorizationError::MissingClaims)? 219 | | .get(0) | |___________________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#get_first = note: `#[warn(clippy::get_first)]` on by default help: try | 215 ~ let user_hash = xbox_resp 216 + .display_claims 217 + .get("xui") 218 + .ok_or(MinecraftAuthorizationError::MissingClaims)?.first() |
.ok_or(MinecraftAuthorizationError::MissingClaims)?
.get("uhs")
.ok_or(MinecraftAuthorizationError::MissingClaims)?
.to_owned();
Ok((xbox_token, user_hash))
}
Ok(response)
}

0 comments on commit 57ea697

Please sign in to comment.