Skip to content

Fix the ever-changing device_token - #300

Merged
jmfernandes merged 1 commit into
jmfernandes:masterfrom
bfoz:patch-1
Oct 20, 2021
Merged

Fix the ever-changing device_token#300
jmfernandes merged 1 commit into
jmfernandes:masterfrom
bfoz:patch-1

Conversation

@bfoz

@bfoz bfoz commented Jul 16, 2021

Copy link
Copy Markdown
Contributor

While working on a small script for fetching my RH order history I noticed that I kept getting "ERROR: There was an issue with the pickle file..." and would have to do the MFA dance yet again. Eventually that annoyed me enough to look into the problem. At first I tried deleting the pickle file and starting over, but that didn't help. Then I tried looking at the list of authenticated devices in my RH account and noticed 41 new devices! That seemed strange, especially given (I assumed) that the pickle file was saving some sort of authentication token. But clearly RH thought I was using a new device every time. Very strange...

I added a bit of print() debugging and discovered that the device_token being sent to RH was different every time, and wasn't what was saved in the pickle file.

% ./fetch_robinhood.py 
pickle_path='/Users/bfoz/.tokens/robinhood.pickle'
pickle_data={'token_type': 'Bearer', 'access_token': '', 'refresh_token': '', 'device_token': '427bac7c-a31f-bb0c-0d8b-48880a1b1065'}
payload['device_token']='427bac7c-a31f-bb0c-0d8b-48880a1b1065'
ERROR: There was an issue loading pickle file. Authentication may be expired - logging in normally.
Enter Robinhood code for validation: 
Found Additional pages.
Loading page 2 ...
Loading page 3 ...


% ./fetch_robinhood.py                                                                    
pickle_path='/Users/bfoz/.tokens/robinhood.pickle'
pickle_data={'token_type': 'Bearer', 'access_token': '', 'refresh_token': '', 'device_token': '796191eb-3d55-422a-f658-637960239022'}
payload['device_token']='796191eb-3d55-422a-f658-637960239022'
ERROR: There was an issue loading pickle file. Authentication may be expired - logging in normally.
Enter Robinhood code for validation: 
Found Additional pages.
Loading page 2 ...
Loading page 3 ...

It turns out that if a login fails for any reason, and then succeeds after the user enters the SMS code, the device token that's stored in the pickle file is not the one that was sent to RH in the successful login payload (or the unsuccessful one, for that matter). This appears to be due to the call to pickle.dump() saving the device_token that was generated when payload was first initialized (line 94) rather than the one from the pickle file. Loading the pickle file overwrites the value in payload (which is what is sent to RH) but the call to pickle.dump() stores device_token rather than payload['device_token'], which ends up being the original device token, not the one loaded from the pickle file.

All subsequent login attempts will fail because the "wrong" device token that was written to the pickle file will be loaded and sent to RH, which rightly rejects the login attempt. The user then enters the code which causes login to succeed with the saved device token, but once again the "wrong" token is then written to the pickle file, which causes the next login attempt to also fail...and around we go.

The submitted patch may not be the most elegant way to solve this, but it is simple.

While working on a small script for fetching my RH order history I noticed that I kept getting "ERROR: There was an issue with the pickle file..." and would have to do the MFA dance yet again. Eventually that annoyed me enough to look into the problem. At first I tried deleting the pickle file and starting over, but that didn't help. Then I tried looking at the list of authenticated devices in my RH account and noticed 41 new devices! That seemed strange, especially given (I assumed) that the pickle file was saving some sort of authentication token. But clearly RH thought I was using a new device every time. Very strange...

I added a bit of `print()` debugging and discovered that the `device_token` being sent to RH was different every time, and wasn't what was saved in the pickle file. 

```
% ./fetch_robinhood.py 
pickle_path='/Users/bfoz/.tokens/robinhood.pickle'
pickle_data={'token_type': 'Bearer', 'access_token': '', 'refresh_token': '', 'device_token': '427bac7c-a31f-bb0c-0d8b-48880a1b1065'}
payload['device_token']='427bac7c-a31f-bb0c-0d8b-48880a1b1065'
ERROR: There was an issue loading pickle file. Authentication may be expired - logging in normally.
Enter Robinhood code for validation: 
Found Additional pages.
Loading page 2 ...
Loading page 3 ...


% ./fetch_robinhood.py                                                                    
pickle_path='/Users/bfoz/.tokens/robinhood.pickle'
pickle_data={'token_type': 'Bearer', 'access_token': '', 'refresh_token': '', 'device_token': '796191eb-3d55-422a-f658-637960239022'}
payload['device_token']='796191eb-3d55-422a-f658-637960239022'
ERROR: There was an issue loading pickle file. Authentication may be expired - logging in normally.
Enter Robinhood code for validation: 
Found Additional pages.
Loading page 2 ...
Loading page 3 ...
```

It turns out that if a login fails for any reason, and then succeeds after the user enters the SMS code, the device token that's stored in the pickle file is not the one that was sent to RH in the successful login payload (or the unsuccessful one, for that matter). This appears to be due to the call to `pickle.dump()` saving the `device_token` that was generated when `payload` was first initialized (line 94) rather than the one from the pickle file. Loading the pickle file overwrites the value in `payload` (which is what is sent to RH) but the call to `pickle.dump()` stores `device_token` rather than `payload['device_token']`, which ends up being the original device token, not the one loaded from the pickle file.

All subsequent API calls will fail because the "wrong" device token that was written to the pickle file is loaded and sent to RH, which rightly rejects the login attempt. The user then enters the code which causes login to succeed with the saved device token, but once again the "wrong" token is then written to the pickle file, which causes the next login attempt to also fail...and around we go.

The submitted patch may not be the most elegant way to solve this, but it is simple.
@nickdela

Copy link
Copy Markdown

I tried installing this branch but I am still prompted for a MFA with every login attempt

@kimardenmiller

Copy link
Copy Markdown

While it appears this fix prevents the repeated creation of devices on Robinhood, it seems the request_get line here still returns a 401 error and subsequently the ERROR: There was an issue loading pickle file message:

PR #313's approach I believe fixes both, though I would personally write lines 128-128 as:

                    res = request_get(portfolio_profile_url(), 'regular', jsonify_data=False)

Given that PR #313's approach works, does that suggest something changed on Robinhood's API which enables it to detect your session without any payload data being sent back to Robinhood?

@jmfernandes
jmfernandes merged commit 8dd6233 into jmfernandes:master Oct 20, 2021
@kimardenmiller

Copy link
Copy Markdown

I'm surprised to see this merge. It seems to me that reusing the payload['device_token'] only cosmetically prevents additional Robinhood devices by reusing the device token last used. But this approach does not seem to correct the systemic problem of having to re-login every session, only mask it.

As mentioned, it seems to me that the #313 approach fixes the systemic issue and is the approach I have been using with success for several months now. In several months of testing this approach seems to both:

  • Correctly pass authentication while the last device is still valid, but then

  • Correctly re-login when the device is no longer valid (about once a month for me).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants