I need help

One of the defining qualities of a good developer, in my opinion, is to know when to ask for help. And I’ve been battling this issue for way to longer than I should have. But since this is a solo open source project, it is a bit of special circumstance, normally I would have asked a colleague to have a look with me ages ago. Anyhow, if any Java developer with a Tesla wants to help out, that would be highly appreciated!

TeslaTasks has been running for over two years now, and like so many other third party apps it is using the undocumented but reverse engineered Tesla REST API. Every now and then Tesla makes a change to the API and the “community” scrambles to adapt their code so it still works. There are a number of implementations in different languages, all the usual suspects are present like Type/Javascript and Python, and naturally also my implementation in Java.

Every so often a change occurs in the login process, and that is also what happened recently. Tesla has two login processes, one without and one with multi factor authentication (MFA). The login processes’ implementations are not that difficult, for example the MFA process is using OAuth, and basically it is replicating the exact same steps as a webbrowser makes during login.

	Map<String, String> loginHiddenFields = requestLoginPage(authorizeUrl);
	String transactionId = loginHiddenFields.get("transaction_id");
	attemptMFALogin(username, password, authorizeUrl, loginHiddenFields);
	List<String> factorIds = obtainMFAFactorIds(transactionId);
	verifyMFAPasscode(passcode, transactionId, factorIds);
	String authorizationCode = obtainMFAAuthorizationCode(authorizeUrl, transactionId);
	Tokens oauthTokens = obtainMFAOAuthTokens(codeVerifier, authorizationCode);
	Tokens apiTokens = obtainAPITokensUsingMFA(oauthTokens.accessToken);

Just to quickly walk you through it:

  1. Get the login page and extract all hidden fields. There are two sets, but we need the first one.
  2. Get the transaction id from the hidden fields.
  3. Attempt to login with the username and password.
  4. Get which devices (factor ids) are registered to generate the MFA token. Tesla supports a maximum of 2 devices.
  5. Attempt the provided MFA token (passcode) on all of the devices (factor ids).
  6. Get the authorization code that is issued because the login was successful.
  7. Use that code to get an OAuth token.
  8. Use the OAuth token to get an API token. Tesla’s API initially did not support MFA, and from that legacy it still requires a separate API token, instead of the one provided by OAuth.

This process was reversed engineered by simply putting the browser in developer mode and trace the requests being made. And that implementation has worked fine for 2 years, with the occasional tweak.

What at the moment goes wrong is step 4, obtainMFAFactorIds: it hangs a while and then returns a HTTP 500 (server error) without any clue what failed. Seems like a timeout on the server side. But I am not able to figure out where the Java implementation deviates from the browser… That of course is not totally true, it deviates a lot, not only because each login process use different values for e.g. transaction id, but also because not all headers are needed (as the current implementation which used to work shows). The trick is to find the thing that makes it not work. And yes, I’ve tried adding all headers.

Response{protocol=h2, code=500, message=, url=https://auth.tesla.com/oauth2/v1/authorize/mfa/factors?transaction_id=A2ox9gxE} {
  "error": {
    "message": "",
    "code": 500,
    "reason": "Error",
    "referenceID": "5ab028bd09ad4744ac15cd09a00462ed-1631473939669"
  }
}
Exception in thread "main" java.lang.RuntimeException: Request not succesful: Response{protocol=h2, code=500, message=, url=https://auth.tesla.com/oauth2/v1/authorize/mfa/factors?transaction_id=A2ox9gxE}
	at org.tbee.tesla.TeslaMFALogic.failIfNotSuccesful(TeslaMFALogic.java:441)
	at org.tbee.tesla.TeslaMFALogic.obtainMFAFactorIds(TeslaMFALogic.java:255)
	at org.tbee.tesla.TeslaMFALogic.login(TeslaMFALogic.java:113)
	at org.tbee.tesla.TeslaAPI.login(TeslaAPI.java:157)
	at org.tbee.tesla.TestHappyFlow.main(TestHappyFlow.java:52)

One would say that it should be simple to solve, after all the request to be made is right there in the browser, waiting to be copied. And if you think that (as well), great! Show me what I am overlooking, by checkout the TeslaAPI project and make the TestHappyFlow work. 🙂 It’s simply a java class with a main (so not really a tests) that needs your Tesla username and password as command line arguments, and will ask on the console for the passcode (because that changes every time), and then does a login and some simple API calls to the first car registered in the account. Even if only to verify if your Tesla gets the same HTTP 500 (and let me know if yes or no). You can see and trace the code, nothing phishy is happening.

If you crack the problem for me, you can use TeslaTasks for free! Oh, wait, it already is free… Well, then maybe for my eternal gratitude? Helping a fellow developer provide a free service to the Tesla community? Or just to gloat? I don’t mind, all I want to know what is going wrong. Sigh. 😀 You can reach me on info@teslatasks.com or start a discussion on GitHub.

Leave a Reply