Created
September 12, 2021 15:19
-
-
Save Plagiatus/ce5f18bc010395fc45d8553905e10f55 to your computer and use it in GitHub Desktop.
A basic implementation for a full flow of Microsoft-Login -> Minecraft Auth + Player Info. No error handling included for easier understandability.
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
import { XMLHttpRequest } from "xmlhttprequest"; | |
interface AuthorizationTokenResponse { | |
token_type: string, | |
expires_in: number, | |
scope: string, | |
access_token: string, | |
refresh_token: string, | |
user_id: string, | |
foci: string | |
} | |
interface XboxServiceTokenResponse { | |
IssueInstant: string, | |
NotAfter: string, | |
Token: string, | |
DisplayClaims: DisplayClaim; | |
} | |
interface MCTokenResponse { | |
username: string, | |
roles: any[], | |
access_token: string, | |
token_type: string, | |
expires_in: number | |
} | |
interface MCUserInfo { | |
id: string, | |
name: string, | |
skins: MCSkinInfo[], | |
capes: MCCapeInfo[] | |
} | |
interface MCInfo { | |
id: string, | |
state: "ACTIVE" | "INACTIVE", | |
url: string, | |
} | |
interface MCSkinInfo extends MCInfo { | |
variant: string, | |
} | |
interface MCCapeInfo extends MCInfo { | |
alias: string, | |
} | |
interface AuthInfo { | |
auth_token: AuthorizationTokenResponse, | |
xbox_token: XboxServiceTokenResponse, | |
xsts_token: XboxServiceTokenResponse, | |
mc_token: MCTokenResponse, | |
mc_info: MCUserInfo | |
} | |
interface DisplayClaim { | |
xui: { | |
uhs: string; | |
}[] | |
} | |
export default class AuthenticationHandler { | |
private clientId: string; | |
private clientSecret: string; | |
private redirectUri: string; | |
constructor(clientID: string, clientSecret: string, redirectUri: string) { | |
if (!clientID) throw new Error("clientID is required"); | |
this.clientId = clientID; | |
if (!clientSecret) throw new Error("clientSecret is required"); | |
this.clientSecret = clientSecret; | |
if (!redirectUri) throw new Error("redirectUri is required"); | |
this.redirectUri = redirectUri; | |
} | |
public get forwardUrl(): string { | |
let url: string = `https://login.live.com/oauth20_authorize.srf?client_id=${this.clientId}&response_type=code&redirect_uri=${this.redirectUri}&scope=XboxLive.signin%20offline_access`; | |
return url; | |
} | |
public async getAuthCodes(code: string, refresh: boolean = false): Promise<AuthInfo> { | |
if (!code) throw Error("No Code provided."); | |
let authToken: AuthorizationTokenResponse = await this.authCodeToAuthToken(code, refresh); | |
let xbl: XboxServiceTokenResponse = await this.authTokenToXBL(authToken); | |
let xsts: XboxServiceTokenResponse = await this.xblToXsts(xbl); | |
let mcToken: MCTokenResponse = await this.xstsToMc(xsts); | |
let mcInfo: MCUserInfo = await this.getMCInfo(mcToken); | |
return { | |
auth_token: authToken, | |
mc_info: mcInfo, | |
mc_token: mcToken, | |
xbox_token: xbl, | |
xsts_token: xsts | |
} | |
} | |
private async authCodeToAuthToken(code: string, refresh: boolean): Promise<AuthorizationTokenResponse> { | |
let request = new XMLHttpRequest(); | |
let data = "client_id=" + this.clientId + | |
"&client_secret=" + this.clientSecret + | |
"&redirect_uri=" + this.redirectUri; | |
if (refresh) { | |
data += "&refresh_token=" + code + | |
"&grant_type=refresh_token"; | |
} else { | |
data += "&code=" + code + | |
"&grant_type=authorization_code"; | |
} | |
request.open("POST", "https://login.live.com/oauth20_token.srf", false); | |
request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); | |
let promise: Promise<AuthorizationTokenResponse> = this.waitForRequestText(request); | |
request.send(data); | |
return promise; | |
} | |
private async authTokenToXBL(authToken: AuthorizationTokenResponse): Promise<XboxServiceTokenResponse> { | |
let request = new XMLHttpRequest(); | |
let data = `{ | |
"Properties": { | |
"AuthMethod": "RPS", | |
"SiteName": "user.auth.xboxlive.com", | |
"RpsTicket": "d=${authToken.access_token}" | |
}, | |
"RelyingParty": "http://auth.xboxlive.com", | |
"TokenType": "JWT" | |
}`; | |
request.open("POST", "https://user.auth.xboxlive.com/user/authenticate", false); | |
request.setRequestHeader("Content-Type", "application/json"); | |
request.setRequestHeader("Accept", "application/json"); | |
let promise: Promise<XboxServiceTokenResponse> = this.waitForRequestText(request); | |
request.send(data); | |
return promise; | |
} | |
private async xblToXsts(token: XboxServiceTokenResponse): Promise<XboxServiceTokenResponse> { | |
let request = new XMLHttpRequest(); | |
let data = `{ | |
"Properties": { | |
"SandboxId": "RETAIL", | |
"UserTokens": [ | |
"${token.Token}" | |
] | |
}, | |
"RelyingParty": "rp://api.minecraftservices.com/", | |
"TokenType": "JWT" | |
}`; | |
request.open("POST", "https://xsts.auth.xboxlive.com/xsts/authorize", false); | |
request.setRequestHeader("Content-Type", "application/json"); | |
request.setRequestHeader("Accept", "application/json"); | |
let promise: Promise<XboxServiceTokenResponse> = this.waitForRequestText(request); | |
request.send(data); | |
return promise; | |
} | |
private async xstsToMc(token: XboxServiceTokenResponse): Promise<MCTokenResponse> { | |
let request = new XMLHttpRequest(); | |
let data = `{ | |
"identityToken": "XBL3.0 x=${token.DisplayClaims.xui[0].uhs};${token.Token}" | |
}`; | |
request.open("POST", "https://api.minecraftservices.com/authentication/login_with_xbox", false); | |
request.setRequestHeader("Content-Type", "application/json"); | |
request.setRequestHeader("Accept", "application/json"); | |
let promise: Promise<MCTokenResponse> = this.waitForRequestText(request); | |
request.send(data); | |
return promise; | |
} | |
private async getMCInfo(mc_token: MCTokenResponse): Promise<MCUserInfo> { | |
let request = new XMLHttpRequest(); | |
request.open("GET", "https://api.minecraftservices.com/minecraft/profile"); | |
request.setRequestHeader("Authorization", "Bearer " + mc_token.access_token); | |
let promise: Promise<MCUserInfo> = this.waitForRequestText(request); | |
request.send(); | |
return promise; | |
} | |
private async waitForRequestText(req: XMLHttpRequest): Promise<any> { | |
return new Promise((resolve, reject) => { | |
req.addEventListener("readystatechange", () => { | |
if (req.readyState == 4) { | |
if (req.status == 200) { | |
resolve(JSON.parse(req.responseText)); | |
} else { | |
console.error(req.status, req.statusText); | |
reject(); | |
} | |
} | |
}); | |
}); | |
} | |
} |
hallowxd
commented
Sep 30, 2024
@javajuicer it's a nodejs implementation, so you'll need to install the xmlhttprequest module. See https://www.npmjs.com/package/xmlhttprequest.
Though xmlhttprequest is sorta outdated and unmaintained, so you might want to substitute it for a more modern approach like the xhr2 module or the natively implemented fetch: https://nodejs.org/en/blog/announcements/v18-release-announce/#fetch-experimental
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment