From d4acc813bc11d1e72a288dda1ff9da830fa4ab92 Mon Sep 17 00:00:00 2001 From: nocci Date: Sun, 8 Mar 2026 12:23:52 +0000 Subject: [PATCH] refactor(api): restructure Epic Games OAuth flow with new client credentials step 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. --- epic-claimer-new.js | 94 ++++++++++++++++++++++++++++++++------------- 1 file changed, 67 insertions(+), 27 deletions(-) diff --git a/epic-claimer-new.js b/epic-claimer-new.js index 2b82ef2..31cd309 100644 --- a/epic-claimer-new.js +++ b/epic-claimer-new.js @@ -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 {