refactor(api): restructure Epic Games OAuth flow with new client credentials step
Some checks failed
build-and-push / lint (push) Failing after 8s
build-and-push / sonar (push) Has been skipped
build-and-push / docker (push) Has been skipped

The OAuth device flow has been refactored to use the client credentials grant flow as the first step, followed by a proper device authorization request using the obtained client credentials token. This change modernizes the authentication flow to align with current Epic Games OAuth requirements and replaces the previous direct device authorization approach that used client_id and client_secret in the request body with the standardized authorization header pattern.
This commit is contained in:
nocci 2026-03-08 12:23:52 +00:00
parent e8c28db63d
commit d4acc813bc

View file

@ -47,11 +47,16 @@ const fetchFreeGamesAPI = async () => {
const pollForTokens = async (deviceCode, maxAttempts = 30) => {
for (let i = 0; i < maxAttempts; i++) {
try {
const response = await axios.post('https://account-public-service-prod.ol.epicgames.com/account/api/oauth/token', {
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
device_code: deviceCode,
client_id: '98f7e42c2e3a4f86a74eb43fbb41ed39',
client_secret: '0a2449a2-001a-451e-afec-3e812901c4d7',
const params = new URLSearchParams();
params.append('grant_type', 'urn:ietf:params:oauth:grant-type:device_code');
params.append('device_code', deviceCode);
params.append('client_id', '98f7e42c2e3a4f86a74eb43fbb41ed39');
params.append('client_secret', '0a2449a2-001a-451e-afec-3e812901c4d7');
const response = await axios.post('https://account-public-service-prod.ol.epicgames.com/account/api/oauth/token', params.toString(), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
});
if (response.data?.access_token) {
console.log('✅ OAuth successful');
@ -84,6 +89,48 @@ const exchangeTokenForCookies = async accessToken => {
return cookies;
};
// Get client credentials token (first step of OAuth flow)
const getClientCredentialsToken = async () => {
try {
const response = await axios.post('https://account-public-service-prod.ol.epicgames.com/account/api/oauth/token', {
grant_type: 'client_credentials',
}, {
auth: {
username: '98f7e42c2e3a4f86a74eb43fbb41ed39',
password: '0a2449a2-001a-451e-afec-3e812901c4d7',
},
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
});
return response.data.access_token;
} catch (error) {
console.error('Failed to get client credentials token:', error.response?.status || error.message);
throw error;
}
};
// Get device authorization code (second step of OAuth flow)
const getDeviceAuthorizationCode = async (clientCredentialsToken) => {
try {
const params = new URLSearchParams();
params.append('prompt', 'login');
const response = await axios.post('https://account-public-service-prod.ol.epicgames.com/account/api/oauth/deviceAuthorization', params.toString(), {
headers: {
Authorization: `Bearer ${clientCredentialsToken}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
});
console.log('Device authorization response:', response.data);
return response.data;
} catch (error) {
console.error('Failed to get device authorization code:', error.response?.status || error.message);
console.error('Error response data:', error.response?.data);
throw error;
}
};
// Get valid authentication
const getValidAuth = async ({ otpKey, reuseCookies, cookiesPath }) => {
if (reuseCookies && existsSync(cookiesPath)) {
@ -96,25 +143,13 @@ const getValidAuth = async ({ otpKey, reuseCookies, cookiesPath }) => {
}
console.log('🔐 Starting fresh OAuth device flow (manual approval required)...');
let deviceResponse;
try {
const params = new URLSearchParams();
params.append('clientId', '98f7e42c2e3a4f86a74eb43fbb41ed39');
params.append('clientSecret', '0a2449a2-001a-451e-afec-3e812901c4d7');
params.append('scope', 'account.basicprofile account.userentitlements');
// Step 1: Get client credentials token
const clientCredentialsToken = await getClientCredentialsToken();
console.log('✅ Got client credentials token');
deviceResponse = await axios.post('https://account-public-service-prod.ol.epicgames.com/account/api/oauth/deviceAuthorization', params.toString(), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
});
} catch (error) {
console.error('Device code flow failed (fallback to manual login):', error.response?.status || error.message);
return { bearerToken: null, cookies: [] };
}
const { deviceCode, userCode, verificationUriComplete } = deviceResponse.data;
// Step 2: Get device authorization code
const { deviceCode, userCode, verificationUriComplete } = await getDeviceAuthorizationCode(clientCredentialsToken);
console.log(`📱 Open: ${verificationUriComplete}`);
console.log(`💳 Code: ${userCode}`);
@ -124,11 +159,16 @@ const getValidAuth = async ({ otpKey, reuseCookies, cookiesPath }) => {
const totpCode = authenticator.generate(otpKey);
console.log(`🔑 TOTP Code (generated): ${totpCode}`);
try {
const refreshed = await axios.post('https://account-public-service-prod.ol.epicgames.com/account/api/oauth/token', {
grant_type: 'refresh_token',
refresh_token: tokens.refresh_token,
client_id: '98f7e42c2e3a4f86a74eb43fbb41ed39',
client_secret: '0a2449a2-001a-451e-afec-3e812901c4d7',
const params = new URLSearchParams();
params.append('grant_type', 'refresh_token');
params.append('refresh_token', tokens.refresh_token);
params.append('client_id', '98f7e42c2e3a4f86a74eb43fbb41ed39');
params.append('client_secret', '0a2449a2-001a-451e-afec-3e812901c4d7');
const refreshed = await axios.post('https://account-public-service-prod.ol.epicgames.com/account/api/oauth/token', params.toString(), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
});
tokens.access_token = refreshed.data.access_token;
} catch {