https://wiki.vg/api.php?action=feedcontributions&user=MrAlisYT&feedformat=atomwiki.vg - User contributions [en]2024-03-29T13:47:47ZUser contributionsMediaWiki 1.34.4https://wiki.vg/index.php?title=Microsoft_Authentication_Scheme&diff=16586Microsoft Authentication Scheme2021-04-19T18:09:01Z<p>MrAlisYT: /* Authenticate with XBL */ t=<access_token> not working now, d=<access_token> works</p>
<hr />
<div>Minecraft is moving to Microsoft accounts. Starting December 2020, all new Accounts already use the new system, old accounts will be migrated later, see [https://www.minecraft.net/en-us/article/java-edition-moving-house this blog post]<br />
<br />
There are multiple steps and different tokens required, but in the end, you get a normal Minecraft token back. Launching the game itself hasn't changed.<br />
<br />
== Microsoft OAuth Flow ==<br />
[[File:minecraft_login.png|frame|Example of the login page]]<br />
<br />
Prior to any of these steps, you will first need to obtain an [https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow OAuth 2.0] Client ID & secret by creating a [https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app Microsoft Azure application].<br />
<br />
In the first step, we are logging into the Microsoft account. This has to be done in a browser/webview!<br />
<br />
The URL generated, either manually, or through an OAuth2 library, would look something like this:<br />
<br />
https://login.live.com/oauth20_authorize.srf<br />
?client_id=<your Azure client ID><br />
&response_type=code<br />
&redirect_uri=<your redirect uri><br />
&scope=XboxLive.signin%20offline_access //without offline_access you won't get an refresh_token<br />
&state=<optional; used to prevent CSRF & restoring previous application states><br />
<br />
<i>Note: You may also use the Azure Active Directory endpoints which would look like https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize but this just redirects to the live.com URL.</i><br />
<br />
The user will be prompted to enter a username (E-Mail, Skype ID, Phone number, whatever) and their password. If those are valid, the user will be redirected. The user doesn't need to own MC, that check comes way later!<br />
<br />
The redirect will look something like this<br />
https://<your redirect URL>?code=codegoeshere&state=<optional; only if provided><br />
<br />
You have to extract the code param, it's your Microsoft Authorization Code.<br />
<br />
== Authorization Code -> Authorization Token ==<br />
<br />
The next step is to get an access token from the auth code. This isn't done in the browser for security reasons.<br />
<br />
<i>This exchange should be done on the server-side as it requires the use of your client secret which, as the name implies, should be kept secret.</i><br />
<br />
POST https://login.live.com/oauth20_token.srf<br />
client_id=<your client id><br />
&client_secret=<your client secret><br />
&code=<auth code / the code from step 1><br />
&grant_type=authorization_code<br />
&redirect_uri=<your redirect uri><br />
<br />
Don't forget to set <code>Content-Type: application/x-www-form-urlencoded</code><br />
<br />
The response will look like this:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"token_type":"bearer",<br />
"expires_in":86400,<br />
"scope":"XboxLive.signin",<br />
"access_token":"token here",<br />
"refresh_token":"M.R3_BAY.token here",<br />
"user_id":"889ed4a3d844f672",<br />
"foci":"1"<br />
}<br />
</syntaxhighlight><br />
<br />
We care about the access_token here. <br />
<br />
== Refreshing Tokens ==<br />
You can use the refresh_token you got in the previous request to acquire a new access_token. Just call the same endpoint as before, switching out code for the refresh token and grant type <code>authorization_code</code> for <code>refresh_token</code><br />
<br />
POST https://login.live.com/oauth20_token.srf<br />
client_id=<your client id><br />
&client_secret=<your client secret><br />
&refresh_token=<refresh token from previous step><br />
&grant_type=refresh_token<br />
&redirect_uri=<your redirect uri><br />
<br />
(the response is the same as for requesting tokens with a code)<br />
<br />
== Authenticate with XBL ==<br />
<br />
Now that we are authenticated with Microsoft, we can authenticate to Xbox Live.<br />
<br />
To do that, we send<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
POST https://user.auth.xboxlive.com/user/authenticate<br />
{<br />
"Properties": {<br />
"AuthMethod": "RPS",<br />
"SiteName": "user.auth.xboxlive.com",<br />
"RpsTicket": "d=<access_token>" // your access token from step 2 here<br />
},<br />
"RelyingParty": "http://auth.xboxlive.com",<br />
"TokenType": "JWT"<br />
}<br />
</syntaxhighlight><br />
<br />
Again, it will complain if you don't set <code>Content-Type: application/json</code> and <code>Accept: application/json</code><br />
<br />
The response will look like this:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"IssueInstant":"2020-12-07T19:52:08.4463796Z",<br />
"NotAfter":"2020-12-21T19:52:08.4463796Z",<br />
"Token":"token", // save this, this is your xbl token<br />
"DisplayClaims":{<br />
"xui":[<br />
{<br />
"uhs":"userhash" // save this<br />
}<br />
]<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
We need to save token and userhash.<br />
<br />
== Authenticate with XSTS ==<br />
<br />
Now that we are authenticated with XBL, we need to get a XSTS token, we can use to login to Minecraft.<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
POST https://xsts.auth.xboxlive.com/xsts/authorize<br />
{<br />
"Properties": {<br />
"SandboxId": "RETAIL",<br />
"UserTokens": [<br />
"xbl_token" // from above<br />
]<br />
},<br />
"RelyingParty": "rp://api.minecraftservices.com/",<br />
"TokenType": "JWT"<br />
}<br />
</syntaxhighlight><br />
<br />
Again, set content type and accept to json.<br />
<br />
Response will look like this:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"IssueInstant":"2020-12-07T19:52:09.2345095Z",<br />
"NotAfter":"2020-12-08T11:52:09.2345095Z",<br />
"Token":"token", // save this, this is your xsts token<br />
"DisplayClaims":{<br />
"xui":[<br />
{<br />
"uhs":"userhash" // same as last request<br />
}<br />
]<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
The endpoint can return a 401 error with the below response:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"Identity":"0",<br />
"XErr":2148916238,<br />
"Message":"",<br />
"Redirect":"https://start.ui.xboxlive.com/AddChildToFamily"<br />
}<br />
</syntaxhighlight><br />
<br />
The Redirect parameter usually will not resolve or go anywhere in a browser, likely they're targeting Xbox consoles.<br />
<br />
Noted XErr codes and their meanings:<br />
<br />
* '''2148916233''': The account doesn't have an Xbox account. Once they sign up for one (or login through minecraft.net to create one) then they can proceed with the login. This shouldn't happen with accounts that have purchased Minecraft with a Microsoft account, as they would've already gone through that Xbox signup process.<br />
* '''2148916238''': The account is a child (under 18) and cannot proceed unless the account is added to a Family by an adult. This only seems to occur when using a custom Microsoft Azure application. When using the Minecraft launchers client id, this doesn't trigger.<br />
<br />
== Authenticate with Minecraft ==<br />
<br />
Now we can finally start talking to Minecraft. The XSTS token from the last request allows us to authenticate to Minecraft using<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
POST https://api.minecraftservices.com/authentication/login_with_xbox<br />
{<br />
"identityToken": "XBL3.0 x=<userhash>;<xsts_token>"<br />
}<br />
</syntaxhighlight><br />
<br />
Response:<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"username" : "some uuid", // this is not the uuid of the account<br />
"roles" : [ ],<br />
"access_token" : "minecraft access token", // jwt, your good old minecraft access token<br />
"token_type" : "Bearer",<br />
"expires_in" : 86400<br />
}<br />
</syntaxhighlight><br />
<br />
This access token allows us to launch the game, but, we haven't actually checked if the account owns the game. Everything until here works with a normal Microsoft account!<br />
<br />
== Checking Game Ownership ==<br />
<br />
So let's use our mc access token to check if a product licence is attached to the account.<br />
GET https://api.minecraftservices.com/entitlements/mcstore<br />
<br />
The access token goes into the auth header: <code>Authorization: Bearer <Minecraft Access Token></code>. (Keep in mind that <code>Bearer </code> is actually the prefix you must include!)<br />
<br />
If the account owns the game, the response will look like this:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"items" : [ {<br />
"name" : "product_minecraft",<br />
"signature" : "jwt sig"<br />
}, {<br />
"name" : "game_minecraft",<br />
"signature" : "jwt sig"<br />
} ],<br />
"signature" : "jwt sig",<br />
"keyId" : "1"<br />
}<br />
</syntaxhighlight><br />
<br />
The first jwts contain the values:<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"typ": "JWT",<br />
"alg": "RS256",<br />
"kid": "1"<br />
}.{<br />
"signerId": "2535416586892404",<br />
"name": "product_minecraft"<br />
}.[Signature]<br />
</syntaxhighlight><br />
<br />
the last jwt looks like this decoded:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"typ": "JWT",<br />
"alg": "RS256",<br />
"kid": "1"<br />
}.{<br />
"entitlements": [<br />
{<br />
"name": "product_minecraft"<br />
},<br />
{<br />
"name": "game_minecraft"<br />
}<br />
],<br />
"signerId": "2535416586892404"<br />
}.[Signature]<br />
</syntaxhighlight><br />
<br />
If the account doesn't own the game, the items array will be empty.<br />
<br />
== Get the profile ==<br />
<br />
Now that we know that the account owns the game, lets get his profile so we get uuid:<br />
GET https://api.minecraftservices.com/minecraft/profile<br />
<br />
Again, the access token goes into the auth header: <code>Authorization: Bearer token</code><br />
<br />
The response will look like this, if the account owns the game:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"id" : "986dec87b7ec47ff89ff033fdb95c4b5", // the real uuid of the account, woo<br />
"name" : "HowDoesAuthWork", // the mc user name of the account<br />
"skins" : [ {<br />
"id" : "6a6e65e5-76dd-4c3c-a625-162924514568",<br />
"state" : "ACTIVE",<br />
"url" : "http://textures.minecraft.net/texture/1a4af718455d4aab528e7a61f86fa25e6a369d1768dcb13f7df319a713eb810b",<br />
"variant" : "CLASSIC",<br />
"alias" : "STEVE"<br />
} ],<br />
"capes" : [ ]<br />
}<br />
</syntaxhighlight><br />
<br />
Else it will look like this:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"path" : "/minecraft/profile",<br />
"errorType" : "NOT_FOUND",<br />
"error" : "NOT_FOUND",<br />
"errorMessage" : "The server has not found anything matching the request URI",<br />
"developerMessage" : "The server has not found anything matching the request URI"<br />
}<br />
</syntaxhighlight><br />
<br />
You should know have all necessary data (the mc access token, the username and the uuid) to launch the game. Well done!<br />
<br />
== Sample Implementations ==<br />
<br />
There is a rough sample implementation in Java (using javafx and its webview) [https://github.com/MiniDigger/MiniLauncher/blob/master/launcher/src/main/java/me/minidigger/minecraftlauncher/launcher/gui/MsaFragmentController.java here], and in Go [https://gist.github.com/rbrick/be8ed86864fc5d77aa6c979053cfc892 here].<br />
[https://github.com/PrismarineJS/node-minecraft-protocol/blob/master/src/client/microsoftAuth.js Js implementation]<br />
<br />
A implementation in Kotlin (with JavaFX) can be found here: [https://gitlab.bixilon.de/bixilon/minosoft/-/blob/development/src/main/java/de/bixilon/minosoft/gui/main/dialogs/login/MicrosoftLoginController.java here] and [https://gitlab.bixilon.de/bixilon/minosoft/-/blob/development/src/main/java/de/bixilon/minosoft/util/microsoft/MicrosoftOAuthUtils.kt here]</div>MrAlisYThttps://wiki.vg/index.php?title=Mojang_API&diff=16573Mojang API2021-04-16T10:54:51Z<p>MrAlisYT: Added one more Java API wrapper</p>
<hr />
<div><br />
== Notes ==<br />
<br />
* All public APIs are rate limited so you are expected to cache the results. This is currently set at 600 requests per 10 minutes but this may change.<br />
* For some parts of the API, demo accounts are sometimes included, sometimes not. Mojang keeps changing this.<br />
<br />
== API Status ==<br />
<br />
GET https://status.mojang.com/check<br />
<br />
Returns status of various Mojang services. Possible values are <code>green</code> (no issues), <code>yellow</code> (some issues), <code>red</code> (service unavailable).<br />
<br />
=== Response ===<br />
<br />
<syntaxhighlight lang="javascript" style="overflow-x: scroll"><br />
[<br />
{<br />
"minecraft.net": "yellow"<br />
},<br />
{<br />
"session.minecraft.net": "green"<br />
},<br />
{<br />
"account.mojang.com": "green"<br />
},<br />
{<br />
"auth.mojang.com": "green"<br />
},<br />
{<br />
"skins.minecraft.net": "green"<br />
},<br />
{<br />
"authserver.mojang.com": "green"<br />
},<br />
{<br />
"sessionserver.mojang.com": "yellow"<br />
},<br />
{<br />
"api.mojang.com": "green"<br />
},<br />
{<br />
"textures.minecraft.net": "red"<br />
},<br />
{<br />
"mojang.com": "green"<br />
}<br />
]<br />
</syntaxhighlight><br />
<br />
== Username -> UUID at time ==<br />
<br />
GET <nowiki>https://api.mojang.com/users/profiles/minecraft/<username>?at=<timestamp></nowiki><br />
<br />
This will return the UUID of the name at the timestamp provided.<br />
<br />
<code>?at=0</code> can be used to get the UUID of the original user of that username, however, it only works if the name was changed at least once, or if the account is legacy.<br />
<br />
* The timestamp is a [[wikipedia:Unix time|UNIX timestamp]] (without milliseconds)<br />
* When the <code>at</code> parameter is not sent, the current time is used<br />
<br />
{{Warning2|Since November 2020, Mojang stopped supporting the timestamp parameter. If a timestamp is provided, it is silently ignored and the current uuid is returned. Please remind them to fix this here: [https://bugs.mojang.com/browse/WEB-3367 WEB-3367]}}<br />
<br />
=== Response ===<br />
<br />
<syntaxhighlight lang="javascript" style="overflow-x: scroll"><br />
{<br />
"id": "7125ba8b1c864508b92bb5c042ccfe2b",<br />
"name": "KrisJelbring"<br />
}<br />
</syntaxhighlight><br />
<br />
* <code>id</code> is the uuid<br />
* <code>name</code> is the '''current name of that uuid''', it is '''not the name requested!'''<br />
* <code>legacy</code> only appears when true (not migrated to mojang account)<br />
* <code>demo</code> only appears when true (account unpaid)<br />
<br />
If there is no player with the given username an HTTP status code 204 (No Content) is sent without any HTTP body.<br /><br />
If the timestamp is not a number, too big or too small the HTTP status code 400 (Bad Request) is sent with an error message looking like this:<br />
<br />
<syntaxhighlight lang="javascript" style="overflow-x: scroll"><br />
{<br />
"error": "IllegalArgumentException",<br />
"errorMessage": "Invalid timestamp."<br />
}<br />
</syntaxhighlight><br />
<br />
== UUID -> Name history ==<br />
<br />
<nowiki>GET https://api.mojang.com/user/profiles/<uuid>/names</nowiki><br />
<br />
Returns all the usernames this user has used in the past and the one they are using currently. The UUID must be given without hyphens.<br />
<br />
=== Response ===<br />
<br />
<syntaxhighlight lang="javascript" style="overflow-x: scroll"><br />
[<br />
{<br />
"name": "Gold"<br />
},<br />
{<br />
"name": "Diamond",<br />
"changedToAt": 1414059749000<br />
}<br />
]<br />
</syntaxhighlight><br />
<br />
The <code>changedToAt</code> field is a Java timestamp in milliseconds.<br />
<br />
== Playernames -> UUIDs ==<br />
<br />
POST <nowiki>https://api.mojang.com/profiles/minecraft</nowiki><br />
<br />
This will return player UUIDs and some extras.<br />
<br />
=== Payload ===<br />
<br />
<syntaxhighlight lang="javascript" style="overflow-x: scroll"><br />
[<br />
"maksimkurb",<br />
"nonExistingPlayer" //Test for non-existing player<br />
]<br />
</syntaxhighlight><br />
<br />
=== Response ===<br />
<br />
<syntaxhighlight lang="javascript" style="overflow-x: scroll"><br />
[<br />
{<br />
"id": "0d252b7218b648bfb86c2ae476954d32",<br />
"name": "maksimkurb",<br />
"legacy": true,<br />
"demo": true<br />
}<br />
]<br />
</syntaxhighlight><br />
<br />
* name is case-corrected<br />
* legacy only appears when true (profile not migrated to mojang.com)<br />
* demo only appears when true (account unpaid)<br />
* BadRequestException is returned when any of the usernames is null or otherwise invalid<br />
* The <code>Content-Type</code> HTTP header must be <code>application/json</code><br />
* You cannot request more than 10 names per request<br />
<br />
== UUID -> Profile + Skin/Cape ==<br />
<br />
<nowiki>GET https://sessionserver.mojang.com/session/minecraft/profile/<uuid></nowiki><br />
<br />
This will return the player's username plus any additional information about them (e.g. skins). Example: https://sessionserver.mojang.com/session/minecraft/profile/4566e69fc90748ee8d71d7ba5aa00d20<br />
<br />
This has no ratelimit.<br />
<br />
[[Category:Protocol Details]]<br />
[[Category:Minecraft Modern]]<br />
<br />
=== Response ===<br />
<syntaxhighlight lang="javascript" style="overflow-x: scroll"><br />
{<br />
"id": "<profile identifier>",<br />
"name": "<player name>",<br />
"properties": [ <br />
{<br />
"name": "textures",<br />
"value": "<base64 string>",<br />
"signature": "<base64 string; signed data using Yggdrasil's private key>" // Only provided if ?unsigned=false is appended to url<br />
}<br />
]<br />
}<br />
</syntaxhighlight><br />
<br />
<br />
* <code>"legacy": true</code> will appear in the response if the user has not migrated their minecraft.net account to mojang.<br />
The "value" base64 string for the "textures" object decoded:<br />
<syntaxhighlight lang="javascript" style="overflow-x: scroll"><br />
{<br />
"timestamp": <java time in ms>,<br />
"profileId": "<profile uuid>",<br />
"profileName": "<player name>",<br />
"signatureRequired": true, // Only present if ?unsigned=false is appended to url<br />
"textures": {<br />
"SKIN": {<br />
"url": "<player skin URL>"<br />
},<br />
"CAPE": {<br />
"url": "<player cape URL>"<br />
}<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
* The timestamp is sometimes in the past (probably due to cached results?)<br />
* The <code>"SKIN"</code> object will have <code>"metadata": {"model": "slim"}</code> if the player model has slim arms (“Alex?” style). For square arms (“Steve?” style), <code>"metadata"</code> will be missing.<br />
* If no custom skin has been set, <code>"SKIN"</code> will be missing.<br>Whether the player has the “Alex?” or “Steve?” skin depends on [http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/java/util/UUID.java#l394 the Java hashCode] of their UUID. Steve is used for even hashes. Example implementations:<br />
** [https://github.com/mapcrafter/mapcrafter-playermarkers/blob/c583dd9157a041a3c9ec5c68244f73b8d01ac37a/playermarkers/player.php#L8-L19 PHP]<br />
** [https://github.com/LapisBlue/Lapitar/blob/55ede80ce4ebb5ecc2b968164afb40f61b4cc509/mc/uuid.go#L34-L36 Go]<br />
** [https://github.com/crafatar/crafatar/blob/9d2fe0c45424de3ebc8e0b10f9446e7d5c3738b2/lib/skins.js#L90-L108 JavaScript] (includes explanation)<br />
** [https://web.archive.org/web/20151022205119/https://gist.github.com/jomo/9968b8d572c38e1b1f4c Java] (includes sample UUIDs)<br />
* Likewise <code>"CAPE"</code> will be missing if the account has no cape.<br />
<br />
== Change Name ==<br />
PUT <nowiki>https://api.minecraftservices.com/minecraft/profile/name/</nowiki>'''<name>'''<br />
<br />
This will set the name for the account that the access token in the Authorization header belongs to.<br />
<br />
=== Response ===<br />
Upon error the server will send back a JSON with the error.<br />
{| class="wikitable"<br />
|'''Status Codes'''<br />
|'''Responses'''<br />
|-<br />
|400<br />
|Name is invalid, longer than 16 characters or contains characters other than (a-zA-Z0-9_)<br />
|-<br />
|403<br />
|Name is unavailable (Either taken or has not become available)<br />
|-<br />
|401<br />
|Unauthorized (Bearer token expired or is not correct)<br />
|-<br />
|500<br />
|Timed out (API lagged out and could not respond)<br />
|-<br />
|200<br />
|Success (Name changed)<br />
|}<br />
<br />
=== Headers ===<br />
Authorization: Bearer '''<access token>'''<br />
<br />
=== Payload ===<br />
No payload needed.<br />
<br />
=== Success Response ===<br />
<syntaxhighlight lang="javascript" style="overflow-x: scroll"><br />
{<br />
"id" : "31e0ccbef5fa4eb988592f30516f65fe",<br />
"name" : "newname34234", //newly acquired name<br />
"skins" : [ {<br />
"id" : "c97f6fea-6ca8-4bf0-bdae-b7cf91fb089c", //uuid of account<br />
"state" : "ACTIVE",<br />
"url" : "<nowiki>http://textures.minecraft.net/texture/3b60a1f6d562f52aaebbf1434f1de147933a3affe0e764fa49ea057536623cd3</nowiki>", //skin url<br />
"variant" : "SLIM"<br />
} ],<br />
"capes" : [ ]<br />
}<br />
</syntaxhighlight><br />
<br />
<br />
== Change Skin ==<br />
POST <nowiki>https://api.minecraftservices.com/minecraft/profile/skins</nowiki><br />
<br />
This will set the skin for the selected profile, but Mojang's servers will fetch the skin from a URL. <br />
<br />
=== Response ===<br />
Upon error the server will send back a JSON with the error. (Success is a blank payload)<br />
=== Headers ===<br />
Authorization: Bearer '''<access token>'''<br />
Content-Type: application/json; charset=utf-8<br />
<br />
=== Payload ===<br />
The payload for this API consists of a JSON object containing the URL and variant<br />
<br />
{<br />
"variant": "classic",<br />
"url": "http://assets.mojang.com/SkinTemplates/steve.png"<br />
}<br />
<br />
variant is either "classic" or "slim"<br />
<br />
<br />
=== Example ===<br />
curl -H "Authorization: Bearer '''<access token>'''" -H "Content-Type: application/json; charset=utf-8" --request POST --data '{"variant": "classic", "url": "http://assets.mojang.com/SkinTemplates/steve.png"}' <nowiki>https://api.minecraftservices.com/minecraft/profile/skins</nowiki><br />
<br />
== Upload Skin ==<br />
POST <nowiki>https://api.minecraftservices.com/minecraft/profile/skins</nowiki><br />
<br />
This uploads a skin to Mojang's servers. It also sets the users skin. This works on legacy counts as well.<br />
<br />
=== Response ===<br />
No response unless error<br />
=== Headers ===<br />
Authorization: Bearer '''<access token>'''<br />
<br />
=== Payload ===<br />
The payload for this API consists of multipart form data. There are two parts (order does not matter b/c of boundary):<br />
{| class="wikitable"<br />
|'''variant'''<br />
|either "classic" for normal model or "slim" for slim model.<br />
|-<br />
|'''file'''<br />
|Raw image file data<br />
|}<br />
=== Example ===<br />
curl -X POST -H "Authorization: Bearer '''<access token>'''" -F variant=classic -F file="@steeevee.png;type=image/png" <nowiki>https://api.minecraftservices.com/minecraft/profile/skins</nowiki><br />
<br />
PUT /minecraft/profile/skins HTTP/1.1<br />
Host: api.minecraftservices.com<br />
User-Agent: curl/7.49.0<br />
Accept: */*<br />
Authorization: Bearer '''<access token>'''<br />
Content-Length: '''<length>'''<br />
Content-Type: multipart/form-data; boundary='''<boundary>'''<br />
<br />
--'''<boundary>'''<br />
Content-Disposition: form-data; name="variant"<br />
<br />
classic<br />
--'''<boundary>'''<br />
Content-Disposition: form-data; name="file"; filename="alex.png"<br />
Content-Type: image/png<br />
<br />
'''<image data>'''<br />
--'''<boundary>'''--<br />
<br />
== Reset Skin ==<br />
DELETE <nowiki>https://api.mojang.com/user/profile/</nowiki>'''<uuid>'''/skin<br />
<br />
Resets the user's skin to the default one.<br />
<br />
=== Response ===<br />
No response unless error<br />
<br />
=== Headers ===<br />
Authorization: Bearer '''<access token>'''<br />
<br />
=== Example ===<br />
curl -X DELETE -H "Authorization: Bearer '''<access token>'''" <nowiki>https://api.mojang.com/user/profile/</nowiki>'''<uuid>'''/skin<br />
<br />
DELETE /user/profile/'''<uuid>'''/skin HTTP/1.1<br />
Host: api.mojang.com<br />
User-Agent: curl/7.46.0<br />
Accept: */*<br />
Authorization: Bearer '''<access token>'''<br />
<br />
== Security question-answer flow == <br />
<br />
This is required to get the skin change endpoint to work in case the services do not trust your IP yet.<br />
<br />
=== Check if security questions are needed ===<br />
<br />
GET https://api.mojang.com/user/security/location<br />
Authorization: Bearer '''<access token>'''<br />
<br />
Good answer:<br />
204 NO CONTENT<br />
<br />
Bad answer:<br />
<syntaxhighlight lang="javascript" style="overflow-x: scroll"><br />
{<br />
"error": "ForbiddenOperationException",<br />
"errorMessage": "Current IP is not secured"<br />
}<br />
</syntaxhighlight><br />
<br />
=== Get list of questions ===<br />
GET https://api.mojang.com/user/security/challenges<br />
Authorization: Bearer '''<access token>'''<br />
<br />
Answer:<br />
<syntaxhighlight lang="javascript" style="overflow-x: scroll"><br />
[<br />
{<br />
"answer": {<br />
"id": 123<br />
},<br />
"question": {<br />
"id": 1,<br />
"question": "What is your favorite pet's name?"<br />
}<br />
},<br />
{<br />
"answer": {<br />
"id": 456<br />
},<br />
"question": {<br />
"id": 2,<br />
"question": "What is your favorite movie?"<br />
}<br />
},<br />
{<br />
"answer": {<br />
"id": 789<br />
},<br />
"question": {<br />
"id": 3,<br />
"question": "What is your favorite author's last name?"<br />
}<br />
}<br />
],<br />
</syntaxhighlight><br />
<br />
The possible IDs are these:<br />
<pre><br />
1 What is your favorite pet's name?<br />
2 What is your favorite movie?<br />
3 What is your favorite author's last name?<br />
4 What is your favorite artist's last name?<br />
5 What is your favorite actor's last name?<br />
6 What is your favorite activity?<br />
7 What is your favorite restaurant?<br />
8 What is the name of your favorite cartoon?<br />
9 What is the name of the first school you attended?<br />
10 What is the last name of your favorite teacher?<br />
11 What is your best friend's first name?<br />
12 What is your favorite cousin's name?<br />
13 What was the first name of your first girl/boyfriend?<br />
14 What was the name of your first stuffed animal?<br />
15 What is your mother's middle name?<br />
16 What is your father's middle name?<br />
17 What is your oldest sibling's middle name?<br />
18 In what city did your parents meet?<br />
19 In what hospital were you born?<br />
20 What is your favorite team?<br />
21 How old were you when you got your first computer?<br />
22 How old were you when you got your first gaming console?<br />
23 What was your first video game?<br />
24 What is your favorite card game?<br />
25 What is your favorite board game?<br />
26 What was your first gaming console?<br />
27 What was the first book you ever read?<br />
28 Where did you go on your first holiday?<br />
29 In what city does your grandmother live?<br />
30 In what city does your grandfather live?<br />
31 What is your grandmother's first name?<br />
32 What is your grandfather's first name?<br />
33 What is your least favorite food?<br />
34 What is your favorite ice cream flavor?<br />
35 What is your favorite ice cream flavor?<br />
36 What is your favorite place to visit?<br />
37 What is your dream job?<br />
38 What color was your first pet?<br />
39 What is your lucky number?<br />
</pre><br />
<br />
=== Send back the answers ===<br />
POST https://api.mojang.com/user/security/location<br />
Authorization: Bearer '''<access token>'''<br />
<br />
<syntaxhighlight lang="javascript" style="overflow-x: scroll"><br />
[<br />
{<br />
"id": 123,<br />
"answer" : "foo"<br />
},<br />
{<br />
"id": 456,<br />
"answer" : "bar"<br />
},<br />
{<br />
"id": 589,<br />
"answer" : "baz"<br />
}<br />
]<br />
</syntaxhighlight><br />
<br />
On failure, you will get some sort of error. Unless it's a syntax or json structure error, it will be this:<br />
<br />
<syntaxhighlight lang="javascript" style="overflow-x: scroll"><br />
{<br />
"error": "ForbiddenOperationException",<br />
"errorMessage": "At least one answer was incorrect"<br />
}<br />
</syntaxhighlight><br />
<br />
On success:<br />
204 NO CONTENT<br />
<br />
== Blocked Servers ==<br />
GET https://sessionserver.mojang.com/blockedservers<br />
<br />
Returns a list of SHA1 hashes used to check server addresses against when the client tries to connect.<br />
<br />
Clients check the lowercase name, using the ISO-8859-1 charset, against this list. They will also attempt to check subdomains, replacing each level with a <code>*</code>. Specifically, it splits based off of the <code>.</code> in the domain, goes through each section removing one at a time. For instance, for <code>mc.example.com</code>, it would try <code>mc.example.com</code>, <code>*.example.com</code>, and <code>*.com</code>. With IP addresses (verified by having 4 split sections, with each section being a valid integer between 0 and 255, inclusive<!-- Decompiles seem to mess this up with an empty if, but there is logic for checking the range -->) substitution starts from the end, so for <code>192.168.0.1</code>, it would try <code>192.168.0.1</code>, <code>192.168.0.*</code>, <code>192.168.*</code>, and <code>192.*</code>.<br />
<br />
This check is done by the bootstrap class in netty. The default netty class is overridden by one in the com.mojang:netty dependency loaded by the launcher. This allows it to affect any version that used netty (1.7+)<br />
<br />
=== Response ===<br />
A line separated list of all SHA1 hashes.<br />
<br />
Some of the current ~2200 hashes have been cracked.<br />
<pre><br />
6f2520f8bd70a718c568ab5274c56bdbbfc14ef4:*.minetime.com<br />
7ea72de5f8e70a2ac45f1aa17d43f0ca3cddeedd:*.trollingbrandon.club<br />
c005ad34245a8f2105658da2d6d6e8545ef0f0de:*.skygod.us<br />
c645d6c6430db3069abd291ec13afebdb320714b:*.mineaqua.es<br />
8bf58811e6ebca16a01b842ff0c012db1171d7d6:*.eulablows.host<br />
8789800277882d1989d384e7941b6ad3dadab430:*.moredotsmoredots.xyz<br />
e40c3456fb05687b8eeb17213a47b263d566f179:*.brandonlovescock.bid<br />
278b24ffff7f9f46cf71212a4c0948d07fb3bc35:*.brandonlovescock.club<br />
c78697e385bfa58d6bd2a013f543cdfbdc297c4f:*.mineaqua.net<br />
b13009db1e2fbe05465716f67c8d58b9c0503520:*.endercraft.com<br />
3e560742576af9413fca72e70f75d7ddc9416020:*.insanefactions.org<br />
986204c70d368d50ffead9031e86f2b9e70bb6d0:*.playmc.mx<br />
65ca8860fa8141da805106c0389de9d7c17e39bf:*.howdoiblacklistsrv.host<br />
7dca807cc9484b1eed109c003831faf189b6c8bf:*.brandonlovescock.online<br />
c6a2203285fb0a475c1cd6ff72527209cc0ccc6e:*.brandonlovescock.press<br />
e3985eb936d66c9b07aa72c15358f92965b1194e:*.insanenetwork.org<br />
b140bec2347bfbe6dcae44aa876b9ba5fe66505b:*.phoenixnexus.net<br />
27ae74becc8cd701b19f25d347faa71084f69acd:*.arkhamnetwork.org<br />
48f04e89d20b15de115503f22fedfe2cb2d1ab12:brandonisan.unusualperson.com<br />
9f0f30820cebb01f6c81f0fdafefa0142660d688:*.kidslovemy500dollarranks.club<br />
cc90e7b39112a48064f430d3a08bbd78a226d670:*.eccgamers.com<br />
88f155cf583c930ffed0e3e69ebc3a186ea8cbb7:*.fucktheeula.com<br />
605e6296b8dba9f0e4b8e43269fe5d053b5f4f1b:*.mojangendorsesbrazzers.webcam<br />
5d2e23d164a43fbfc4e6093074567f39b504ab51:touchmybody.redirectme.net<br />
f3df314d1f816a8c2185cd7d4bcd73bbcffc4ed8:*.mojangsentamonkeyinto.space<br />
073ca448ef3d311218d7bd32d6307243ce22e7d0:*.diacraft.org<br />
33839f4006d6044a3a6675c593fada6a690bb64d:*.diacraft.de<br />
e2e12f3b7b85eab81c0ee5d2e9e188df583fe281:*.eulablacklist.club<br />
11a2c115510bfa6cb56bbd18a7259a4420498fd5:*.slaughterhousepvp.com<br />
75df09492c6c979e2db41116100093bb791b8433:*.timelesspvp.net<br />
d42339c120bc10a393a0b1d2c6a2e0ed4dbdd61b:*.herowars.org<br />
4a1b3b860ba0b441fa722bbcba97a614f6af9bb8:justgiveinandblockddnsbitches.ddns.net<br />
b8c876f599dcf5162911bba2d543ccbd23d18ae5:brandonisagainst.health-carereform.com<br />
9a9ae8e9d0b6f3bf54c266dcd1e4ec034e13f714:brandonwatchesporn.onthewifi.com<br />
336e718ffbc705e76b4a72884172c6b95216b57c:canyouwildcardipsplease.gotdns.ch<br />
27cf97ecf24c92f1fe5c84c5ff654728c3ee37dd:letsplaysome.servecounterstrike.com<br />
32066aa0c7dc9b097eed5b00c5629ad03f250a2d:mojangbrokeintomy.homesecuritymac.com<br />
39f4bbfd123a5a5ddbf97489877831c15a70d7f2:*.primemc.org<br />
f32f824d41aaed334aef248fbe3a0f8ecf4ac1a0:*.meep.in<br />
c22efe4cf7fb319ca2387bbc930c1fdf77ab72fc:*.itsjerryandharry.com<br />
cc8e1ae93571d144bf4b37369cb8466093d6db5a:*.thearchon.net<br />
9c0806e5ffaccb45121e57e4ce88c7bc76e057f1:*.goatpvp.com<br />
5ca81746337088b7617c851a1376e4f00d921d9e:*.gotpvp.com<br />
a5944b9707fdb2cc95ed4ef188cf5f3151ac0525:*.guildcraft.org<br />
</pre><br />
<br />
== Statistics ==<br />
POST <nowiki>https://api.mojang.com/orders/statistics</nowiki><br />
<br />
Get statistics on the sales of Minecraft.<br />
<br />
=== Payload ===<br />
The payload is a json list of options under the metricKeys key.<br />
You will receive a single object corresponding to the sum of sales of the requested type(s).<br />
You must request at least one type of sale.<br />
Below is the default list used by https://minecraft.net/en/stats/<br />
<syntaxhighlight lang="javascript" style="overflow-x: scroll"><br />
{<br />
"metricKeys": [<br />
"item_sold_minecraft",<br />
"prepaid_card_redeemed_minecraft"<br />
]<br />
}<br />
</syntaxhighlight><br />
<br />
Valid options are:<br />
item_sold_minecraft<br />
prepaid_card_redeemed_minecraft<br />
item_sold_cobalt<br />
item_sold_scrolls<br />
prepaid_card_redeemed_cobalt<br />
item_sold_dungeons<br />
<br />
=== Response ===<br />
A json object is returned with the total amount of copies sold, the amount of copies sold in the last 24h and how many sales there are per second.<br />
<syntaxhighlight lang="javascript" style="overflow-x: scroll"><br />
{<br />
"total": integer total amount sold,<br />
"last24h": integer total sold in last 24 hours,<br />
"saleVelocityPerSeconds": decimal average sales per second<br />
}<br />
</syntaxhighlight><br />
<br />
== Examples ==<br />
<br />
[https://github.com/hawezo/MojangSharp C#] | full API wrapper<br />
<br />
[https://github.com/CmlLib/MojangAPI C#] | full API wrapper with Mojang/Microsoft Authentication<br />
<br />
[https://github.com/Lukaesebrot/mojango Go] | full API wrapper<br />
<br />
[https://github.com/PhilipBorgesen/minecraft/tree/master/profile Go] | UUIDs or names to profiles with skins, capes and name histories<br />
<br />
[https://github.com/summer/mojang Python] | Full API Wrapper. Also supports authentication & parts of the Minecraft website<br />
<br />
[https://github.com/SynchronousX/mojang-api Python] | full API wrapper<br />
<br />
[https://github.com/techkid6/AccountsClientPython Python] | UUIDs or names to profiles<br />
<br />
[https://gist.github.com/jomo/74944770e7647855ac9d Python] | names file to uuids+names file<br />
<br />
[https://github.com/elyby/mojang-api PHP] | Complete Mojang's API wrapper<br />
<br />
[https://github.com/MineTheCube/MojangAPI PHP] | UUIDs or names to profiles with skins, heads and name histories<br />
<br />
[https://gist.github.com/ezfe/a71feccd3a837a2592f1 PHP] | UUIDs to names<br />
<br />
[https://github.com/ozzyfant/AccountsClientPHP PHP] | UUIDs to names, names to uuids<br />
<br />
[https://github.com/SparklingComet/java-mojang-api Java] | Almost full API Wrapper<br />
<br />
[https://github.com/dpkgsoft/mojang Java] | Almost full API Wrapper with auth<br />
<br />
[https://github.com/thechunknetwork/mojang-api JavaScript] | UUIDs or names to profiles with skins, capes and name histories<br />
<br />
[[Category:Protocol Details]]<br />
[[Category:Minecraft Modern]]</div>MrAlisYThttps://wiki.vg/index.php?title=Microsoft_Authentication_Scheme&diff=16201Microsoft Authentication Scheme2020-12-09T05:10:09Z<p>MrAlisYT: /* Authorization Code -> Authorization Token */ Changed Java format to json (useful for non-java implementations), wich required by Microsoft</p>
<hr />
<div>Minecraft is moving to Microsoft accounts. Starting December 2020, all new Accounts already use the new system, old accounts will be migrated later, see [https://www.minecraft.net/en-us/article/java-edition-moving-house this blog post]<br />
<br />
There are multiple steps and different tokens required, but in the end, you get a normal minecraft token back. Launching the game itself hasn't changed.<br />
<br />
<br />
== Microsoft OAuth Flow ==<br />
[[File:minecraft_login.png|frame|Example of the login page]]<br />
<br />
In the first step, we are logging into the microsoft account. This has to be done in a browser/webview! The redirect URL cannot be changed. The client id is hardcoded (<code>00000000402b5328</code>), it's Minecrafts id.<br />
<br />
https://login.live.com/oauth20_authorize.srf<br />
?client_id=00000000402b5328<br />
&response_type=code<br />
&scope=service%3A%3Auser.auth.xboxlive.com%3A%3AMBI_SSL<br />
&redirect_uri=https%3A%2F%2Flogin.live.com%2Foauth20_desktop.srf<br />
<br />
The user will be prompted to enter username (E-Mail, Skype ID, Phone number, whatever) and their password. If those are legal, the user will be redirected. The user doesn't need to own MC, that check comes way later!<br />
<br />
The redirect will looks something like this<br />
https://login.live.com/oauth20_desktop.srf?code=codegoeshere&lc=1033<br />
<br />
You have to extract the code param, it's your Microsoft Authorization Code.<br />
<br />
== Authorization Code -> Authorization Token ==<br />
<br />
The next step is to get a auth token from the auth code. This isn't done in the browser for security reasons.<br />
POST https://login.live.com/oauth20_token.srf<br />
Content:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"client_id":"00000000402b5328", // minecrafts client id again<br />
"code":"authcode", // the code from step 1<br />
"grant_type":"sauthorization_code",<br />
"redirect_uri":"https://login.live.com/oauth20_desktop.srf",<br />
"scope":"service::user.auth.xboxlive.com::MBI_SSL"<br />
}<br />
</syntaxhighlight><br />
<br />
Don't forget to set <code>Content-Type: application/x-www-form-urlencoded</code><br />
<br />
The response will look like this:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"token_type":"bearer",<br />
"expires_in":86400,<br />
"scope":"service::user.auth.xboxlive.com::MBI_SSL",<br />
"access_token":"token here",<br />
"refresh_token":"M.R3_BAY.token here",<br />
"user_id":"889ed4a3d844f672",<br />
"foci":"1"<br />
}<br />
</syntaxhighlight><br />
<br />
We care about the access_token here. (TODO: check what we can do with the refresh token)<br />
<br />
== Authenticate with XBL ==<br />
<br />
Now that we are authenticated with microsoft, we can authenticate to xbox live.<br />
<br />
To do that, we send<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
POST https://user.auth.xboxlive.com/user/authenticate<br />
{<br />
"Properties": {<br />
"AuthMethod": "RPS",<br />
"SiteName": "user.auth.xboxlive.com",<br />
"RpsTicket": "access_token" // your access token from step 2 here<br />
},<br />
"RelyingParty": "http://auth.xboxlive.com",<br />
"TokenType": "JWT"<br />
}<br />
</syntaxhighlight><br />
<br />
Again, it will complain if you don't set <code>Content-Type: application/json</code> and <code>Accept: application/json</code><br />
<br />
The response will look like this:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"IssueInstant":"2020-12-07T19:52:08.4463796Z",<br />
"NotAfter":"2020-12-21T19:52:08.4463796Z",<br />
"Token":"token", // save this, this is your xbl token<br />
"DisplayClaims":{<br />
"xui":[<br />
{<br />
"uhs":"uhs" // save this<br />
}<br />
]<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
We need to save token and uhs. I have no idea what uhs stands for. (TODO: find out)<br />
<br />
== Authenticate with XSTS ==<br />
<br />
Now that we are authenticated with XBL, we need to get a XSTS token, we can use to login to minecraft.<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
POST https://xsts.auth.xboxlive.com/xsts/authorize<br />
{<br />
"Properties": {<br />
"SandboxId": "RETAIL",<br />
"UserTokens": [<br />
"xbl_token" // from above<br />
]<br />
},<br />
"RelyingParty": "rp://api.minecraftservices.com/",<br />
"TokenType": "JWT"<br />
}<br />
</syntaxhighlight><br />
<br />
Again, set content type and accept to json.<br />
<br />
Response will look like this:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"IssueInstant":"2020-12-07T19:52:09.2345095Z",<br />
"NotAfter":"2020-12-08T11:52:09.2345095Z",<br />
"Token":"token", // save this, this is your xsts token<br />
"DisplayClaims":{<br />
"xui":[<br />
{<br />
"uhs":"" // same as last request<br />
}<br />
]<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
== Authenticate with Minecraft ==<br />
<br />
Now we can finally start talking to minecraft. The XSTS token from the last request allows us to authenticate to minecraft using<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
POST https://api.minecraftservices.com/authentication/login_with_xbox<br />
{<br />
"identityToken": "XBL3.0 x=<uhs>;<xsts_token>"<br />
}<br />
</syntaxhighlight><br />
<br />
Response:<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"username" : "some uuid", // this is not the uuid of the account<br />
"roles" : [ ],<br />
"access_token" : "minecraft access token", // jwt, your good old minecraft access token<br />
"token_type" : "Bearer",<br />
"expires_in" : 86400<br />
}<br />
</syntaxhighlight><br />
<br />
This access token allows us to launch the game, but, we haven't actually checked if the account owns the game. Everything until here works with a normal microsoft account!<br />
<br />
== Checking Game Ownership ==<br />
<br />
So let's use our mc access token to check if a product licence is attached to the account.<br />
GET https://api.minecraftservices.com/entitlements/mcstore<br />
<br />
The access token goes into the auth header: <code>Authorization: Bearer token</code><br />
<br />
If the account owns the game, the response will look like this:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"items" : [ {<br />
"name" : "product_minecraft",<br />
"signature" : "jwt sig"<br />
}, {<br />
"name" : "game_minecraft",<br />
"signature" : "jwt sig"<br />
} ],<br />
"signature" : "jwt sig",<br />
"keyId" : "1"<br />
}<br />
</syntaxhighlight><br />
<br />
The first jwts contain the values:<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"typ": "JWT",<br />
"alg": "RS256",<br />
"kid": "1"<br />
}.{<br />
"signerId": "2535416586892404",<br />
"name": "product_minecraft"<br />
}.[Signature]<br />
</syntaxhighlight><br />
<br />
the last jwt looks like this decoded:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"typ": "JWT",<br />
"alg": "RS256",<br />
"kid": "1"<br />
}.{<br />
"entitlements": [<br />
{<br />
"name": "product_minecraft"<br />
},<br />
{<br />
"name": "game_minecraft"<br />
}<br />
],<br />
"signerId": "2535416586892404"<br />
}.[Signature]<br />
</syntaxhighlight><br />
<br />
If the account doesn't own the game, the items array will be empty.<br />
<br />
== Get the profile ==<br />
<br />
Now that we know that the account owns the game, lets get his profile so we get uuid:<br />
GET https://api.minecraftservices.com/minecraft/profile<br />
<br />
Again, the access token goes into the auth header: <code>Authorization: Bearer token</code><br />
<br />
The responce will look like this, if the account owns the game:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"id" : "986dec87b7ec47ff89ff033fdb95c4b5", // the real uuid of the account, woo<br />
"name" : "HowDoesAuthWork", // the mc user name of the account<br />
"skins" : [ {<br />
"id" : "6a6e65e5-76dd-4c3c-a625-162924514568",<br />
"state" : "ACTIVE",<br />
"url" : "http://textures.minecraft.net/texture/1a4af718455d4aab528e7a61f86fa25e6a369d1768dcb13f7df319a713eb810b",<br />
"variant" : "CLASSIC",<br />
"alias" : "STEVE"<br />
} ],<br />
"capes" : [ ]<br />
}<br />
</syntaxhighlight><br />
<br />
Else it will look like this:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"path" : "/minecraft/profile",<br />
"errorType" : "NOT_FOUND",<br />
"error" : "NOT_FOUND",<br />
"errorMessage" : "The server has not found anything matching the request URI",<br />
"developerMessage" : "The server has not found anything matching the request URI"<br />
}<br />
</syntaxhighlight><br />
<br />
You should know have all necessary data (the mc access token, the username and the uuid) to launch the game. Well done!<br />
<br />
== Sample Implementations ==<br />
There is a rough sample implementation in Java (using javafx and its webview) [https://github.com/MiniDigger/MiniLauncher/blob/master/launcher/src/main/java/me/minidigger/minecraftlauncher/launcher/gui/MsaFragmentController.java here].</div>MrAlisYThttps://wiki.vg/index.php?title=Microsoft_Authentication_Scheme&diff=16199Microsoft Authentication Scheme2020-12-08T16:18:38Z<p>MrAlisYT: Undo revision 16198 by MrAlisYT (talk)</p>
<hr />
<div>Minecraft is moving to Microsoft accounts. Starting December 2020, all new Accounts already use the new system, old accounts will be migrated later, see [https://www.minecraft.net/en-us/article/java-edition-moving-house this blog post]<br />
<br />
There are multiple steps and different tokens required, but in the end, you get a normal minecraft token back. Launching the game itself hasn't changed.<br />
<br />
<br />
== Microsoft OAuth Flow ==<br />
[[File:minecraft_login.png|frame|Example of the login page]]<br />
<br />
In the first step, we are logging into the microsoft account. This has to be done in a browser/webview! Other redirect urls have not been tested. The client id is hardcoded (<code>00000000402b5328</code>), it's Minecrafts id.<br />
<br />
https://login.live.com/oauth20_authorize.srf<br />
?client_id=00000000402b5328<br />
&response_type=code<br />
&scope=service%3A%3Auser.auth.xboxlive.com%3A%3AMBI_SSL<br />
&redirect_uri=https%3A%2F%2Flogin.live.com%2Foauth20_desktop.srf<br />
<br />
The user will be prompted to enter username (E-Mail, Skype ID, Phone number, whatever) and their password. If those are legal, the user will be redirected. The user doesn't need to own MC, that check comes way later!<br />
<br />
The redirect will looks something like this<br />
https://login.live.com/oauth20_desktop.srf?code=codegoeshere&lc=1033<br />
<br />
You have to extract the code param, it's your Microsoft Authorization Code.<br />
<br />
== Authorization Code -> Authorization Token ==<br />
<br />
The next step is to get a auth token from the auth code. This isn't done in the browser for security reasons.<br />
POST https://login.live.com/oauth20_token.srf<br />
Content:<br />
<br />
<syntaxhighlight lang="java" line='line'><br />
Map<Object, Object> data = Map.of(<br />
"client_id", "00000000402b5328", // minecrafts client id again<br />
"code", authcode, // the code from step 1<br />
"grant_type", "authorization_code",<br />
"redirect_uri", "https://login.live.com/oauth20_desktop.srf",<br />
"scope", "service::user.auth.xboxlive.com::MBI_SSL"<br />
);<br />
</syntaxhighlight><br />
<br />
Don't forget to set <code>Content-Type: application/x-www-form-urlencoded</code><br />
<br />
The response will look like this:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"token_type":"bearer",<br />
"expires_in":86400,<br />
"scope":"service::user.auth.xboxlive.com::MBI_SSL",<br />
"access_token":"token here",<br />
"refresh_token":"M.R3_BAY.token here",<br />
"user_id":"889ed4a3d844f672",<br />
"foci":"1"<br />
}<br />
</syntaxhighlight><br />
<br />
We care about the access_token here. (TODO: check what we can do with the refresh token)<br />
<br />
== Authenticate with XBL ==<br />
<br />
Now that we are authenticated with microsoft, we can authenticate to xbox live.<br />
<br />
To do that, we send<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
POST https://user.auth.xboxlive.com/user/authenticate<br />
{<br />
"Properties": {<br />
"AuthMethod": "RPS",<br />
"SiteName": "user.auth.xboxlive.com",<br />
"RpsTicket": "access_token" // your access token from step 2 here<br />
},<br />
"RelyingParty": "http://auth.xboxlive.com",<br />
"TokenType": "JWT"<br />
}<br />
</syntaxhighlight><br />
<br />
Again, it will complain if you don't set <code>Content-Type: application/json</code> and <code>Accept: application/json</code><br />
<br />
The response will look like this:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"IssueInstant":"2020-12-07T19:52:08.4463796Z",<br />
"NotAfter":"2020-12-21T19:52:08.4463796Z",<br />
"Token":"token", // save this, this is your xbl token<br />
"DisplayClaims":{<br />
"xui":[<br />
{<br />
"uhs":"uhs" // save this<br />
}<br />
]<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
We need to save token and uhs. I have no idea what uhs stands for. (TODO: find out)<br />
<br />
== Authenticate with XSTS ==<br />
<br />
Now that we are authenticated with XBL, we need to get a XSTS token, we can use to login to minecraft.<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
POST https://xsts.auth.xboxlive.com/xsts/authorize<br />
{<br />
"Properties": {<br />
"SandboxId": "RETAIL",<br />
"UserTokens": [<br />
"xbl_token" // from above<br />
]<br />
},<br />
"RelyingParty": "rp://api.minecraftservices.com/",<br />
"TokenType": "JWT"<br />
}<br />
</syntaxhighlight><br />
<br />
Again, set content type and accept to json.<br />
<br />
Response will look like this:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"IssueInstant":"2020-12-07T19:52:09.2345095Z",<br />
"NotAfter":"2020-12-08T11:52:09.2345095Z",<br />
"Token":"token", // save this, this is your xsts token<br />
"DisplayClaims":{<br />
"xui":[<br />
{<br />
"uhs":"" // same as last request<br />
}<br />
]<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
== Authenticate with Minecraft ==<br />
<br />
Now we can finally start talking to minecraft. The XSTS token from the last request allows us to authenticate to minecraft using<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
POST https://api.minecraftservices.com/authentication/login_with_xbox<br />
{<br />
"identityToken": "XBL3.0 x=<uhs>;<xsts_token>"<br />
}<br />
</syntaxhighlight><br />
<br />
Response:<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"username" : "some uuid", // this is not the uuid of the account<br />
"roles" : [ ],<br />
"access_token" : "minecraft access token", // jwt, your good old minecraft access token<br />
"token_type" : "Bearer",<br />
"expires_in" : 86400<br />
}<br />
</syntaxhighlight><br />
<br />
This access token allows us to launch the game, but, we haven't actually checked if the account owns the game. Everything until here works with a normal microsoft account!<br />
<br />
== Checking Game Ownership ==<br />
<br />
So let's use our mc access token to check if a product licence is attached to the account.<br />
GET https://api.minecraftservices.com/entitlements/mcstore<br />
<br />
The access token goes into the auth header: <code>Authorization: Bearer token</code><br />
<br />
If the account owns the game, the response will look like this:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"items" : [ {<br />
"name" : "product_minecraft",<br />
"signature" : "jwt sig"<br />
}, {<br />
"name" : "game_minecraft",<br />
"signature" : "jwt sig"<br />
} ],<br />
"signature" : "jwt sig",<br />
"keyId" : "1"<br />
}<br />
</syntaxhighlight><br />
<br />
The first jwts contain the values:<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"typ": "JWT",<br />
"alg": "RS256",<br />
"kid": "1"<br />
}.{<br />
"signerId": "2535416586892404",<br />
"name": "product_minecraft"<br />
}.[Signature]<br />
</syntaxhighlight><br />
<br />
the last jwt looks like this decoded:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"typ": "JWT",<br />
"alg": "RS256",<br />
"kid": "1"<br />
}.{<br />
"entitlements": [<br />
{<br />
"name": "product_minecraft"<br />
},<br />
{<br />
"name": "game_minecraft"<br />
}<br />
],<br />
"signerId": "2535416586892404"<br />
}.[Signature]<br />
</syntaxhighlight><br />
<br />
If the account doesn't own the game, the items array will be empty.<br />
<br />
== Get the profile ==<br />
<br />
Now that we know that the account owns the game, lets get his profile so we get uuid:<br />
GET https://api.minecraftservices.com/minecraft/profile<br />
<br />
Again, the access token goes into the auth header: <code>Authorization: Bearer token</code><br />
<br />
The responce will look like this, if the account owns the game:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"id" : "986dec87b7ec47ff89ff033fdb95c4b5", // the real uuid of the account, woo<br />
"name" : "HowDoesAuthWork", // the mc user name of the account<br />
"skins" : [ {<br />
"id" : "6a6e65e5-76dd-4c3c-a625-162924514568",<br />
"state" : "ACTIVE",<br />
"url" : "http://textures.minecraft.net/texture/1a4af718455d4aab528e7a61f86fa25e6a369d1768dcb13f7df319a713eb810b",<br />
"variant" : "CLASSIC",<br />
"alias" : "STEVE"<br />
} ],<br />
"capes" : [ ]<br />
}<br />
</syntaxhighlight><br />
<br />
Else it will look like this:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"path" : "/minecraft/profile",<br />
"errorType" : "NOT_FOUND",<br />
"error" : "NOT_FOUND",<br />
"errorMessage" : "The server has not found anything matching the request URI",<br />
"developerMessage" : "The server has not found anything matching the request URI"<br />
}<br />
</syntaxhighlight><br />
<br />
You should know have all necessary data (the mc access token, the username and the uuid) to launch the game. Well done!<br />
<br />
== Sample Implementations ==<br />
There is a rough sample implementation in Java (using javafx and its webview) [https://github.com/MiniDigger/MiniLauncher/blob/master/launcher/src/main/java/me/minidigger/minecraftlauncher/launcher/gui/MsaFragmentController.java here].</div>MrAlisYThttps://wiki.vg/index.php?title=Microsoft_Authentication_Scheme&diff=16198Microsoft Authentication Scheme2020-12-08T16:09:31Z<p>MrAlisYT: Other redirect urls will not work (tested)</p>
<hr />
<div>Minecraft is moving to Microsoft accounts. Starting December 2020, all new Accounts already use the new system, old accounts will be migrated later, see [https://www.minecraft.net/en-us/article/java-edition-moving-house this blog post]<br />
<br />
There are multiple steps and different tokens required, but in the end, you get a normal minecraft token back. Launching the game itself hasn't changed.<br />
<br />
<br />
== Microsoft OAuth Flow ==<br />
[[File:minecraft_login.png|frame|Example of the login page]]<br />
<br />
In the first step, we are logging into the microsoft account. This has to be done in a browser/webview! Other redirect urls will not work. The client id is hardcoded (<code>00000000402b5328</code>), it's Minecrafts id.<br />
<br />
https://login.live.com/oauth20_authorize.srf<br />
?client_id=00000000402b5328<br />
&response_type=code<br />
&scope=service%3A%3Auser.auth.xboxlive.com%3A%3AMBI_SSL<br />
&redirect_uri=https%3A%2F%2Flogin.live.com%2Foauth20_desktop.srf<br />
<br />
The user will be prompted to enter username (E-Mail, Skype ID, Phone number, whatever) and their password. If those are legal, the user will be redirected. The user doesn't need to own MC, that check comes way later!<br />
<br />
The redirect will looks something like this<br />
https://login.live.com/oauth20_desktop.srf?code=codegoeshere&lc=1033<br />
<br />
You have to extract the code param, it's your Microsoft Authorization Code.<br />
<br />
== Authorization Code -> Authorization Token ==<br />
<br />
The next step is to get a auth token from the auth code. This isn't done in the browser for security reasons.<br />
POST https://login.live.com/oauth20_token.srf<br />
Content:<br />
<br />
<syntaxhighlight lang="java" line='line'><br />
Map<Object, Object> data = Map.of(<br />
"client_id", "00000000402b5328", // minecrafts client id again<br />
"code", authcode, // the code from step 1<br />
"grant_type", "authorization_code",<br />
"redirect_uri", "https://login.live.com/oauth20_desktop.srf",<br />
"scope", "service::user.auth.xboxlive.com::MBI_SSL"<br />
);<br />
</syntaxhighlight><br />
<br />
Don't forget to set <code>Content-Type: application/x-www-form-urlencoded</code><br />
<br />
The response will look like this:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"token_type":"bearer",<br />
"expires_in":86400,<br />
"scope":"service::user.auth.xboxlive.com::MBI_SSL",<br />
"access_token":"token here",<br />
"refresh_token":"M.R3_BAY.token here",<br />
"user_id":"889ed4a3d844f672",<br />
"foci":"1"<br />
}<br />
</syntaxhighlight><br />
<br />
We care about the access_token here. (TODO: check what we can do with the refresh token)<br />
<br />
== Authenticate with XBL ==<br />
<br />
Now that we are authenticated with microsoft, we can authenticate to xbox live.<br />
<br />
To do that, we send<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
POST https://user.auth.xboxlive.com/user/authenticate<br />
{<br />
"Properties": {<br />
"AuthMethod": "RPS",<br />
"SiteName": "user.auth.xboxlive.com",<br />
"RpsTicket": "access_token" // your access token from step 2 here<br />
},<br />
"RelyingParty": "http://auth.xboxlive.com",<br />
"TokenType": "JWT"<br />
}<br />
</syntaxhighlight><br />
<br />
Again, it will complain if you don't set <code>Content-Type: application/json</code> and <code>Accept: application/json</code><br />
<br />
The response will look like this:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"IssueInstant":"2020-12-07T19:52:08.4463796Z",<br />
"NotAfter":"2020-12-21T19:52:08.4463796Z",<br />
"Token":"token", // save this, this is your xbl token<br />
"DisplayClaims":{<br />
"xui":[<br />
{<br />
"uhs":"uhs" // save this<br />
}<br />
]<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
We need to save token and uhs. I have no idea what uhs stands for. (TODO: find out)<br />
<br />
== Authenticate with XSTS ==<br />
<br />
Now that we are authenticated with XBL, we need to get a XSTS token, we can use to login to minecraft.<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
POST https://xsts.auth.xboxlive.com/xsts/authorize<br />
{<br />
"Properties": {<br />
"SandboxId": "RETAIL",<br />
"UserTokens": [<br />
"xbl_token" // from above<br />
]<br />
},<br />
"RelyingParty": "rp://api.minecraftservices.com/",<br />
"TokenType": "JWT"<br />
}<br />
</syntaxhighlight><br />
<br />
Again, set content type and accept to json.<br />
<br />
Response will look like this:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"IssueInstant":"2020-12-07T19:52:09.2345095Z",<br />
"NotAfter":"2020-12-08T11:52:09.2345095Z",<br />
"Token":"token", // save this, this is your xsts token<br />
"DisplayClaims":{<br />
"xui":[<br />
{<br />
"uhs":"" // same as last request<br />
}<br />
]<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
== Authenticate with Minecraft ==<br />
<br />
Now we can finally start talking to minecraft. The XSTS token from the last request allows us to authenticate to minecraft using<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
POST https://api.minecraftservices.com/authentication/login_with_xbox<br />
{<br />
"identityToken": "XBL3.0 x=<uhs>;<xsts_token>"<br />
}<br />
</syntaxhighlight><br />
<br />
Response:<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"username" : "some uuid", // this is not the uuid of the account<br />
"roles" : [ ],<br />
"access_token" : "minecraft access token", // jwt, your good old minecraft access token<br />
"token_type" : "Bearer",<br />
"expires_in" : 86400<br />
}<br />
</syntaxhighlight><br />
<br />
This access token allows us to launch the game, but, we haven't actually checked if the account owns the game. Everything until here works with a normal microsoft account!<br />
<br />
== Checking Game Ownership ==<br />
<br />
So let's use our mc access token to check if a product licence is attached to the account.<br />
GET https://api.minecraftservices.com/entitlements/mcstore<br />
<br />
The access token goes into the auth header: <code>Authorization: Bearer token</code><br />
<br />
If the account owns the game, the response will look like this:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"items" : [ {<br />
"name" : "product_minecraft",<br />
"signature" : "jwt sig"<br />
}, {<br />
"name" : "game_minecraft",<br />
"signature" : "jwt sig"<br />
} ],<br />
"signature" : "jwt sig",<br />
"keyId" : "1"<br />
}<br />
</syntaxhighlight><br />
<br />
The first jwts contain the values:<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"typ": "JWT",<br />
"alg": "RS256",<br />
"kid": "1"<br />
}.{<br />
"signerId": "2535416586892404",<br />
"name": "product_minecraft"<br />
}.[Signature]<br />
</syntaxhighlight><br />
<br />
the last jwt looks like this decoded:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"typ": "JWT",<br />
"alg": "RS256",<br />
"kid": "1"<br />
}.{<br />
"entitlements": [<br />
{<br />
"name": "product_minecraft"<br />
},<br />
{<br />
"name": "game_minecraft"<br />
}<br />
],<br />
"signerId": "2535416586892404"<br />
}.[Signature]<br />
</syntaxhighlight><br />
<br />
If the account doesn't own the game, the items array will be empty.<br />
<br />
== Get the profile ==<br />
<br />
Now that we know that the account owns the game, lets get his profile so we get uuid:<br />
GET https://api.minecraftservices.com/minecraft/profile<br />
<br />
Again, the access token goes into the auth header: <code>Authorization: Bearer token</code><br />
<br />
The responce will look like this, if the account owns the game:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"id" : "986dec87b7ec47ff89ff033fdb95c4b5", // the real uuid of the account, woo<br />
"name" : "HowDoesAuthWork", // the mc user name of the account<br />
"skins" : [ {<br />
"id" : "6a6e65e5-76dd-4c3c-a625-162924514568",<br />
"state" : "ACTIVE",<br />
"url" : "http://textures.minecraft.net/texture/1a4af718455d4aab528e7a61f86fa25e6a369d1768dcb13f7df319a713eb810b",<br />
"variant" : "CLASSIC",<br />
"alias" : "STEVE"<br />
} ],<br />
"capes" : [ ]<br />
}<br />
</syntaxhighlight><br />
<br />
Else it will look like this:<br />
<br />
<syntaxhighlight lang="json" line='line'><br />
{<br />
"path" : "/minecraft/profile",<br />
"errorType" : "NOT_FOUND",<br />
"error" : "NOT_FOUND",<br />
"errorMessage" : "The server has not found anything matching the request URI",<br />
"developerMessage" : "The server has not found anything matching the request URI"<br />
}<br />
</syntaxhighlight><br />
<br />
You should know have all necessary data (the mc access token, the username and the uuid) to launch the game. Well done!<br />
<br />
== Sample Implementations ==<br />
There is a rough sample implementation in Java (using javafx and its webview) [https://github.com/MiniDigger/MiniLauncher/blob/master/launcher/src/main/java/me/minidigger/minecraftlauncher/launcher/gui/MsaFragmentController.java here].</div>MrAlisYT