pyrh icon indicating copy to clipboard operation
pyrh copied to clipboard

Login Errors with 2FA (email code)

Open BenjaminGuLi opened this issue 4 years ago • 29 comments

Checklist

  • [ x] I am on the latest pyrh version.
  • [ x] I have searched the issues of this repo and believe that this is not a duplicate.

Description

Getting an error when trying to log in following instructions on quickstart, prompts for a 2FA email code and I enter the SMS code which fails to work

Steps/Code to Reproduce

import config

robinhood = Robinhood()

robinhood.login(username= config.username, password= config.password)

quote = robinhood.quote_data("TWTR")

print(quote)

Results

Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/pyrh/robinhood.py", line 215, in login
    res2.raise_for_status()
  File "/usr/local/lib/python3.7/site-packages/requests/models.py", line 941, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://api.robinhood.com/challenge//respond/

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "trade.py", line 6, in <module>
    robinhood.login(username= config.username, password= config.password)
  File "/usr/local/lib/python3.7/site-packages/pyrh/robinhood.py", line 228, in login
    raise RH_exception.LoginFailed()
pyrh.exceptions.LoginFailed

BenjaminGuLi avatar May 27 '20 22:05 BenjaminGuLi

same issue for me. I got a code emailed to me though, but it isn't working still

ppkantorski avatar Jun 06 '20 11:06 ppkantorski

Hello I figured this out; this issue tilted me as well.

You need to:

  1. Turn off SMS, etc. 2FA (when you turn this off, you will get email codes to verify log-ins).
  2. Start running the program code.
  3. Check your email, and copy and paste the email code into the terminal prompt and hit Enter.

7heaxe avatar Jun 07 '20 20:06 7heaxe

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Aug 08 '20 06:08 stale[bot]

@BenjaminGuLi were you able to resolve this?

scubieman avatar Nov 17 '20 14:11 scubieman

Hello I figured this out; this issue tilted me as well.

You need to:

1. Turn off SMS, etc. 2FA (when you turn this off, you will get email codes to verify log-ins).

2. Start running the program code.

3. Check your email, and copy and paste the email code into the terminal prompt and hit Enter.

This instruction is not working as of today Feb-02-2021. I received no email from Robinhood when turned off the SMS, or vise versa.

seantab avatar Feb 16 '21 22:02 seantab

Hi everyone, Running into the same issue. I'm also using SMS for 2FA. However, I noticed that in the traceback, a 404 Not Found HTTP error is raised for the URL: requests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://api.robinhood.com/challenge//respond/. (Notice the two // in the URL. With a single / this resolves correctly.)

Are we sure this isn't the source of this bug?

yanniskatsaros avatar Feb 17 '21 22:02 yanniskatsaros

Ok, I think I figured out the issue! :tada:

In the constructor for Robinhood.login() there is a keyword argument challenge_type. The default is email. Change this to whatever 2FA format you prefer. I'm not sure what all the available options are (not really documented anywhere) but I changed mine to sms and it worked.

Here's an example below.

import os
from pyrh import Robinhood

USERNAME = os.getenv('ROBINHOOD_USERNAME')
PASSWORD = os.getenv('ROBINHOOD_PASSWORD')

rh = Robinhood()
rh.login(username=USERNAME, password=PASSWORD, challenge_type='sms')

You should receive your 2FA code via SMS - enter it and it should return True.

Note I don't have 2FA turned on in my Robinhood app settings.

Hope this helps!

yanniskatsaros avatar Feb 17 '21 22:02 yanniskatsaros

Hi everyone, Running into the same issue. I'm also using SMS for 2FA. However, I noticed that in the traceback, a 404 Not Found HTTP error is raised for the URL: requests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://api.robinhood.com/challenge//respond/. (Notice the two // in the URL. With a single / this resolves correctly.)

Are we sure this isn't the source of this bug?

No, you can dig down and correct it, but it isn't the source of the issue.

downrightmike avatar Mar 03 '21 20:03 downrightmike

Hi everyone, Running into the same issue. I'm also using SMS for 2FA. However, I noticed that in the traceback, a 404 Not Found HTTP error is raised for the URL: requests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://api.robinhood.com/challenge//respond/. (Notice the two // in the URL. With a single / this resolves correctly.) Are we sure this isn't the source of this bug?

No, you can dig down and correct it, but it isn't the source of the issue.

Thanks! Realized that shortly after posting. Please see my answer above if you're still curious about this issue.

TL;DR: make sure to use the challenge_type='sms' kwarg in Robinhood.login(...)

import os
from pyrh import Robinhood

USERNAME = os.getenv('ROBINHOOD_USERNAME')
PASSWORD = os.getenv('ROBINHOOD_PASSWORD')

rh = Robinhood()
rh.login(username=USERNAME, password=PASSWORD, challenge_type='sms')

yanniskatsaros avatar Mar 04 '21 22:03 yanniskatsaros

Yeah you have to disable 2fa on your profile then do a challenge type of sms otherwise it won't work.

davidsalazar avatar Apr 06 '21 17:04 davidsalazar

it is not working for me. I disabled my 2fa and did a challenge tyupe... still not working.

habibabirached avatar Apr 07 '21 03:04 habibabirached

I take that back... it wokrs... the user name had to be the email address...

habibabirached avatar Apr 07 '21 03:04 habibabirached

Make sure your email and password are correct. (since the error wont mention it) Make sure you are using challenge_type='sms' Make sure you are using python3 Make sure pip is not using python 2.7

ajredniwja avatar Sep 02 '21 19:09 ajredniwja

This isn't working for me. I turned off 2FA and set challenge_type to 'sms' any chance that something has been updated that makes this code invalid?

thewac avatar Oct 30 '21 17:10 thewac

I'm using python 3.8

thewac avatar Oct 30 '21 17:10 thewac

I just ran it and it's working for me using JupterLab with python 3.7.3. If it was working before for you then it sounds like bug hunting and tracking down what happened between then and now. Are you getting an error message?

viaConBodhi avatar Oct 30 '21 17:10 viaConBodhi

I'm getting 405 "Method POST not allowed." Also, it seems people are suggesting to disable 2fa then set to sms? I don't see a place to even disable 2fa in RH at this point.

Edit: Looks like the website POSTs to https://api.robinhood.com/oauth2/token/ for sms auth. Is this all just out of date code?

dyburke avatar Nov 30 '21 19:11 dyburke

I've been fighting with this all evening and have figured out how to make it work (at least for me)

The robinhood.login has another variable you can set called qr_code. If you look at the (very brief) documentation for this, it mentions that the qr code is the preferred method now for 2FA. To get it, in Robinhood you have to go to your 2FA settings and change 2FA from SMS to Authenticator App. As far as i can tell, Robinhood won't allow you to disable 2FA or set it to email anymore. But when you set it to Authenticator App, it will give you a QR code to scan with a 3rd party app. Click the link that says "I can't scan it" and instead it will give you a string of letters and numbers. Save that somewhere.

Then in your code, set qr_code to that string. So my code looks like:

rh = Robinhood() un = "my username" pa = "my password" qr = "the code i got from robinhood"

rh.login(username=un, password=pa, qr_code=qr)

It seems to work.

taylorlm88 avatar Jan 10 '22 22:01 taylorlm88

I'm having real problems with this as well. I've tried your suggestion @taylorlm88, however, it does not seem to work or me. Could you perhaps give me a more detailed low? Is the number you get from robinhood the one you enter, or the 6 digit code from the third party authenticator?

alexandrepdumont avatar Jan 20 '22 23:01 alexandrepdumont

I'm having real problems with this as well. I've tried your suggestion @taylorlm88, however, it does not seem to work or me. Could you perhaps give me a more detailed low? Is the number you get from robinhood the one you enter, or the 6 digit code from the third party authenticator?

@alexandrepdumont The number i provide in the code is NOT the 6 digit code i get from the authenticator. I don't have to actually use the third party authenticator to get the code to work. Instead, it is the much longer code you get from Robinhood when you click "I can't scan it" when setting up the 2FA. That way it doesn't change every time you run your code.

rh = Robinhood() rh.login(username="username", password="password", qr_code="code i got from i can't scan it link"). Judging by the code I got, it should be long (something like 15+ characters) and a combination of letters and numbers. I just tested it again and it is still logging in fine for me using this method.

I guess I should note that I did use a third party auth app to test this and log in to my account the normal way before putting it in my code just to make sure the qr code was working. I don't know see how that would make a difference but maybe test to make sure the new 2FA method is working in general before you try to do it in your code.

taylorlm88 avatar Jan 21 '22 13:01 taylorlm88

thank you @taylorlm88 I will try this.

alexandrepdumont avatar Jan 23 '22 13:01 alexandrepdumont

I'm having real problems with this as well. I've tried your suggestion @taylorlm88, however, it does not seem to work or me. Could you perhaps give me a more detailed low? Is the number you get from robinhood the one you enter, or the 6 digit code from the third party authenticator?

@alexandrepdumont The number i provide in the code is NOT the 6 digit code i get from the authenticator. I don't have to actually use the third party authenticator to get the code to work. Instead, it is the much longer code you get from Robinhood when you click "I can't scan it" when setting up the 2FA. That way it doesn't change every time you run your code.

rh = Robinhood() rh.login(username="username", password="password", qr_code="code i got from i can't scan it link"). Judging by the code I got, it should be long (something like 15+ characters) and a combination of letters and numbers. I just tested it again and it is still logging in fine for me using this method.

I guess I should note that I did use a third party auth app to test this and log in to my account the normal way before putting it in my code just to make sure the qr code was working. I don't know see how that would make a difference but maybe test to make sure the new 2FA method is working in general before you try to do it in your code.

I tried this, it does not appear to work...

One note... I logged in to Robin Hood recently using SMS auth, and this is the 4-step request-response OAUTH flow:

  1. I send a login request to https://api.robinhood.com/oauth2/token/:
HTTP POST https://api.robinhood.com/oauth2/token/
{
    "device_token": "x",
    "client_id": "x",
    "expires_in": 86400,
    "grant_type": "password",
    "scope": "internal",
    "username": "[email protected]",
    "password": "xxx",
    "al_token": "x",
    "al_pk": "x"
}
  1. Robin hood responds with:
{"mfa_required":true,"mfa_type":"sms"}

and I get a text message on my cell phone.

  1. I send a second POST to the same URL, https://api.robinhood.com/oauth2/token/, with a single extra item in the JSON body -- mfa_code
HTTP POST https://api.robinhood.com/oauth2/token/
{
    "device_token": "x",
    "client_id": "x",
    "expires_in": 86400,
    "grant_type": "password",
    "scope": "internal",
    "username": "[email protected]",
    "password": "xxx",
    "mfa_code": "xxx",
    "al_token": "x",
    "al_pk": "x"
}
  1. I receive this back from Robin Hood:
{
    "access_token": "xxx-really-long-token",
    "expires_in": 123456,
    "token_type": "Bearer",
    "scope": "internal",
    "refresh_token": "xxx",
    "mfa_code": "xxx",
    "backup_code": null
}

It seems like this project should really be sending 1 or 2 HTTP POST requests to https://api.robinhood.com/oauth2/token/ and stop sending requests to https://api.robinhood.com/challenge/{}/respond/. Just my 2 cents. Maybe someone else has already done this Robin Hood OAUTH2 login code, if so we can just use their method.

meltingscales avatar Apr 17 '22 15:04 meltingscales

EDIT: I'm a dummy :D I got it to work. The latest version of pyrh already uses OAUTH2 with Robin Hood. https://github.com/robinhood-unofficial/pyrh/blob/master/pyrh/urls.py#L47 made me realize that.

Previously, I was logging in after instantiating a Robinhood object...and also using an old version of pyrh, it seems...

Non-working code:

import json
import os.path
from typing import Dict

from pyrh import Robinhood


def get_robinhood_login_json(path='~/.robinhood/login.json') -> Dict:
    with open(os.path.abspath(os.path.expanduser(path))) as fh:
        obj = json.load(fh)
    return obj


if __name__ == '__main__':
    rh = Robinhood()
    creds = get_robinhood_login_json()
    rh.login(username=creds['email'], password=creds['password'], challenge_type='sms')
    del creds
    rh.print_quote("AAPL")

I forked pyrh and I dug into the library a bit, https://github.com/HenryFBP/pyrh/blob/fix-oauth/pyrh/test_login_smsauth.py and realized the constructor took args...I decided to pass them...and it just worked. I am using SMS 2FA.

working code:

import json
import os
from typing import Dict
from pyrh import Robinhood


def get_robinhood_login_json(path='~/.robinhood/login.json') -> Dict:
    with open(os.path.abspath(os.path.expanduser(path))) as fh:
        obj = json.load(fh)
    return obj


if __name__ == '__main__':
    creds = get_robinhood_login_json()
    rh = Robinhood(username=creds['email'], password=creds['password'], challenge_type='sms')
    print("rh.oauth.is_valid={}".format(rh.oauth.is_valid))
    del creds
    rh.print_quote("AAPL")

meltingscales avatar Apr 17 '22 15:04 meltingscales

I just realized...The latest version of pyrh on PyPI seems to be outdated. The constructor I get when installing pyrh==2.0 looks like this:

class Robinhood:
    """Wrapper class for fetching/parsing Robinhood endpoints """

    session = None
    username = None
    password = None
    headers = None
    auth_token = None
    refresh_token = None

but the master branch, which works with the "working code" I put above, looks like this:

class Robinhood(InstrumentManager, SessionManager):
    """Wrapper class for fetching/parsing Robinhood endpoints.

    Please see :py:class:`SessionManager` for login functionality.

    Provides a global convenience wrapper for the following manager objects:

        * InstrumentManager
        * TODO: Add to this list

    """

meltingscales avatar Apr 17 '22 15:04 meltingscales

You sir are a coding genius I'll try this latest solution and get back to you.

On Sun, Apr 17, 2022, 11:37 Henry Post @.***> wrote:

I just realized...The latest version of pyrh on PyPI seems to be outdated. The constructor I get when installing pyrh==2.0 looks like this:

class Robinhood: """Wrapper class for fetching/parsing Robinhood endpoints """

session = None
username = None
password = None
headers = None
auth_token = None
refresh_token = None

but the master branch, which works with the "working code" I put above, looks like this:

class Robinhood(InstrumentManager, SessionManager): """Wrapper class for fetching/parsing Robinhood endpoints. Please see :py:class:SessionManager for login functionality. Provides a global convenience wrapper for the following manager objects: * InstrumentManager * TODO: Add to this list """

— Reply to this email directly, view it on GitHub https://github.com/robinhood-unofficial/pyrh/issues/251#issuecomment-1100902950, or unsubscribe https://github.com/notifications/unsubscribe-auth/APFHTBSV3AAHJ7GXLUTLFQDVFQV3RANCNFSM4NMR2M6A . You are receiving this because you were mentioned.Message ID: @.***>

alexandrepdumont avatar Apr 17 '22 15:04 alexandrepdumont

@alexandrepdumont I am working on releasing the latest build of pyrh to PyPI -- check https://github.com/HenryFBP/robinhoodtest/blob/master/pyproject.toml line 10 for the new version, I am about to upload it

meltingscales avatar Apr 17 '22 16:04 meltingscales

@alexandrepdumont done, check https://github.com/HenryFBP/robinhoodtest. It should work finally...

I hope the maintainers of this package see this thread and decide to finally make a new release, or automate their releases...I may end up doing that myself if this proves useful for stock trading and then just submit a pull request.

Haha, so this stupid idiot (me) spent 1 hour re-releasing an old Python OSS project into PyPI, but didn't know that Robin Hood changed their backend APIs too, and now all the pagination/data structures are different...and has to either find a new library or rewrite their rest api backend

Yes...let's hope not XD

Anyways, Poetry can pull from PyPI (poetry add pyrhhfbp) or using Git URLs. Check the pyproject.toml file for how to do either.

meltingscales avatar Apr 17 '22 17:04 meltingscales

Sorry everyone for leaving y'all hanging! I've been busy lately with other projects. I'll do more research into this

Jamonek avatar Apr 17 '22 17:04 Jamonek

Sorry everyone for leaving y'all hanging! I've been busy lately with other projects. I'll do more research into this

Yo @Jamonek -- The latest PYPI release is just old -- you can re-release it should fix the issues. Thanks so much!! This is an awesome project!

meltingscales avatar Apr 17 '22 17:04 meltingscales

Closed by v2.0.1 release

adithyabsk avatar Nov 26 '22 05:11 adithyabsk