Skip to content

Instantly share code, notes, and snippets.

@1oh1
Last active June 20, 2021 05:36
Show Gist options
  • Save 1oh1/3b148a1f91cbf6f906866be27962bd2a to your computer and use it in GitHub Desktop.
Save 1oh1/3b148a1f91cbf6f906866be27962bd2a to your computer and use it in GitHub Desktop.
Using PRAW with 2FA

Using PRAW with 2FA

If like me, you got a prawcore.exceptions.OAuthException: invalid_grant error processing request error out of the blue for your reddit bot that had been running without an issue for years on AWS Lambda, then maybe like me you also enabled 2FA on your Reddit account recently and failed to realize that the change would affect your reddit bot too since it authenticates to Reddit using your credentials.

To the best of my knowledge, reddit doesn't have 'App Passwords' like Google does so you'll have to find a way to supply the OTP code every time you authenticate to Reddit using PRAW.

Thanks to this pull request, prawcore can now accept 2FA codes for ScriptAuthorizer via a callback function.

To get the OTP code, you can use pyotp. The following sample code is from the pyotp project page:

totp = pyotp.TOTP('base32secret3232')
totp.now() # => '492039'

# OTP verified for current time
totp.verify('492039') # => True
time.sleep(30)
totp.verify('492039') # => False

To get the TOTP secret if you don't have it, you could disable 2FA and enable it again to get it or if you use Authy as your 2FA Authenticator app, you can refer to this gist about exporting TOTP tokens from Authy Desktop app.

Once you have everything you need, refer to the sample code below:

Old code (No 2FA - doesn't work):

import praw

reddit = praw.Reddit(
	client_id=PRAW_CLIENT_ID,
	client_secret=PRAW_SECRET, password=REDDIT_PASSWORD,
	user_agent=REDDIT_USERAGENT, username=REDDIT_USERNAME
)

print(list(reddit.user.blocked())) # fails

Old code modifed to supply OTP code along with REDDIT_PASSWORD:

import praw, prawcore, pyotp

totp = pyotp.TOTP('REDDIT_TOTP_SECRET')
reddit = praw.Reddit(
	client_id=PRAW_CLIENT_ID,
	client_secret=PRAW_SECRET, password="{}:{}".format(REDDIT_PASSWORD, totp.now()),
	user_agent=REDDIT_USERAGENT, username=REDDIT_USERNAME
)

print(list(reddit.user.blocked())) # works

New code with 2FA callback function:

import praw, prawcore, pyotp

totp = pyotp.TOTP('REDDIT_TOTP_SECRET')
def create_prawcore_two_factor_script_authorizer(two_factor_callback=None):
    authenticator = prawcore.TrustedAuthenticator(
	    prawcore.Requestor(REDDIT_USERAGENT),
	    PRAW_CLIENT_ID,
	    PRAW_SECRET,
	)
    authorizer = prawcore.ScriptAuthorizer(
        authenticator, REDDIT_USERNAME, REDDIT_PASSWORD, two_factor_callback
    )
    authorizer.refresh()
    return authorizer
    
authorizer_2fa = create_prawcore_two_factor_script_authorizer(totp.now)
reddit = praw.Reddit(
	client_id='fakeclient',
	user_agent='fakeagent',
	client_secret='fakesecret'
)
reddit._core._authorizer = authorizer_2fa

print(list(reddit.user.blocked())) # works

With both versions, you have to handle prawcore.exceptions.OAuthException which will be raised for API calls made after an hour has passed, as mentioned in the PRAW docs

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