Skip to content

Commit 1402f43

Browse files
authored
Add methods for fetching PlayFab and Minecraft Service tokens (#107)
* Add PlayFab and Minecraft Services managers * Add PlayFab example * Add types * Format Minecraft Services response * Linting * Add new caches * Correct output * Add docs * Fix some types * Rename `getMinecraftServicesToken` to `getMinecraftBedrockServicesToken` * Remove node-fetch * Update types
1 parent 6fb1902 commit 1402f43

File tree

9 files changed

+340
-5
lines changed

9 files changed

+340
-5
lines changed

README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,50 @@ flow.getMinecraftJavaToken({ fetchProfile: true }).then(console.log)
7979
### getMinecraftBedrockToken
8080
See [docs/API.md](docs/API.md) and [example](examples).
8181

82+
### getMinecraftBedrockServicesToken
83+
```js
84+
const { Authflow, Titles } = require('prismarine-auth')
85+
86+
const userIdentifier = 'any unique identifier'
87+
const cacheDir = './' // You can leave this as undefined unless you want to specify a caching directory
88+
const flow = new Authflow(userIdentifier, cacheDir)
89+
// Get a Minecraft Services token, then log it
90+
flow.getMinecraftBedrockServicesToken().then(console.log)
91+
```
92+
93+
### Expected Response
94+
```json
95+
{
96+
"mcToken": "MCToken eyJ...",
97+
"validUntil": "1970-01-01T00:00:00.000Z",
98+
"treatments": [
99+
"mc-enable-feedback-landing-page",
100+
"mc-store-enableinbox",
101+
"mc-nps-freeorpaid-paidaug24",
102+
// and more
103+
],
104+
"configurations": {
105+
"validation": {
106+
"id": "Validation",
107+
"parameters": {
108+
"minecraftnetaatest": "false"
109+
}
110+
},
111+
"minecraft": {
112+
"id": "Minecraft",
113+
"parameters": {
114+
"with-spongebobadd-button-noswitch": "true",
115+
"sfsdfsdfsfss": "true",
116+
"fsdfd": "true",
117+
"mc-maelstrom-disable": "true",
118+
// and more
119+
}
120+
}
121+
},
122+
"treatmentContext": "mc-sunsetting_5:31118471;mc-..."
123+
}
124+
```
125+
82126
### More
83127
[View more examples here](https://github.com/PrismarineJS/prismarine-auth/tree/master/examples).
84128

docs/API.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Example usage :
3939
const { Authflow } = require('prismarine-auth')
4040
const flow = new Authflow() // No parameters needed
4141
flow.getXboxToken().then(console.log)
42-
``````
42+
```
4343

4444
#### getMinecraftJavaToken (options?: { fetchEntitlements?: boolean fetchProfile?: boolean }) : Promise<{ token: string, entitlements: object, profile: object }>
4545

@@ -54,6 +54,18 @@ Returns a Minecraft: Bedrock Edition auth token. The first parameter is a Node.j
5454

5555
The return object are multiple JWTs returned from the auth server, from both the Mojang and Xbox steps.
5656

57+
### getPlayfabLogin (): Promise<GetPlayfabLoginResponse>
58+
59+
Returns a Playfab login response which can be used to authenticate to the Playfab API. The SessionTicket returned in the response is used when generating the MCToken.
60+
61+
[Returns ServerLoginResult](https://learn.microsoft.com/en-us/rest/api/playfab/server/authentication/login-with-xbox?view=playfab-rest#serverloginresult)
62+
63+
### getMinecraftBedrockServicesToken ({ version }): Promise<GetMinecraftBedrockServicesResponse>
64+
65+
Returns an mctoken which can be used to query the minecraft-services.net/api and is also used to authenticate the WebSocket connection for the NetherNet WebRTC signalling channel.
66+
67+
The return object contains the `mcToken` and `treatments` relating to the features the user has access to.
68+
5769
### Titles
5870

5971
* A list of known client IDs for convenience. Currently exposes `MinecraftNintendoSwitch` and `MinecraftJava`. These should be passed to `Authflow` options constructor (make sure to set appropriate `deviceType`).

examples/playfab/deviceCode.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const { Authflow, Titles } = require('prismarine-auth')
2+
3+
const [, , username, cacheDir] = process.argv
4+
5+
if (!username) {
6+
console.log('Usage: node deviceCode.js <username> [cacheDirectory]')
7+
process.exit(1)
8+
}
9+
10+
async function doAuth () {
11+
const flow = new Authflow(username, cacheDir, { authTitle: Titles.MinecraftNintendoSwitch, deviceType: 'Nintendo', flow: 'live' })
12+
13+
const response = await flow.getPlayfabLogin()
14+
15+
console.log(response)
16+
}
17+
18+
module.exports = doAuth()

examples/services/deviceCode.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const { Authflow, Titles } = require('prismarine-auth')
2+
3+
const [, , username, cacheDir] = process.argv
4+
5+
if (!username) {
6+
console.log('Usage: node deviceCode.js <username> [cacheDirectory]')
7+
process.exit(1)
8+
}
9+
10+
async function doAuth () {
11+
const flow = new Authflow(username, cacheDir, { authTitle: Titles.MinecraftNintendoSwitch, deviceType: 'Nintendo', flow: 'live' })
12+
13+
const response = await flow.getMinecraftBedrockServicesToken({ version: '1.21.50' })
14+
15+
console.log(response)
16+
}
17+
18+
module.exports = doAuth()

index.d.ts

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ import { KeyObject } from 'crypto'
33

44
declare module 'prismarine-auth' {
55
export class Authflow {
6+
7+
username: string
8+
9+
options: MicrosoftAuthFlowOptions
10+
611
/**
712
* Creates a new Authflow instance, which holds its own token cache
813
* @param username A unique identifier. If using password auth, this should be an email.
@@ -15,7 +20,7 @@ declare module 'prismarine-auth' {
1520
// Returns a Microsoft Oauth access token -- https://docs.microsoft.com/en-us/azure/active-directory/develop/access-tokens
1621
getMsaToken(): Promise<string>
1722
// Returns an XSTS token -- https://docs.microsoft.com/en-us/gaming/xbox-live/api-ref/xbox-live-rest/additional/edsauthorization
18-
getXboxToken(relyingParty?: string): Promise<{
23+
getXboxToken(relyingParty?: string, forceRefresh?: boolean): Promise<{
1924
userXUID: string,
2025
userHash: string,
2126
XSTSToken: string,
@@ -29,6 +34,11 @@ declare module 'prismarine-auth' {
2934
}): Promise<{ token: string, entitlements: MinecraftJavaLicenses, profile: MinecraftJavaProfile, certificates: MinecraftJavaCertificates }>
3035
// Returns a Minecraft Bedrock Edition auth token. Public key parameter must be a KeyLike object.
3136
getMinecraftBedrockToken(publicKey: KeyObject): Promise<string>
37+
38+
getMinecraftBedrockServicesToken(config: { version: string }): Promise<GetMinecraftBedrockServicesResponse>
39+
40+
getPlayfabLogin(): Promise<GetPlayfabLoginResponse>
41+
3242
}
3343

3444
// via request to https://api.minecraftservices.com/entitlements/license, a list of licenses the player has
@@ -144,4 +154,69 @@ declare module 'prismarine-auth' {
144154
}
145155

146156
export type CacheFactory = (options: { username: string, cacheName: string }) => Cache
157+
158+
export type GetMinecraftBedrockServicesResponse = {
159+
mcToken: string
160+
validUntil: string
161+
treatments: string[]
162+
treatmentContext: string
163+
configurations: object
164+
}
165+
166+
export type GetPlayfabLoginResponse = {
167+
SessionTicket: string;
168+
PlayFabId: string;
169+
NewlyCreated: boolean;
170+
SettingsForUser: {
171+
NeedsAttribution: boolean;
172+
GatherDeviceInfo: boolean;
173+
GatherFocusInfo: boolean;
174+
};
175+
LastLoginTime: string;
176+
InfoResultPayload: {
177+
AccountInfo: {
178+
PlayFabId: string;
179+
Created: string;
180+
TitleInfo: {
181+
Origination: string;
182+
Created: string;
183+
LastLogin: string;
184+
FirstLogin: string;
185+
isBanned: boolean;
186+
TitlePlayerAccount: {
187+
Id: string;
188+
Type: string;
189+
TypeString: string;
190+
};
191+
};
192+
PrivateInfo: Record<string, unknown>;
193+
XboxInfo: {
194+
XboxUserId: string;
195+
XboxUserSandbox: string;
196+
};
197+
};
198+
UserInventory: any[];
199+
UserDataVersion: number;
200+
UserReadOnlyDataVersion: number;
201+
CharacterInventories: any[];
202+
PlayerProfile: {
203+
PublisherId: string;
204+
TitleId: string;
205+
PlayerId: string;
206+
};
207+
};
208+
EntityToken: {
209+
EntityToken: string;
210+
TokenExpiration: string;
211+
Entity: {
212+
Id: string;
213+
Type: string;
214+
TypeString: string;
215+
};
216+
};
217+
TreatmentAssignment: {
218+
Variants: any[];
219+
Variables: any[];
220+
};
221+
}
147222
}

src/MicrosoftAuthFlow.js

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const path = require('path')
33
const crypto = require('crypto')
44
const debug = require('debug')('prismarine-auth')
55

6+
const Titles = require('./common/Titles')
67
const { createHash } = require('./common/Util')
78
const { Endpoints, msalConfig } = require('./common/Constants')
89
const FileCache = require('./common/cache/FileCache')
@@ -12,7 +13,8 @@ const JavaTokenManager = require('./TokenManagers/MinecraftJavaTokenManager')
1213
const XboxTokenManager = require('./TokenManagers/XboxTokenManager')
1314
const MsaTokenManager = require('./TokenManagers/MsaTokenManager')
1415
const BedrockTokenManager = require('./TokenManagers/MinecraftBedrockTokenManager')
15-
const Titles = require('./common/Titles')
16+
const PlayfabTokenManager = require('./TokenManagers/PlayfabTokenManager')
17+
const MinecraftServicesTokenManager = require('./TokenManagers/MinecraftBedrockServicesManager')
1618

1719
async function retry (methodFn, beforeRetry, times) {
1820
while (times--) {
@@ -26,7 +28,7 @@ async function retry (methodFn, beforeRetry, times) {
2628
}
2729
}
2830

29-
const CACHE_IDS = ['msal', 'live', 'sisu', 'xbl', 'bed', 'mca']
31+
const CACHE_IDS = ['msal', 'live', 'sisu', 'xbl', 'bed', 'mca', 'mcs', 'pfb']
3032

3133
class MicrosoftAuthFlow {
3234
constructor (username = '', cache = __dirname, options, codeCallback) {
@@ -87,6 +89,8 @@ class MicrosoftAuthFlow {
8789
this.xbl = new XboxTokenManager(keyPair, cache({ cacheName: 'xbl', username }))
8890
this.mba = new BedrockTokenManager(cache({ cacheName: 'bed', username }))
8991
this.mca = new JavaTokenManager(cache({ cacheName: 'mca', username }))
92+
this.mcs = new MinecraftServicesTokenManager(cache({ cacheName: 'mcs', username }))
93+
this.pfb = new PlayfabTokenManager(cache({ cacheName: 'pfb', username }))
9094
}
9195

9296
async getMsaToken () {
@@ -113,6 +117,34 @@ class MicrosoftAuthFlow {
113117
}
114118
}
115119

120+
async getPlayfabLogin () {
121+
const cache = this.pfb.getCachedAccessToken()
122+
123+
if (cache.valid) {
124+
return cache.data
125+
}
126+
127+
const xsts = await this.getXboxToken(Endpoints.PlayfabRelyingParty)
128+
129+
const playfab = await this.pfb.getAccessToken(xsts)
130+
131+
return playfab
132+
}
133+
134+
async getMinecraftBedrockServicesToken ({ verison }) {
135+
const cache = await this.mcs.getCachedAccessToken()
136+
137+
if (cache.valid) {
138+
return cache.data
139+
}
140+
141+
const playfab = await this.getPlayfabLogin()
142+
143+
const mcs = await this.mcs.getAccessToken(playfab.SessionTicket, { verison })
144+
145+
return mcs
146+
}
147+
116148
async getXboxToken (relyingParty = this.options.relyingParty || Endpoints.XboxRelyingParty, forceRefresh = false) {
117149
const options = { ...this.options, relyingParty }
118150

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
const debug = require('debug')('prismarine-auth')
2+
3+
const { Endpoints } = require('../common/Constants')
4+
const { checkStatus } = require('../common/Util')
5+
6+
class MinecraftBedrockServicesTokenManager {
7+
constructor (cache) {
8+
this.cache = cache
9+
}
10+
11+
async getCachedAccessToken () {
12+
const { mcs: token } = await this.cache.getCached()
13+
debug('[mcs] token cache', token)
14+
15+
if (!token) return { valid: false }
16+
17+
const expires = new Date(token.validUntil)
18+
const remainingMs = expires - Date.now()
19+
const valid = remainingMs > 1000
20+
return { valid, until: expires, token: token.mcToken, data: token }
21+
}
22+
23+
async setCachedToken (data) {
24+
await this.cache.setCachedPartial(data)
25+
}
26+
27+
async getAccessToken (sessionTicket, options = {}) {
28+
const response = await fetch(Endpoints.MinecraftServicesSessionStart, {
29+
method: 'post',
30+
headers: { 'Content-Type': 'application/json' },
31+
body: JSON.stringify({
32+
device: {
33+
applicationType: options.applicationType ?? 'MinecraftPE',
34+
gameVersion: options.version ?? '1.20.62',
35+
id: options.deviceId ?? 'c1681ad3-415e-30cd-abd3-3b8f51e771d1',
36+
memory: options.deviceMemory ?? String(8 * (1024 * 1024 * 1024)),
37+
platform: options.platform ?? 'Windows10',
38+
playFabTitleId: options.playFabtitleId ?? '20CA2',
39+
storePlatform: options.storePlatform ?? 'uwp.store',
40+
type: options.type ?? 'Windows10'
41+
},
42+
user: {
43+
token: sessionTicket,
44+
tokenType: 'PlayFab'
45+
}
46+
})
47+
}).then(checkStatus)
48+
49+
const tokenResponse = {
50+
mcToken: response.result.authorizationHeader,
51+
validUntil: response.result.validUntil,
52+
treatments: response.result.treatments,
53+
configurations: response.result.configurations,
54+
treatmentContext: response.result.treatmentContext
55+
}
56+
57+
debug('[mc] mc-services token response', tokenResponse)
58+
59+
await this.setCachedToken({ mcs: tokenResponse })
60+
61+
return tokenResponse
62+
}
63+
}
64+
65+
module.exports = MinecraftBedrockServicesTokenManager

0 commit comments

Comments
 (0)