ammaraskar / pyCraft

Minecraft-client networking library in Python
Other
817 stars 184 forks source link

Microsoft Accounts #234

Open martinpugdal opened 3 years ago

martinpugdal commented 3 years ago

I think u need to update ur authentication.py file to get microsoft accounts to work with that. U can example do that authentication.AuthenticationToken().authenticate(username, password, microsoft_account=True).

MiniDigger commented 3 years ago

sadly its not so straight forward, cause its kinda expected that you do this interactively, I am not sure if MSA has flows enabled that allow username password authentication like that. see https://wiki.vg/Microsoft_Authentication_Scheme

edit: seems like I am wrong, prismarine is able to use device code flows https://github.com/PrismarineJS/node-minecraft-protocol/blob/master/src/client/microsoftAuth.js

TheStaticTurtle commented 3 years ago

Actually PrismarineJS doesn't use the device code flow by default, it only does if it can't authenticate on Xbox live with a username / password

Over the last few days I've been developing a custom Minecraft proxy and I needed Microsoft auth for someone, so I tried to implement it. So here is my python implementation of the xbox live auth: https://github.com/TheStaticTurtle/MineProxy/blob/master/networking/McAuth/MicrosoftAuth.py It's based on PrismarineJS and the xbox-live-auth module, and it seems to work for the person that I wrote it for, but it might have some bugs still

Actually, my implementation is somewhat compatible with the one of pyCraft, so it might work right away (provided that you only call authenticate and join_server)

martinpugdal commented 3 years ago

Actually PrismarineJS doesn't use the device code flow by default, it only does if it can't authenticate on Xbox live with a username / password

Over the last few days I've been developing a custom Minecraft proxy and I needed Microsoft auth for someone, so I tried to implement it. So here is my python implementation of the xbox live auth: https://github.com/TheStaticTurtle/MineProxy/blob/master/networking/McAuth/MicrosoftAuth.py It's based on PrismarineJS and the xbox-live-auth module, and it seems to work for the person that I wrote it for, but it might have some bugs still

Actually, my implementation is somewhat compatible with the one of pyCraft, so it might work right away (provided that you only call authenticate and join_server)

Hmmm, so how can I use that, because I tried a lot stuff with your implementation and I can't get that to work. Can you maybe make an example to use that with pycraft?

TheStaticTurtle commented 3 years ago

Actually, I meant compatible as in "doesn't need to rewrite everything that uses the auth_token variable", my token classes use pretty much the same functions as the current one, so it might work, but I'm not sure how seamless it would be

That said, if something were to work, it would look something like this:

auth_token= MicrosoftAuthenticationToken()
auth_token.authenticate("email", "password")

connection = Connection("127.0.0.1", 25565, auth_token=auth_token)
...

Just for context, I don't actually know how to use pycraft even tho I read a lot of its code for my project, so there might be something I missed.

tropicbliss commented 3 years ago

I have a similar process that gets the bearer token (drawback is that it ofc doesn't support 2FA enabled accounts). It's in rust but it should be easy enough to understand. https://github.com/tropicbliss/xboxlive-auth/blob/main/src/xbox.rs

mckuhei commented 2 years ago

I made it on Python! Just change a little for it microsoft_login.py

tcrch commented 2 years ago

I made it on Python! Just change a little for it microsoft_login.py

Links not working. Anyone else has any clue about the Microsoft Login situation?

Acurisu commented 2 years ago

Links not working. Anyone else has any clue about the Microsoft Login situation?

If you are still struggling you can use helper/Auth.py as an example on how to implement it using a local http server and your default webbrowser.

get_auth_code gets the auth_code which you then can pass to authenticate which then returns the auth_token which can be passed to Connection.

The reason I implemented it in two steps was that a server for which one only has console access does not provide a default browser. See README#server.

And as the usual you need an Azure App for this to work to begin with. See README#microsoft-login.

mckuhei commented 2 years ago

I made it on Python! Just change a little for it microsoft_login.py

Links not working. Anyone else has any clue about the Microsoft Login situation?

You need register a toke. For more information, See wiki.vg

zty012 commented 1 year ago

I made it on Python! Just change a little for it microsoft_login.py

404?

zty012 commented 1 year ago

@martinersej #253 U can add this code to ur authentication.py

```python # ... imports import os # other code..... class Microsoft_AuthenticationToken(object): """ Represents an authentication token. See https://wiki.vg/Microsoft_Authentication_Scheme. """ UserLoginURL = "https://login.live.com/oauth20_authorize.srf?\ client_id=00000000402b5328&response_type=code\ &scope=service%3A%3Auser.auth.xboxlive.com%3A%3AMBI_SSL&redirect_uri=\ https%3A%2F%2Flogin.live.com%2Foauth20_desktop.srf" oauth20_URL = 'https://login.live.com/oauth20_token.srf' XBL_URL = 'https://user.auth.xboxlive.com/user/authenticate' XSTS_URL = 'https://xsts.auth.xboxlive.com/xsts/authorize' LOGIN_WITH_XBOX_URL = "https://api.minecraftservices.com/\ authentication/login_with_xbox" CheckAccount_URL = 'https://api.minecraftservices.com/entitlements/mcstore' Profile_URL = 'https://api.minecraftservices.com/minecraft/profile' jwt_Token = '' def __init__(self, access_token=None): self.access_token = access_token self.profile = Profile() def GetoAuth20(self, code='') -> object: if code == '': print("Please copy this link to your browser to open:" "\n%s" % self.UserLoginURL) code = input( "After logging in," "paste the 'code' field in your browser's address bar here:") oauth20 = requests.post( self.oauth20_URL, data={ "client_id": "00000000402b5328", "code": "{}".format(code), "grant_type": "authorization_code", "redirect_uri": "https://login.live.com/oauth20_desktop.srf", "scope": "service::user.auth.xboxlive.com::MBI_SSL" }, headers={"content-type": "application/x-www-form-urlencoded"}, timeout=15) oauth20 = json.loads(oauth20.text) if 'error' in oauth20: print("Error: %s" % oauth20["error"]) return 1 else: self.oauth20_access_token = oauth20['access_token'] self.oauth20_refresh_token = oauth20['refresh_token'] oauth20_access_token = oauth20['access_token'] oauth20_refresh_token = oauth20['refresh_token'] return { "access_token": oauth20_access_token, "refresh_token": oauth20_refresh_token } def GetXBL(self, access_token: str) -> object: XBL = requests.post(self.XBL_URL, json={ "Properties": { "AuthMethod": "RPS", "SiteName": "user.auth.xboxlive.com", "RpsTicket": "{}".format(access_token) }, "RelyingParty": "http://auth.xboxlive.com", "TokenType": "JWT" }, headers=HEADERS, timeout=15) return { "Token": json.loads(XBL.text)['Token'], "uhs": json.loads(XBL.text)['DisplayClaims']['xui'][0]['uhs'] } def GetXSTS(self, access_token: str) -> object: XBL = requests.post(self.XSTS_URL, json={ "Properties": { "SandboxId": "RETAIL", "UserTokens": ["{}".format(access_token)] }, "RelyingParty": "rp://api.minecraftservices.com/", "TokenType": "JWT" }, headers=HEADERS, timeout=15) return { "Token": json.loads(XBL.text)['Token'], "uhs": json.loads(XBL.text)['DisplayClaims']['xui'][0]['uhs'] } def GetXBOX(self, access_token: str, uhs: str) -> str: mat_jwt = requests.post( self.LOGIN_WITH_XBOX_URL, json={"identityToken": "XBL3.0 x={};{}".format(uhs, access_token)}, headers=HEADERS, timeout=15) self.access_token = json.loads(mat_jwt.text)['access_token'] return self.access_token def CheckAccount(self, jwt_Token: str) -> bool: CheckAccount = requests.get( self.CheckAccount_URL, headers={"Authorization": "Bearer {}".format(jwt_Token)}, timeout=15) CheckAccount = len(json.loads(CheckAccount.text)['items']) if CheckAccount != 0: return True else: return False def GetProfile(self, access_token: str) -> object: if self.CheckAccount(access_token): Profile = requests.get( self.Profile_URL, headers={"Authorization": "Bearer {}".format(access_token)}, timeout=15) Profile = json.loads(Profile.text) if 'error' in Profile: return False self.profile.id_ = Profile["id"] self.profile.name = Profile["name"] self.username = Profile["name"] return True else: return False @property def authenticated(self): """ Attribute which is ``True`` when the token is authenticated and ``False`` when it isn't. """ if not self.username: return False if not self.access_token: return False if not self.oauth20_refresh_token: return False if not self.profile: return False return True def authenticate(self): "Get verification information for a Microsoft account" oauth20 = self.GetoAuth20() if oauth20 == 1: return False XBL = self.GetXBL(oauth20['access_token']) XSTS = self.GetXSTS(XBL['Token']) XBOX = self.GetXBOX(XSTS['Token'], XSTS['uhs']) if self.GetProfile(XBOX): print('GameID: {}'.format(self.profile.id_)) self.PersistenceLogoin_w() return True else: print('Account does not exist') return False def refresh(self): """ Refreshes the `AuthenticationToken`. Used to keep a user logged in between sessions and is preferred over storing a user's password in a file. Returns: Returns `True` if `AuthenticationToken` was successfully refreshed. Otherwise it raises an exception. Raises: minecraft.exceptions.YggdrasilError ValueError - if `AuthenticationToken.access_token` or `AuthenticationToken.client_token` isn't set. """ if self.access_token is None: raise ValueError("'access_token' not set!'") if self.oauth20_refresh_token is None: raise ValueError("'oauth20_refresh_token' is not set!") oauth20 = requests.post( self.oauth20_URL, data={ "client_id": "00000000402b5328", "refresh_token": "{}".format(self.oauth20_refresh_token), "grant_type": "refresh_token", "redirect_uri": "https://login.live.com/oauth20_desktop.srf", "scope": "service::user.auth.xboxlive.com::MBI_SSL" }, headers={"content-type": "application/x-www-form-urlencoded"}, timeout=15) oauth20 = json.loads(oauth20.text) if 'error' in oauth20: print("Error: %s" % oauth20["error"]) return False else: self.oauth20_access_token = oauth20['access_token'] self.oauth20_refresh_token = oauth20['refresh_token'] XBL = self.GetXBL(self.oauth20_access_token) XSTS = self.GetXSTS(XBL['Token']) XBOX = self.GetXBOX(XSTS['Token'], XSTS['uhs']) if self.GetProfile(XBOX): self.PersistenceLogoin_w() print('account: {}'.format(self.profile.id_)) return True else: print('Account does not exist') return False def join(self, server_id): """ Informs the Mojang session-server that we're joining the MineCraft server with id ``server_id``. Parameters: server_id - ``str`` with the server id Returns: ``True`` if no errors occured Raises: :class:`minecraft.exceptions.YggdrasilError` """ if not self.authenticated: err = "AuthenticationToken hasn't been authenticated yet!" raise YggdrasilError(err) res = _make_request( SESSION_SERVER, "join", { "accessToken": self.access_token, "selectedProfile": self.profile.to_dict(), "serverId": server_id }) if res.status_code != 204: _raise_from_response(res) return True def PersistenceLogoin_w(self): "Save access token persistent login" ProjectDir = os.path.dirname(os.path.dirname('{}'.format(__file__))) PersistenceDir = '{}/Persistence'.format(ProjectDir) if not self.authenticated: err = "AuthenticationToken hasn't been authenticated yet!" raise YggdrasilError(err) if not os.path.exists(PersistenceDir): os.mkdir(PersistenceDir) print(PersistenceDir) "Save access_token and oauth20_refresh_token" with open("{}/{}".format(PersistenceDir, self.username), mode='w', encoding='utf-8') as file_obj: file_obj.write('{{"{}": "{}","{}": "{}"}}'.format( 'access_token', self.access_token, 'oauth20_refresh_token', self.oauth20_refresh_token)) file_obj.close() return True def PersistenceLogoin_r(self, GameID: str): "Load access token persistent login" ProjectDir = os.path.dirname(os.path.dirname('{}'.format(__file__))) PersistenceDir = '{}/Persistence'.format(ProjectDir) if not os.path.exists(PersistenceDir): return False "Load access_token and oauth20_refresh_token" if os.path.isfile("{}/{}".format(PersistenceDir, GameID)): with open("{}/{}".format(PersistenceDir, GameID), mode='r', encoding='utf-8') as file_obj: Persistence = file_obj.read() file_obj.close() Persistence = json.loads(Persistence) self.access_token = Persistence["access_token"] self.oauth20_refresh_token = Persistence[ "oauth20_refresh_token"] self.refresh() return self.authenticated else: return False ```

Then u can

```python auth = minecraft.authentication.Microsoft_AuthenticationToken() auth.authenticate() conn = minecraft.networking.connection.Connection("mc.hypixel.net", 25565, auth, None, "1.8") ```