Skip to content

Instantly share code, notes, and snippets.

@yonderbread
Created December 14, 2020 20:59
Show Gist options
  • Save yonderbread/e0dc075213314aad3462c3954ccdcc08 to your computer and use it in GitHub Desktop.
Save yonderbread/e0dc075213314aad3462c3954ccdcc08 to your computer and use it in GitHub Desktop.
New Minecraft authentication implemented in Python
# Microsoft Authentication Scheme
# https://wiki.vg/Microsoft_Authentication_Scheme
#
# DEV NOTE: Might switch to using xbox-webapi-python
# instead later on.
# https://github.com/OpenXbox/xbox-webapi-python
import requests
MS_OAUTH2_URL = "https://login.live.com/oauth20_authorize.srf"
XBL_AUTHENTICATE = "https://user.auth.xboxlive.com/user/authenticate"
XBL_LOGIN = "https://api.minecraftservices.com/authentication/login_with_xbox"
XSTS_AUTHENTICATE = "https://xsts.auth.xboxlive.com/xsts/authorize"
API_ENTITLEMENTS_URL = "https://api.minecraftservices.com/entitlements/mcstore"
API_PROFILE_URL = "https://api.minecraftservices.com/minecraft/profile"
class MicrosoftOAuth2:
def __init__(self, client_id: str, client_secret: str, redirect_uri: str, oauth2_scope: str = None):
self.client_id = client_id
self.client_secret = client_secret
self.redirect_uri = redirect_uri
self.oauth2_scope = oauth2_scope
self.oauth2_url = None
self.access_token = None
self.refresh_token = None
self.auth_token = None
"""
Builds URL for user to visit in order to authenticate
"""
def get_oauth2_url(self, response_type: str = "code"):
url = MS_OAUTH2_URL
url += "?client_id=" + self.client_id
url += "&response_type=" + response_type
url += "&redirect_uri=" + self.redirect_uri
if self.oauth2_scope is None:
url += "&scope=XboxLive.signin"
self.oauth2_url = url
return url
"""
Appends extra url parameters to redirect url for returning an auth code at callback
"""
def get_redirect(self, code: str, state: str = None):
url = "https://" + self.redirect_uri
url += "?code=" + code
if state is not None:
url += "&state=" + state
return url
"""
Fetches auth token and refresh token
"""
def get_authorization_code(self, oauth2_url: str = None):
oauth2_url = self.oauth2_url if not oauth2_url else oauth2_url
oauth2_url += "&grant_type=authorization_code"
with requests.post(oauth2_url, headers={"Content-Type": "w-xxx-form-urlencoded"}) as req:
req.raise_for_status()
data = req.json()
self.auth_token = data["access_token"]
self.refresh_token = data["refresh_token"]
return req.json()
"""
Uses refresh token to revalidate access token
"""
def refresh(self, refresh_token: str = None, oauth2_url: str = None):
refresh_token = self.refresh_token if not refresh_token else refresh_token
oauth2_url = self.oauth2_url if not oauth2_url else oauth2_url
oauth2_url += "&grant_type=refresh_token"
oauth2_url += "&refresh_token=" + refresh_token
with requests.post(oauth2_url, headers={"Context-Type": "w-xxx-form-urlencoded"}) as req:
req.raise_for_status()
return req.json()
"""
Authenticates via Oauth2 to Xbox Live authentication servers
Returns an XBL token required later in the authentication process
"""
def auth_xbl(self):
if not self.auth_token:
raise Exception("Cannot authenticate Oauth2 user without a valid authentication token/rps ticket.")
payload = {
"Properties": {"AuthMethod": "RPS", "SiteName": "user.auth.xboxlive.com", "RpsTicket": self.auth_token},
"RelyingParty": "http://auth.xboxlive.com",
"TokenType": "JWT",
}
with requests.post(
XBL_AUTHENTICATE, headers={"Content-Type": "application/json", "Accept": "application/json"}, data=payload
) as req:
req.raise_for_status()
return req.json()
"""
Authenticates via Oauth2 to XSTS authentication servers
Fetches the users access token
"""
def auth_xsts(self, xbl_token: str):
payload = {
"Properties": {"SandboxId": "RETAIL", "UserTokens": [xbl_token]},
"RelyingParty": "rp://api.minecraftservices.com/",
"TokenType": "JWT",
}
with requests.post(
XSTS_AUTHENTICATE, headers={"Content-Type": "application/json", "Accept": "application/json"}, data=payload
) as req:
req.raise_for_status()
data = req.json()
self.access_token = data["access_token"]
return data
"""
Login to Minecraft
Retrieves the user's game access token to launch as an 'online' player
"""
@staticmethod
def login_with_xbox(self, xsts_token: str, uhs: str):
payload = {"identityToken": f"XBL3.0 x={uhs};{xsts_token}"}
with requests.post(
XBL_LOGIN, headers={"Content-Type": "application/json", "Accept": "application/json"}, data=payload
) as req:
req.raise_for_status()
return req.json()
"""
Checks the user's store entitlements to see if Minecraft has been purchased
"""
def check_game_ownership(self):
if not self.access_token:
raise Exception("Cannot authenticate Oauth2 user without an access token.")
with requests.get(
API_ENTITLEMENTS_URL, headers={"Accept": "application/json", "Authorization": f"Bearer {self.access_token}"}
) as req:
req.raise_for_status()
data = req.json()
if "items" not in data:
return False
return True
"""
Fetches various information about the player
"""
def get_profile(self):
if not self.access_token:
raise Exception("Cannot authenticate Oauth2 user without an access token.")
with requests.get(
API_ENTITLEMENTS_URL, headers={"Accept": "application/json", "Authorization": f"Bearer {self.access_token}"}
) as req:
req.raise_for_status()
data = req.json()
if req.status_code == 401 or "error" in data:
raise Exception("Unauthorized, cannot fetch profile information")
return data
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment