Skip to content

Instantly share code, notes, and snippets.

@rbrick
Last active February 9, 2024 05:02
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rbrick/be8ed86864fc5d77aa6c979053cfc892 to your computer and use it in GitHub Desktop.
Save rbrick/be8ed86864fc5d77aa6c979053cfc892 to your computer and use it in GitHub Desktop.
package main
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"fmt"
"golang.org/x/oauth2"
"golang.org/x/oauth2/microsoft"
"io/ioutil"
"math/rand"
"net/http"
"strconv"
"time"
)
func main() {
msConfig := &oauth2.Config{
ClientID: "CLIENT_ID",
ClientSecret: "CLIENT_SECRET",
Endpoint: microsoft.LiveConnectEndpoint,
RedirectURL: "http://localhost",
Scopes: []string{"XboxLive.signin", "XboxLive.offline_access"},
}
state := "lol" + strconv.Itoa(int(rand.Int63()))
rand.Seed(time.Now().UnixNano())
fmt.Println(msConfig.AuthCodeURL(state))
x := make(chan *oauth2.Token)
// This is a simple HTTP server for simply relaying the token exchange
// I will provide a better practical example for in production solutions *hopefully* soon if someone doesn't beat me to it
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
// verify that it is the correct state
if state == request.URL.Query().Get("state") {
// obtain the code from the URL and make the exchange to get the token.
token, err := msConfig.Exchange(context.Background(), request.URL.Query().Get("code"))
if err != nil {
panic(err)
}
// pass it along to the channel to actually use the token
x <- token
}
})
go func() {
http.ListenAndServe(":80", nil)
}()
select {
case token := <-x:
{
client := msConfig.Client(context.Background(), token)
client.Transport = &http.Transport{
TLSClientConfig: &tls.Config{
Renegotiation: tls.RenegotiateOnceAsClient,
InsecureSkipVerify: true,
},
}
data := map[string]interface{}{
"RelyingParty": "http://auth.xboxlive.com",
"TokenType": "JWT",
"Properties": map[string]interface{}{
"AuthMethod": "RPS",
"SiteName": "user.auth.xboxlive.com",
"RpsTicket": "d=" + token.AccessToken,
},
}
encoded, _ := json.Marshal(data)
request, _ := http.NewRequest("POST", "https://user.auth.xboxlive.com/user/authenticate", bytes.NewReader(encoded))
request.Header.Set("Content-Type", "application/json")
request.Header.Set("Accept", "application/json")
request.Header.Set("x-xbl-contract-version", "1")
resp, err := client.Do(request)
if err != nil {
fmt.Println(err)
} else {
b, _ := ioutil.ReadAll(resp.Body)
var data map[string]interface{}
json.Unmarshal(b, &data)
// lol this is terrible. use a struct
uhs := data["DisplayClaims"].(map[string]interface{})["xui"].([]interface{})[0].(map[string]interface{})["uhs"].(string)
token := data["Token"].(string)
body := map[string]interface{}{
"Properties": map[string]interface{}{
"SandboxId": "RETAIL",
"UserTokens": []string{
token,
},
},
"RelyingParty": "rp://api.minecraftservices.com/",
"TokenType": "JWT",
}
encoded, _ := json.Marshal(body)
request, _ := http.NewRequest("POST", "https://xsts.auth.xboxlive.com/xsts/authorize", bytes.NewReader(encoded))
request.Header.Set("Content-Type", "application/json")
request.Header.Set("Accept", "application/json")
request.Header.Set("x-xbl-contract-version", "1")
resp, err := client.Do(request)
if err != nil {
fmt.Println(err)
} else {
b, _ := ioutil.ReadAll(resp.Body)
var jsonResponse map[string]interface{}
json.Unmarshal(b, &jsonResponse)
// Hold on to this one
xstsToken := jsonResponse["Token"].(string)
body := map[string]interface{}{}
body["identityToken"] = "XBL3.0 x=" + uhs + ";" + xstsToken
body["ensureLegacyEnabled"] = true
//
encoded, _ := json.Marshal(body)
request, _ := http.NewRequest("POST", "https://api.minecraftservices.com/authentication/login_with_xbox", bytes.NewReader(encoded))
response, _ := client.Do(request)
// This is minecraft's token
minecraftResponse, _ := ioutil.ReadAll(response.Body)
fmt.Println(string(minecraftResponse))
}
}
}
}
}
@overestimate
Copy link

How exactly does this work?

It creates a http server for the token to get logged to, but how does it get back to the client?

Uses a channel to send the token from the router function to the actual authentication function. If you know how OAuth2 works, it's redirecting the browser to localhost:80/?code=xxxx

@OOPSgary
Copy link

OOPSgary commented Feb 9, 2024

A PKCE seem to be required for this auth, but how?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment