[Step 2] Receiving a 403 no matter what I try (SOLVED) #501
-
I've tried countless combinations and haven't figured out how to get it working. For step 1, I request the login page like so (code has been simplified a bit): const loginResponse = await axios.get('https://auth.tesla.com/oauth2/v3/authorize?client_id=ownerapi&code_challenge=QuRXF6eDY3Vxn1nPXTRyMg7RqUUNR2t4p2cSuj3GHdw&code_challenge_method=S256&redirect_uri=https:%2F%2Fauth.tesla.com%2Fvoid%2Fcallback&response_type=code&scope=openid+email+offline_access&state=123&login_hint=email%40example.com', {
headers: {
Authority: 'auth.tesla.com',
Pragma: 'no-cache',
'Cache-Control': 'no-cache',
'User-Agent': 'curl / 6.14.0',
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'Accept-Encoding': 'deflate',
}
}); I got these headers from my browser, but have tried countless combinations, including setting none for this request. Once I receive the response, I get the values from the hidden inputs with these query selectors: const doc = new DOMParser().parseFromString(loginResponse.data, 'text/html');
const csrfElem = doc.querySelector('.sign-in-form input[name="_csrf"]') as HTMLInputElement;
const phaseElem = doc.querySelector('.sign-in-form input[name="_phase"]') as HTMLInputElement;
const processElem = doc.querySelector('.sign-in-form input[name="_process"]') as HTMLInputElement;
const transactionIdElem = doc.querySelector('.sign-in-form input[name="transaction_id"]') as HTMLInputElement;
const cancelElem = doc.querySelector('.sign-in-form input[name="cancel"]') as HTMLInputElement; and I get the cookie string for the Set-Cookie headers. The resulting cookieString looks like this: const cookieString = 'tesla-auth.sid=s%3AjNLzr0weKc0hsHvsfDe4cVH9u8iLokif.PzJ1bPra4uhsq%2FGMLQrvZIcglaYb88XRso23D1maLd0; _abck=0103574A9E1F365204699427E4F824A9~-1~YAAQGXFAF2v0iph9AQAAp7R0wQcJkmL8KCwOoq4RykD4PxLO033y4bc3zxFxu21mz7An3VA6a6%2B7x91s%2FtLt7RMQONWx0JwOk4wuh1wx202NcBLTY3JeUpWGM3fNZ1CTNStB2WNYV%2BlGr88ZOQ%2FAOQme90VCj8VVQY1FAK73lcTUCCWKseR6Ze0QSGc5kPvpQGfyUAjofLO6CXu%2BiEhioq23XWQc723ZA84BF8QIaTyOTvC97oCt%2BTNl743kO0NTvMRWsckfnPoP%2BS%2Bcv321xfEzO4F8xN6rkjK3J%2BycRVehgEliNrlZiGXIJRNblwe5GrwUVwJBq8Nsbl%2BiAtS%2FLKd4RA3OQ5VE6T2uDMDmp0zrL%2B%2BGV8vXWA%3D%3D~-1~-1~-1; bm_sz=D40F909779F151A56E603FB1F93F8999~YAAQGXFAF2z0iph9AQAAp7R0wQ63tH6YxUcguD0P1Y%2FR3CduRcqwTKKI6B8yIlCmv2Qgwkgpyg2hHh0EDLKQHhShxyexF5K5tM9qff4v1wAbY%2BAwei5ReQ9Me%2Fqt%2Fv8MhNUxNqbIyTb8nkyqRt5sI1TjqgSNyFYUn8aQrcS7KFeyzHDBr6tpCp4epsceHxEx%2B5MV6hgHMLd%2FiUmId%2FWg1VE%2B0mUKOtBO9Pt99NbPe1bRybLFYIVFryzLgNr1UKQVILvVFiq2SXw8fpds0h%2FIpwezKXaBu%2BYdfcYgYie5qQG8Lw%3D%3D~3159364~4343109'; and for step 2: const requestBody = {
_csrf: csrfElem.value,
_phase: phaseElem.value,
_process: processElem.value,
transaction_id: transactionIdElem.value,
cancel: cancelElem.value,
identity: 'email@example.com',
credential: 'password',
};
const formUrlencoded = new URLSearchParams();
Object.keys(requestBody).forEach(key => formUrlencoded.append(key, requestBody[key as keyof typeof requestBody]));
// this becomes:
// _csrf=xmkv1fcv-EvQ7RnbeeHXwPHBWu0VvnmGWusE&_phase=authenticate&_process=1&transaction_id=tMk4gsVY&cancel=&identity=email%40example.com&credential=password
const authResponse = await axios.post('https://auth.tesla.com/oauth2/v3/authorize?client_id=ownerapi&code_challenge=QuRXF6eDY3Vxn1nPXTRyMg7RqUUNR2t4p2cSuj3GHdw&code_challenge_method=S256&redirect_uri=https:%2F%2Fauth.tesla.com%2Fvoid%2Fcallback&response_type=code&scope=openid+email+offline_access&state=123',
formUrlencoded.toString(), {
headers: {
Cookie: cookieString,
Authority: 'auth.tesla.com',
Pragma: 'no-cache',
'Cache-Control': 'no-cache',
Origin: 'https://auth.tesla.com',
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'curl / 6.14.0',
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'Accept-Encoding': 'deflate',
Referer: 'https://auth.tesla.com/oauth2/v3/authorize?client_id=ownerapi&code_challenge=QuRXF6eDY3Vxn1nPXTRyMg7RqUUNR2t4p2cSuj3GHdw&code_challenge_method=S256&redirect_uri=https:%2F%2Fauth.tesla.com%2Fvoid%2Fcallback&response_type=code&scope=openid+email+offline_access&state=123&login_hint=email%40example.com',
},
}); Again, I got these headers from my browser, but I've tried countless variations, including what's currently in the documentation. I've also tried just including the session cookie and not the others. I've also tried many different User-Agent strings. The response from this request is always:
What's really strange is even when I do step 1 in my browser, grab the values in my browser, and submit step 2 programmatically with those values exactly as my browser would, I get a 403. I just can't figure it out. If I go through it all in my browser, it works fine. |
Beta Was this translation helpful? Give feedback.
Replies: 4 comments 11 replies
-
I'm also forcing TLSv1.2 In summary: code_verifier nilK6TnHaDKLtworrPSORCUuo5DyB4AXAe92DsXDd4xJj2jE3LEKfGZE_9CRlcvETdUH3_FTrYAFDySzvoA85A
request1 {
url: 'https://auth.tesla.com/oauth2/v3/authorize?client_id=ownerapi&code_challenge=ZI3Z0vqHuTy130iK3nH6aRznBWM4EbSQSxivqTDhIls&code_challenge_method=S256&redirect_uri=https:%2F%2Fauth.tesla.com%2Fvoid%2Fcallback&response_type=code&scope=openid+email+offline_access&state=123&login_hint=email%40example.com',
headers: {
Authority: 'auth.tesla.com',
Pragma: 'no-cache',
'Cache-Control': 'no-cache',
'User-Agent': 'curl / 6.14.0',
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'Accept-Encoding': 'deflate'
}
}
response1 {
responseHeaders: {
'content-type': 'text/html; charset=utf-8',
'x-dns-prefetch-control': 'off',
'x-frame-options': 'DENY',
'strict-transport-security': 'max-age=15552000; includeSubDomains',
'x-download-options': 'noopen',
'x-content-type-options': 'nosniff',
'x-xss-protection': '1; mode=block',
'x-request-id': 'd83c4a70-761d-4d5e-bf4c-43bdfc35d1e9',
'x-correlation-id': 'd83c4a70-761d-4d5e-bf4c-43bdfc35d1e9',
'cache-control': 'no-store',
'content-security-policy': "connect-src 'self'; default-src 'none'; font-src 'self' data: fonts.gstatic.com; frame-src 'self' www.google.com www.recaptcha.net; img-src 'self' data:; script-src www.recaptcha.net 'self' 'nonce-93a00b92b69567e79fd9'; style-src 'unsafe-inline' 'self'",
'x-content-security-policy': "connect-src 'self'; default-src 'none'; font-src 'self' data: fonts.gstatic.com; frame-src 'self' www.google.com www.recaptcha.net; img-src 'self' data:; script-src www.recaptcha.net 'self' 'nonce-93a00b92b69567e79fd9'; style-src 'unsafe-inline' 'self'",
'x-webkit-csp': "connect-src 'self'; default-src 'none'; font-src 'self' data: fonts.gstatic.com; frame-src 'self' www.google.com www.recaptcha.net; img-src 'self' data:; script-src www.recaptcha.net 'self' 'nonce-93a00b92b69567e79fd9'; style-src 'unsafe-inline' 'self'",
etag: 'W/"94d4-bk3uJsCu2SSS6dlZXU22nDyPJks"',
'x-response-time': '23.100ms',
'x-edgeconnect-midmile-rtt': '0',
'x-edgeconnect-origin-mex-latency': '90',
'x-akamai-transformed': '9 11158 0 pmb=mTOE,1',
vary: 'Accept-Encoding',
date: 'Thu, 16 Dec 2021 04:45:05 GMT',
'content-length': '38210',
connection: 'close',
'set-cookie': [
'tesla-auth.sid=s%3AKwqvj1UK0TGuzU3VwAvmZOvUhGAcxl4n.ka6WacoFoLCiBhOohiy8u1z1QSPudfm%2FhIh%2ByK1egmA; Path=/; Expires=Sun, 19 Dec 2021 04:45:05 GMT; HttpOnly; Secure; SameSite=Lax',
'_abck=BB1A7E092EBFA555E0981E37BE79440C~-1~YAAQGXFAF8y0i5h9AQAAsd+OwQfUgaumCHdhtCdU2Dd0t2SPMP8LuU3vrKVIGuDqdPjU6wWPen0iG1xW5vsPT4oAL1+wyhUy9XF906iz78rmrlc7ETj+gOcpmXUPIeTqrUV//DVwlQvFDXOk1IWkfI99PtXhp8zN8HIY6xrPTIx6cv8d44L3eu64KMl76dCFdD/4vzXUG+n2rjl/hL3JPz49E6gvYQV5U3AZ+DW4zAplWbhyQCuWEXkkTJ5wls2oS3VU8xnZoPAUvbSvmBQi86ai1zAWyZbqnXmXvNgmBnLq2wuyMIViVK0f3XK3nQMOE+FPwqp3sOMFx5QlfA+CyTfrQ/0CAytaNo/GSnT1uaI1jvUW3bIZRw==~-1~-1~-1; Domain=.tesla.com; Path=/; Expires=Fri, 16 Dec 2022 04:45:05 GMT; Max-Age=31536000; Secure',
'bm_sz=4F952ED5019D8083840F4B20A175E1C8~YAAQGXFAF820i5h9AQAAsd+OwQ7jZgdnds67OforfzuIA80NS3G07ODafgBmaOclCTXtcaUQr4Kgw3jhRpk4aAnDyU0D0KkPYyE932vwgwdeKOG41beAftUMh8Ujv4LtjJgrjN+UUAzgUduZLm86IxyNZpSjmmOf7XfJ1WqxXCsAmxx0/z3FR7C9Sb4lHuMF/uWcLMB6CZ+Ag9ZcArZCB5xpuyetd8ryDB4mgRtcBIBm5BNGUUqkzBBD3RZaNTBagpx3HS/VEaehOj6I1bi/vJRugvgVpWfHqdnur5eJdMHYwA==~4539447~3687474; Domain=.tesla.com; Path=/; Expires=Thu, 16 Dec 2021 08:45:05 GMT; Max-Age=14400'
],
'permissions-policy': 'interest-cohort=()'
},
responseForm: '<form method="post" id="form" class="sso-form sign-in-form">\n' +
' <input type="hidden" name="_csrf" value="IORC2VHO-lGHqWdyBtuBniwqscxyf6MoRbV8">\n' +
' <input type="hidden" name="_phase" value="authenticate">\n' +
' <input type="hidden" name="_process" value="1">\n' +
' <input type="hidden" name="transaction_id" value="gJ1HtuO3">\n' +
' <input type="hidden" name="cancel" value="" id="form-input-cancel">\n' +
'\n' +
' <div class="tds-form-item tds-form-item--text" data-field="identity">\n' +
' <div class="tds-form-label-wrap">\n' +
' <label class="tds-form-label" for="form-input-identity">\n' +
' <span class="tds-form-label-text" data-i18n-key="login:formIdentityLabel1">Email Address</span>\n' +
' </label>\n' +
' <div class="tds-tooltip-wrapper">\n' +
' <div class="tds-form-label-tooltip">\n' +
' <svg class="tds-icon tds-icon--inline tds-icon-status-info" viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg" data-tds-tooltip-trigger="true" tabindex="0" role="img" aria-label="Open Email Address Tooltip">\n' +
' <title>Email Address Tooltip</title>\n' +
' <use xlink:href="#tds-icon-status-info"></use>\n' +
' </svg>\n' +
' <div class="tds-tooltip tds-tooltip--orientation-down" role="tooltip" aria-label="Email Address Tooltip">\n' +
'\n' +
' <p data-i18n-key="login:formIdentityTooltipText1">\n' +
' If your account is linked to an email address you no longer have access to, please sign into your account and update your email address under account settings\n' +
' </p>\n' +
' <p data-i18n-key="login:formIdentityTooltipText2" data-i18n-data="{"link":{"start":"<a href=\\"https://www.tesla.com/support/account-support?redirect=no\\" target=\\"_blank\\" class=\\"tds-link\\">","end":"</a>"}}">\n' +
' If you have trouble signing in, visit our support page\n' +
' </p>\n' +
' </div>\n' +
' </div>\n' +
' </div>\n' +
' </div>\n' +
' <div class="tds-form-input-wrap">\n' +
' <input class="tds-form-input" id="form-input-identity" type="text" name="identity" value="email@example.com" autocomplete="off" autocorrect="off" autocapitalize="none" aria-label="email" required="">\n' +
' </div>\n' +
' <div class="tds-form-feedback-wrap">\n' +
' <div class="tds-form-feedback-helper"></div>\n' +
' <div class="tds-form-feedback-feedback">\n' +
' <div class="tds-form-item-feedback"></div> \n' +
' </div>\n' +
' </div>\n' +
' </div>\n' +
'\n' +
'\n' +
'\n' +
' <div class="tds-form-item tds-form-item--password" data-showlabel="Show Password" data-hidelabel="Hide Password" data-showicon="tds-icon-eye" data-hideicon="tds-icon-eye--off" data-field="password">\n' +
' <div class="tds-form-label-wrap">\n' +
' <label class="tds-form-label" for="form-input-credential">\n' +
' <span class="tds-form-label-text" data-i18n-key="login:formPasswordLabel">Password</span>\n' +
' </label>\n' +
' </div>\n' +
' <div class="tds-form-input-wrap" data-field="password">\n' +
' <input class="tds-form-input" id="form-input-credential" type="password" name="credential" autocomplete="off" autocorrect="off" autocapitalize="none" aria-label="password" required=""> \n' +
' <button class="tds-password-input--toggle tds-form-input-trailing" type="button">\n' +
' <svg class="tds-icon" role="img">\n' +
' <title>Show/Hide Password</title>\n' +
' <desc>Click Icon to toggle password visibility</desc>\n' +
' <use xlink:href="#tds-icon-eye"></use>\n' +
' </svg>\n' +
' </button>\n' +
' </div>\n' +
' <div class="tds-form-feedback-wrap">\n' +
' <div class="tds-form-feedback-helper"></div>\n' +
' <div class="tds-form-feedback-feedback">\n' +
' <div class="tds-form-item-feedback"></div>\n' +
' </div>\n' +
' </div>\n' +
' </div>\n' +
'\n' +
'\n' +
'\n' +
' \n' +
' <div data-field="recaptcha" class="recaptcha tds-form-item" style="margin: 0 auto;">\n' +
' <div class="tds-form-label-wrap">\n' +
' <div class="g-recaptcha tds-text--center"></div>\n' +
' \n' +
' </div>\n' +
' <div class="tds-form-feedback-wrap">\n' +
' <div class="tds-form-feedback-feedback">\n' +
' <div class="tds-form-item-feedback"></div>\n' +
' </div>\n' +
' </div>\n' +
' </div>\n' +
' \n' +
'\n' +
'\n' +
' \n' +
'\n' +
'\n' +
' \n' +
'\n' +
'\n' +
'\n' +
'\n' +
'\n' +
' <button type="submit" class="tds-btn tds-btn--primary tds-btn--full" data-i18n-key="login:formSubmitLabel" id="form-submit-continue">\n' +
' Sign In\n' +
' </button>\n' +
'\n' +
' \n' +
'\n' +
' <p class="need-help tds-text--center">\n' +
' \n' +
' \n' +
' <a href="https://www.tesla.com/support/account-support?redirect=no" target="_blank" class="tds-link" data-i18n-key="login:forgotSupportLinkLabel">Forgot email?</a>\n' +
' <span class="tmp-link-separator">|</span>\n' +
' \n' +
' <a href="/user/password/forgot" class="tds-link" data-i18n-key="login:forgotPasswordLinkLabel">Forgot password?</a>\n' +
' </p>\n' +
'\n' +
' </form>',
status: 200
}
cookies {
cookies: [
{
name: 'tesla-auth.sid',
value: 's:Kwqvj1UK0TGuzU3VwAvmZOvUhGAcxl4n.ka6WacoFoLCiBhOohiy8u1z1QSPudfm/hIh+yK1egmA',
path: '/',
expires: 2021-12-19T04:45:05.000Z,
httpOnly: true,
secure: true,
sameSite: 'Lax'
},
{
name: '_abck',
value: 'BB1A7E092EBFA555E0981E37BE79440C~-1~YAAQGXFAF8y0i5h9AQAAsd+OwQfUgaumCHdhtCdU2Dd0t2SPMP8LuU3vrKVIGuDqdPjU6wWPen0iG1xW5vsPT4oAL1+wyhUy9XF906iz78rmrlc7ETj+gOcpmXUPIeTqrUV//DVwlQvFDXOk1IWkfI99PtXhp8zN8HIY6xrPTIx6cv8d44L3eu64KMl76dCFdD/4vzXUG+n2rjl/hL3JPz49E6gvYQV5U3AZ+DW4zAplWbhyQCuWEXkkTJ5wls2oS3VU8xnZoPAUvbSvmBQi86ai1zAWyZbqnXmXvNgmBnLq2wuyMIViVK0f3XK3nQMOE+FPwqp3sOMFx5QlfA+CyTfrQ/0CAytaNo/GSnT1uaI1jvUW3bIZRw==~-1~-1~-1',
domain: '.tesla.com',
path: '/',
expires: 2022-12-16T04:45:05.000Z,
maxAge: 31536000,
secure: true
},
{
name: 'bm_sz',
value: '4F952ED5019D8083840F4B20A175E1C8~YAAQGXFAF820i5h9AQAAsd+OwQ7jZgdnds67OforfzuIA80NS3G07ODafgBmaOclCTXtcaUQr4Kgw3jhRpk4aAnDyU0D0KkPYyE932vwgwdeKOG41beAftUMh8Ujv4LtjJgrjN+UUAzgUduZLm86IxyNZpSjmmOf7XfJ1WqxXCsAmxx0/z3FR7C9Sb4lHuMF/uWcLMB6CZ+Ag9ZcArZCB5xpuyetd8ryDB4mgRtcBIBm5BNGUUqkzBBD3RZaNTBagpx3HS/VEaehOj6I1bi/vJRugvgVpWfHqdnur5eJdMHYwA==~4539447~3687474',
domain: '.tesla.com',
path: '/',
expires: 2021-12-16T08:45:05.000Z,
maxAge: 14400
}
]
}
cookieString: tesla-auth.sid=s%3AKwqvj1UK0TGuzU3VwAvmZOvUhGAcxl4n.ka6WacoFoLCiBhOohiy8u1z1QSPudfm%2FhIh%2ByK1egmA; _abck=BB1A7E092EBFA555E0981E37BE79440C~-1~YAAQGXFAF8y0i5h9AQAAsd%2BOwQfUgaumCHdhtCdU2Dd0t2SPMP8LuU3vrKVIGuDqdPjU6wWPen0iG1xW5vsPT4oAL1%2BwyhUy9XF906iz78rmrlc7ETj%2BgOcpmXUPIeTqrUV%2F%2FDVwlQvFDXOk1IWkfI99PtXhp8zN8HIY6xrPTIx6cv8d44L3eu64KMl76dCFdD%2F4vzXUG%2Bn2rjl%2FhL3JPz49E6gvYQV5U3AZ%2BDW4zAplWbhyQCuWEXkkTJ5wls2oS3VU8xnZoPAUvbSvmBQi86ai1zAWyZbqnXmXvNgmBnLq2wuyMIViVK0f3XK3nQMOE%2BFPwqp3sOMFx5QlfA%2BCyTfrQ%2F0CAytaNo%2FGSnT1uaI1jvUW3bIZRw%3D%3D~-1~-1~-1; bm_sz=4F952ED5019D8083840F4B20A175E1C8~YAAQGXFAF820i5h9AQAAsd%2BOwQ7jZgdnds67OforfzuIA80NS3G07ODafgBmaOclCTXtcaUQr4Kgw3jhRpk4aAnDyU0D0KkPYyE932vwgwdeKOG41beAftUMh8Ujv4LtjJgrjN%2BUUAzgUduZLm86IxyNZpSjmmOf7XfJ1WqxXCsAmxx0%2Fz3FR7C9Sb4lHuMF%2FuWcLMB6CZ%2BAg9ZcArZCB5xpuyetd8ryDB4mgRtcBIBm5BNGUUqkzBBD3RZaNTBagpx3HS%2FVEaehOj6I1bi%2FvJRugvgVpWfHqdnur5eJdMHYwA%3D%3D~4539447~3687474
formUrlencoded.toString() _csrf=IORC2VHO-lGHqWdyBtuBniwqscxyf6MoRbV8&_phase=authenticate&_process=1&transaction_id=gJ1HtuO3&cancel=&identity=email%40example.com&credential=password
request2 {
url: 'https://auth.tesla.com/oauth2/v3/authorize?client_id=ownerapi&code_challenge=ZI3Z0vqHuTy130iK3nH6aRznBWM4EbSQSxivqTDhIls&code_challenge_method=S256&redirect_uri=https:%2F%2Fauth.tesla.com%2Fvoid%2Fcallback&response_type=code&scope=openid+email+offline_access&state=123',
payload: '_csrf=IORC2VHO-lGHqWdyBtuBniwqscxyf6MoRbV8&_phase=authenticate&_process=1&transaction_id=gJ1HtuO3&cancel=&identity=email%40example.com&credential=password',
headers: {
Cookie: 'tesla-auth.sid=s%3AKwqvj1UK0TGuzU3VwAvmZOvUhGAcxl4n.ka6WacoFoLCiBhOohiy8u1z1QSPudfm%2FhIh%2ByK1egmA; _abck=BB1A7E092EBFA555E0981E37BE79440C~-1~YAAQGXFAF8y0i5h9AQAAsd%2BOwQfUgaumCHdhtCdU2Dd0t2SPMP8LuU3vrKVIGuDqdPjU6wWPen0iG1xW5vsPT4oAL1%2BwyhUy9XF906iz78rmrlc7ETj%2BgOcpmXUPIeTqrUV%2F%2FDVwlQvFDXOk1IWkfI99PtXhp8zN8HIY6xrPTIx6cv8d44L3eu64KMl76dCFdD%2F4vzXUG%2Bn2rjl%2FhL3JPz49E6gvYQV5U3AZ%2BDW4zAplWbhyQCuWEXkkTJ5wls2oS3VU8xnZoPAUvbSvmBQi86ai1zAWyZbqnXmXvNgmBnLq2wuyMIViVK0f3XK3nQMOE%2BFPwqp3sOMFx5QlfA%2BCyTfrQ%2F0CAytaNo%2FGSnT1uaI1jvUW3bIZRw%3D%3D~-1~-1~-1; bm_sz=4F952ED5019D8083840F4B20A175E1C8~YAAQGXFAF820i5h9AQAAsd%2BOwQ7jZgdnds67OforfzuIA80NS3G07ODafgBmaOclCTXtcaUQr4Kgw3jhRpk4aAnDyU0D0KkPYyE932vwgwdeKOG41beAftUMh8Ujv4LtjJgrjN%2BUUAzgUduZLm86IxyNZpSjmmOf7XfJ1WqxXCsAmxx0%2Fz3FR7C9Sb4lHuMF%2FuWcLMB6CZ%2BAg9ZcArZCB5xpuyetd8ryDB4mgRtcBIBm5BNGUUqkzBBD3RZaNTBagpx3HS%2FVEaehOj6I1bi%2FvJRugvgVpWfHqdnur5eJdMHYwA%3D%3D~4539447~3687474',
Authority: 'auth.tesla.com',
Pragma: 'no-cache',
'Cache-Control': 'no-cache',
Origin: 'https://auth.tesla.com',
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'curl / 6.14.0',
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'Accept-Encoding': 'deflate',
Referer: 'https://auth.tesla.com/oauth2/v3/authorize?client_id=ownerapi&code_challenge=ZI3Z0vqHuTy130iK3nH6aRznBWM4EbSQSxivqTDhIls&code_challenge_method=S256&redirect_uri=https:%2F%2Fauth.tesla.com%2Fvoid%2Fcallback&response_type=code&scope=openid+email+offline_access&state=123&login_hint=email%40example.com'
}
}
response2 {
responseHeaders: {
server: 'AkamaiGHost',
'mime-version': '1.0',
'content-type': 'text/html',
'content-length': '296',
expires: 'Thu, 16 Dec 2021 04:45:06 GMT',
'x-reference-error': '18.19714017.1639629906.1a83a95f',
date: 'Thu, 16 Dec 2021 04:45:06 GMT',
connection: 'close',
'permissions-policy': 'interest-cohort=()',
'set-cookie': [
'_abck=; expires=Thu, 01 Jan 1971 00:00:00 UTC; path=/; domain=.auth.tesla.com; Secure',
'_abck=; expires=Thu, 01 Jan 1971 00:00:00 UTC; path=/; domain=.auth.tesla.com',
'_abck=; expires=Thu, 01 Jan 1971 00:00:00 UTC; path=/; domain=.tesla.com; Secure',
'_abck=; expires=Thu, 01 Jan 1971 00:00:00 UTC; path=/; domain=.tesla.com'
]
},
responseData: '<HTML><HEAD>\n' +
'<TITLE>Access Denied</TITLE>\n' +
'</HEAD><BODY>\n' +
'<H1>Access Denied</H1>\n' +
' \n' +
`You don't have permission to access "http://auth.tesla.com/oauth2/v3/authorize?" on this server.<P>\n` +
'Reference #18.19714017.1639629906.1a83a95f\n' +
'</BODY>\n' +
'</HTML>\n',
status: 403
}
TLSv1.2
(node:93258) UnhandledPromiseRejectionWarning: Error: Request failed with status code 403 |
Beta Was this translation helpful? Give feedback.
-
The solution I came up with was to do all of the login logic in the browser using import util from 'util';
util.inspect.defaultOptions.depth = null;
import crypto from 'crypto';
import puppeteer from 'puppeteer';
import axios from 'axios';
const TESLA_CLIENT_ID = '81527cff06843c8634fdc09e8ac0abefb46ac849f38fe1e431c2ef2106796384';
const TESLA_CLIENT_SECRET = 'c7257eb71a564034f9419ee651c7d0e5f7aa6bfbd18bafb5c5c033b093bb2fa3';
async function teslaLogin(email: string, password: string): Promise<{ access_token: string, refresh_token: string }> {
// this seems to be how Tesla serializes params
const paramsSerializer = (params: {[key: string]: string}) => {
return Object.keys(params).map(key => {
return `${key}=${encodeURIComponent(params[key]).replace(/%20/g, '+').replace(/%3A/g, ':')}`;
}).join('&');
};
// set up variables for initial login page
const redirect_uri = 'https://auth.tesla.com/void/callback';
const state = '123';
const code_verifier = crypto.randomBytes(64).toString('base64').replace(/[+/=]/g, m => ({'+': '-', '/': '_'}[m] || ''));
const code_challenge = crypto.createHash('sha256')
.update(code_verifier)
.digest('base64')
.replace(/[+/=]/g, m => ({'+': '-', '/': '_'}[m] || ''));
const queryParams = {
client_id: 'ownerapi',
code_challenge,
code_challenge_method: 'S256',
redirect_uri,
response_type: 'code',
scope: 'openid email offline_access',
state,
login_hint: email,
};
const queryString = paramsSerializer(queryParams);
const url = `https://auth.tesla.com/oauth2/v3/authorize?${queryString}`;
// launch a browser (does not work in headless mode and I haven't figured out why yet)
const browser = await puppeteer.launch({ headless: false });
let page: puppeteer.Page;
// navigate to login page with a good user agent and caching disabled
const setUpPage = async () => {
const p = await browser.newPage();
// constantly changing part of the version string seems to help
await p.setUserAgent(`ZornCoTeslaIntegration/1.0.${+new Date()}`);
await p.setCacheEnabled(false);
await p.goto(url, { waitUntil: 'networkidle0' });
await p.waitForTimeout(1000);
// clicking on the page a few times seems to help
await p.click('#main-content');
await p.click('#form-input-credential');
await p.click('#main-content');
return p;
};
page = await setUpPage();
const handleCaptcha = async () => {
const hasCaptcha = await page.evaluate(() => document.querySelector('iframe[title="reCAPTCHA"]') !== null);
if (hasCaptcha) {
const captchaIframe = (await page.waitForSelector('iframe[title="reCAPTCHA"]'))!;
const frame = (await captchaIframe.contentFrame())!;
await frame.evaluate(() => {
(document.querySelector('.recaptcha-checkbox') as HTMLSpanElement).click();
});
let imagePicker: puppeteer.ElementHandle<Element> | null = null;
try {
imagePicker = await page.waitForSelector('iframe[title="recaptcha challenge expires in two minutes"]', { timeout: 1000 });
} catch (e) {
console.log(e);
}
if (imagePicker) {
throw new Error('Cannot automatically solve image picker!');
}
}
};
const submitForm = async () => {
await handleCaptcha();
const navigationPromise = page.waitForNavigation({ waitUntil: 'networkidle0', timeout: 5000 });
await page.type('.sign-in-form input[name="credential"]', password);
await page.click('#form-submit-continue');
await navigationPromise;
};
try {
// fill out password and submit form
await submitForm();
// keep trying until we get a successful redirect
let retryCount = 0;
while (retryCount < 10 && !page.url().startsWith('https://auth.tesla.com/void/callback')) {
console.log('retry');
retryCount++;
if (await page.title() === 'Challenge Validation') {
console.log('Challenge Validation');
// unfortunately, this takes upwards of 30 seconds
try {
await page.waitForNavigation({ waitUntil: 'networkidle0', timeout: 32000 });
} catch (e) {
// reload if no navigation after 32 seconds
await page.reload();
}
await submitForm();
} else if (await page.title() === 'Access Denied') {
console.log('Access Denied');
await page.goto(url, { waitUntil: 'networkidle0' });
await page.close();
page = await setUpPage();
// adding a reload seems to help
await page.reload({ waitUntil: 'networkidle0' });
await submitForm();
} else {
console.log('else');
await submitForm();
}
}
if (!page.url().startsWith('https://auth.tesla.com/void/callback')) {
console.log('failed, start over');
await browser.close();
return teslaLogin(email, password);
}
console.log('success');
} catch (e) {
if ((e as Error).message === 'Cannot automatically solve image picker!') {
console.log('CAPTCHA image picker, starting over');
await browser.close();
return teslaLogin(email, password);
} else {
throw e;
}
}
// grab the latest cookies
const cookies = await page.cookies();
// grab the callback URL for the code
const callbackUrl = page.url();
// close the browser to free up resources
await browser.close();
// set up code and cookie string for next requests
const code = new URL(callbackUrl).searchParams.get('code')!;
const cookieString = cookies.map(cookie => `${cookie.name}=${cookie.value}`).join('; ');
try {
// get initial access token
const accessTokenRes = await axios.post('https://auth.tesla.com/oauth2/v3/token', {
grant_type: 'authorization_code',
client_id: 'ownerapi',
code,
code_verifier,
redirect_uri,
}, {
headers: {
Cookie: cookieString,
},
});
// upgrade access token and get refresh token
const tokenRes = await axios.post('https://owner-api.teslamotors.com/oauth/token', {
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
client_id: TESLA_CLIENT_ID,
client_secret: TESLA_CLIENT_SECRET,
}, {
headers: {
Authorization: `Bearer ${accessTokenRes.data.access_token}`,
Cookie: cookieString,
},
});
// this is what we want!
console.log(tokenRes.data);
return tokenRes.data;
} catch (e) {
if (axios.isAxiosError(e)) {
if (e.response) {
console.log({
status: e.response.status,
responseHeaders: e.response.headers,
responseData: e.response.data,
});
}
} else {
console.error(e);
}
throw e;
}
}
teslaLogin('email@example.com', 'SomePassword123'); |
Beta Was this translation helpful? Give feedback.
-
It's killing me for at least three days!! So I'm copying Postman the CURL precisely as they are in the browser and always getting 403! I don't even understand how it's possible! |
Beta Was this translation helpful? Give feedback.
-
I've tried to log traffic and figure out why this isn't working in headless mode but haven't gotten much further, except for determining where it gets stuck. If I make progress with this, I'll ping in here. I'm trying to figure out how to refresh the token; but seemingly, there's no way of doing that just yet, is that right? I'm wondering how this is done in the apps, are people forced to log in again after 45 days? |
Beta Was this translation helpful? Give feedback.
The solution I came up with was to do all of the login logic in the browser using
puppeteer
: