From f9a722bae8cce368b9f8fd447c418e1034c32178 Mon Sep 17 00:00:00 2001 From: Harmon Date: Fri, 7 Jan 2022 18:15:50 -0600 Subject: [PATCH] Update and improve authentication documentation --- docs/auth_tutorial.rst | 148 ------------------- docs/authentication.rst | 301 +++++++++++++++++++++++++++++++++++++++ docs/conf.py | 7 +- docs/getting_started.rst | 3 +- docs/index.rst | 2 +- tweepy/auth.py | 34 ++++- 6 files changed, 340 insertions(+), 155 deletions(-) delete mode 100644 docs/auth_tutorial.rst create mode 100644 docs/authentication.rst diff --git a/docs/auth_tutorial.rst b/docs/auth_tutorial.rst deleted file mode 100644 index 8a22a61..0000000 --- a/docs/auth_tutorial.rst +++ /dev/null @@ -1,148 +0,0 @@ -.. _auth_tutorial: - - -*********************** -Authentication Tutorial -*********************** - -Introduction -============ - -Tweepy supports both OAuth 1a (application-user) and OAuth 2 -(application-only) authentication. Authentication is handled by the -tweepy.AuthHandler class. - -OAuth 1a Authentication -======================= - -Tweepy tries to make OAuth 1a as painless as possible for you. To begin -the process we need to register our client application with -Twitter. Create a new application and once you -are done you should have your consumer key and secret. Keep these -two handy, you'll need them. - -The next step is creating an OAuthHandler instance. Into this we pass -our consumer key and secret which was given to us in the previous -paragraph:: - - auth = tweepy.OAuthHandler(consumer_key, consumer_secret) - -If you have a web application and are using a callback URL that needs -to be supplied dynamically you would pass it in like so:: - - auth = tweepy.OAuthHandler(consumer_key, consumer_secret, - callback_url) - -If the callback URL will not be changing, it is best to just configure -it statically on twitter.com when setting up your application's -profile. - -Unlike basic auth, we must do the OAuth 1a "dance" before we can start -using the API. We must complete the following steps: - -#. Get a request token from twitter - -#. Redirect user to twitter.com to authorize our application - -#. If using a callback, twitter will redirect the user to - us. Otherwise the user must manually supply us with the verifier - code. - -#. Exchange the authorized request token for an access token. - -So let's fetch our request token to begin the dance:: - - try: - redirect_url = auth.get_authorization_url() - except tweepy.TweepError: - print('Error! Failed to get request token.') - -This call requests the token from twitter and returns to us the -authorization URL where the user must be redirect to authorize us. Now -if this is a desktop application we can just hang onto our -OAuthHandler instance until the user returns back. In a web -application we will be using a callback request. So we must store the -request token in the session since we will need it inside the callback -URL request. Here is a pseudo example of storing the request token in -a session:: - - session.set('request_token', auth.request_token['oauth_token']) - -So now we can redirect the user to the URL returned to us earlier from -the get_authorization_url() method. - -If this is a desktop application (or any application not using -callbacks) we must query the user for the "verifier code" that twitter -will supply them after they authorize us. Inside a web application -this verifier value will be supplied in the callback request from -twitter as a GET query parameter in the URL. - -.. code-block :: python - - # Example using callback (web app) - verifier = request.GET.get('oauth_verifier') - - # Example w/o callback (desktop) - verifier = raw_input('Verifier:') - -The final step is exchanging the request token for an access -token. The access token is the "key" for opening the Twitter API -treasure box. To fetch this token we do the following:: - - # Let's say this is a web app, so we need to re-build the auth handler - # first... - auth = tweepy.OAuthHandler(consumer_key, consumer_secret) - token = session.get('request_token') - session.delete('request_token') - auth.request_token = { 'oauth_token' : token, - 'oauth_token_secret' : verifier } - - try: - auth.get_access_token(verifier) - except tweepy.TweepError: - print('Error! Failed to get access token.') - -It is a good idea to save the access token for later use. You do not -need to re-fetch it each time. Twitter currently does not expire the -tokens, so the only time it would ever go invalid is if the user -revokes our application access. To store the access token depends on -your application. Basically you need to store 2 string values: key and -secret:: - - auth.access_token - auth.access_token_secret - -You can throw these into a database, file, or where ever you store -your data. To re-build an OAuthHandler from this stored access token -you would do this:: - - auth = tweepy.OAuthHandler(consumer_key, consumer_secret) - auth.set_access_token(key, secret) - -So now that we have our OAuthHandler equipped with an access token, we -are ready for business:: - - api = tweepy.API(auth) - api.update_status('tweepy + oauth!') - -OAuth 2 Authentication -====================== - -Tweepy also supports OAuth 2 authentication. OAuth 2 is a method of -authentication where an application makes API requests without the -user context. Use this method if you just need read-only access to -public information. - -Like OAuth 1a, we first register our client application and acquire -a consumer key and secret. - -Then we create an AppAuthHandler instance, passing in our consumer -key and secret:: - - auth = tweepy.AppAuthHandler(consumer_key, consumer_secret) - -With the bearer token received, we are now ready for business:: - - api = tweepy.API(auth) - for tweet in tweepy.Cursor(api.search_tweets, q='tweepy').items(10): - print(tweet.text) \ No newline at end of file diff --git a/docs/authentication.rst b/docs/authentication.rst new file mode 100644 index 0000000..383bae1 --- /dev/null +++ b/docs/authentication.rst @@ -0,0 +1,301 @@ +.. _authentication: + +.. currentmodule:: tweepy + +************** +Authentication +************** + +This supplements Twitter's `Authentication documentation`_. + +.. _Authentication documentation: https://developer.twitter.com/en/docs/authentication/overview + +Introduction +============ + +Tweepy supports the OAuth 1.0a User Context, OAuth 2.0 Bearer Token (App-Only), +and OAuth 2.0 Authorization Code Flow with PKCE (User Context) authentication +methods. + +Twitter API v1.1 +================ + +OAuth 2.0 Bearer Token (App-Only) +--------------------------------- +The simplest way to generate a bearer token is through your app's Keys and +Tokens tab under the `Twitter Developer Portal Projects & Apps page`_. + +.. _Twitter Developer Portal Projects & Apps page: https://developer.twitter.com/en/portal/projects-and-apps + +You can then initialize :class:`OAuth2BearerHandler` with the bearer token and +initialize :class:`API` with the :class:`OAuth2BearerHandler` instance:: + + import tweepy + + auth = tweepy.OAuth2BearerHandler("Bearer Token here") + api = tweepy.API(auth) + +Alternatively, you can use the API / Consumer key and secret that can be found +on the same page and initialize :class:`OAuth2AppHandler` instead:: + + import tweepy + + auth = tweepy.OAuth2AppHandler( + "API / Consumer Key here", "API / Consumer Secret here" + ) + api = tweepy.API(auth) + +OAuth 1.0a User Context +----------------------- +Similarly, the simplest way to authenticate as your developer account is to +generate an access token and access token secret through your app's Keys and +Tokens tab under the `Twitter Developer Portal Projects & Apps page`_. + +You'll also need the app's API / consumer key and secret that can be found on +that page. + +You can then initialize :class:`OAuth1UserHandler` with all four credentials +and initialize :class:`API` with the :class:`OAuth1UserHandler` instance:: + + import tweepy + + auth = tweepy.OAuth1UserHandler( + "API / Consumer Key here", "API / Consumer Secret here", + "Access Token here", "Access Token Secret here" + ) + api = tweepy.API(auth) + +To authenticate as a different user, see :ref:`3-legged OAuth`. + +Twitter API v2 +============== + +Tweepy's interface for Twitter API v2, :class:`Client`, handles OAuth 2.0 +Bearer Token (application-only) and OAuth 1.0a User Context authentication for +you. + +OAuth 2.0 Bearer Token (App-Only) +--------------------------------- +The simplest way to generate a bearer token is through your app's Keys and +Tokens tab under the `Twitter Developer Portal Projects & Apps page`_. + +You can then simply pass the bearer token to :class:`Client` when initializing +it:: + + import tweepy + + client = tweepy.Client("Bearer Token here") + +OAuth 1.0a User Context +----------------------- +Similarly, the simplest way to authenticate as your developer account is to +generate an access token and access token secret through your app's Keys and +Tokens tab under the `Twitter Developer Portal Projects & Apps page`_. + +You'll also need the app's API / consumer key and secret that can be found on +that page. + +You can then simply pass all four credentials to :class:`Client` when +initializing it:: + + import tweepy + + client = tweepy.Client( + consumer_key="API / Consumer Key here", + consumer_secret="API / Consumer Secret here", + access_token="Access Token here", + access_token_secret="Access Token Secret here" + ) + +To authenticate as a different user, see :ref:`3-legged OAuth`. + +OAuth 2.0 Authorization Code Flow with PKCE (User Context) +---------------------------------------------------------- +You can generate an access token to authenticate as a user using +:class:`OAuth2UserHandler`. + +You'll need to turn on OAuth 2.0 under the User authentication settings section +of your app's Settings tab under the +`Twitter Developer Portal Projects & Apps page`_. To do this, you'll need to +provide a Callback / Redirect URI / URL. + +Then, you'll need to note the app's Client ID, which you can find through your +app's Keys and Tokens tab under the +`Twitter Developer Portal Projects & Apps page`_. If you're using a +confidential client, you'll also need to generate a Client Secret. + +You can then initialize :class:`OAuth2UserHandler` with the scopes you need:: + + import tweepy + + oauth2_user_handler = tweepy.OAuth2UserHandler( + client_id="Client ID here", + redirect_uri="Callback / Redirect URI / URL here", + scope=["Scope here", "Scope here"], + # Client Secret is only necessary if using a confidential client + client_secret="Client Secret here" + ) + +For a list of scopes, see the Scopes section of Twitter's +`OAuth 2.0 Authorization Code Flow with PKCE documentation`_. + +.. _OAuth 2.0 Authorization Code Flow with PKCE documentation: https://developer.twitter.com/en/docs/authentication/oauth-2-0/authorization-code + +Then, you can get the authorization URL:: + + print(oauth2_user_handler.get_authorization_url()) + +This can be used to have a user authenticate your app. Once they've done so, +they'll be redirected to the Callback / Redirect URI / URL you provided. You'll +need to pass that authorization response URL to fetch the access token:: + + access_token = oauth2_user_handler.fetch_token( + "Authorization Response URL here" + ) + +You can then pass the access token to :class:`Client` when initializing it:: + + client = tweepy.Client("Access Token here") + +3-legged OAuth +============== +This section supplements Twitter's `3-legged OAuth flow documentation`_. + +.. _3-legged OAuth flow documentation: https://developer.twitter.com/en/docs/authentication/oauth-1-0a/obtaining-user-access-tokens + +To authenticate as a user other than your developer account, you'll need to +obtain their access tokens through the 3-legged OAuth flow. + +First, you'll need to turn on OAuth 1.0 under the User authentication settings +section of your app's Settings tab under the +`Twitter Developer Portal Projects & Apps page`_. To do this, you'll need to +provide a Callback / Redirect URI / URL. + +Then, you'll need the app's API / consumer key and secret that can be found +through your app's Keys and Tokens tab under the +`Twitter Developer Portal Projects & Apps page`_. + +You can then initialize an instance of :class:`OAuth1UserHandler`:: + + import tweepy + + oauth1_user_handler = tweepy.OAuth1UserHandler( + "API / Consumer Key here", "API / Consumer Secret here", + callback="Callback / Redirect URI / URL here" + ) + +Then, you can get the authorization URL:: + + print(oauth1_user_handler.get_authorization_url()) + +To use Log in with Twitter / Sign in with Twitter, you can set the +``signin_with_twitter`` parameter when getting the authorization URL:: + + print(oauth1_user_handler.get_authorization_url(signin_with_twitter=True)) + +This can be used to have a user authenticate your app. Once they've done so, +they'll be redirected to the Callback / Redirect URI / URL you provided, with +``oauth_token`` and ``oauth_verifier`` parameters. + +You can then use the verifier to get the access token and secret:: + + access_token, access_token_secret = oauth1_user_handler.fetch_token( + "Verifier (oauth_verifier) here" + ) + +If you need to reinitialize :class:`OAuth1UserHandler`, you can set the request +token and secret afterward, before using the verifier to get the access token +and secret:: + + request_token = oauth1_user_handler.request_token["oauth_token"] + request_secret = oauth1_user_handler.request_token["oauth_token_secret"] + + new_oauth1_user_handler = tweepy.OAuth1UserHandler( + "API / Consumer Key here", "API / Consumer Secret here", + callback="Callback / Redirect URI / URL here" + ) + new_oauth1_user_handler.request_token = { + "oauth_token": "Request Token (oauth_token) here", + "oauth_token_secret": request_secret + } + access_token, access_token_secret = new_oauth1_user_handler.fetch_token( + "Verifier (oauth_verifier) here" + ) + +Otherwise, you can simply use the old instance of :class:`OAuth1UserHandler`. + +You can then use this instance of :class:`OAuth1UserHandler` to initialize +:class:`API`:: + + api = tweepy.API(oauth1_user_handler) + +You can also use the ``access_token`` and ``access_token_secret`` to initialize +a new instance of :class:`OAuth1UserHandler` to initialize :class:`API`:: + + auth = tweepy.OAuth1UserHandler( + "API / Consumer Key here", "API / Consumer Secret here", + "Access Token here", "Access Token Secret here" + ) + api = tweepy.API(auth) + +For initializing :class:`Client`, you can pass ``access_token`` and +``access_token_secret`` directly:: + + client = tweepy.Client( + consumer_key="API / Consumer Key here", + consumer_secret="API / Consumer Secret here", + access_token="Access Token here", + access_token_secret="Access Token Secret here" + ) + +PIN-based OAuth +--------------- +This section supplements Twitter's `PIN-based OAuth documentation`_. + +.. _PIN-based OAuth documentation: https://developer.twitter.com/en/docs/authentication/oauth-1-0a/pin-based-oauth + +The PIN-based OAuth flow can be used by setting the ``callback`` parameter to +``"oob"``:: + + import tweepy + + oauth1_user_handler = tweepy.OAuth1UserHandler( + "API / Consumer Key here", "API / Consumer Secret here", + callback="oob" + ) + +You can then get the authorization URL the same way:: + + print(oauth1_user_handler.get_authorization_url()) + +When the user authenticates with this URL, they'll be provided a PIN. You can +retrieve this PIN from the user to use as the verifier:: + + verifier = input("Input PIN: ") + access_token, access_token_secret = oauth1_user_handler.fetch_token( + verifier + ) + +You can then use the instance of :class:`OAuth1UserHandler` and/or the +``access_token`` and ``access_token_secret``. + +Reference +========= + +.. autoclass:: OAuth1UserHandler + :members: + :member-order: bysource + +.. autoclass:: OAuthHandler + +.. autoclass:: OAuth2AppHandler + +.. autoclass:: AppAuthHandler + +.. autoclass:: OAuth2BearerHandler + :show-inheritance: + +.. autoclass:: OAuth2UserHandler + :members: + :member-order: bysource + :show-inheritance: diff --git a/docs/conf.py b/docs/conf.py index 4cf95bd..4c53424 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -36,12 +36,15 @@ extensions = [ hoverxref_auto_ref = True hoverxref_domains = ['py'] -hoverxref_intersphinx = ['aiohttp', 'requests'] +hoverxref_intersphinx = ['aiohttp', 'requests', 'requests_oauthlib'] intersphinx_mapping = { 'python': ('https://docs.python.org/3', None), 'aiohttp': ('https://docs.aiohttp.org/en/stable/', None), - 'requests': ('https://docs.python-requests.org/en/stable/', None) + 'requests': ('https://docs.python-requests.org/en/stable/', None), + 'requests_oauthlib': ( + 'https://requests-oauthlib.readthedocs.io/en/latest/', None + ) } rst_prolog = """ diff --git a/docs/getting_started.rst b/docs/getting_started.rst index 229f8a4..39e26e8 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -31,7 +31,8 @@ Hello Tweepy This example will download your home timeline tweets and print each one of their texts to the console. Twitter requires all requests to use OAuth for authentication. -The :ref:`auth_tutorial` goes into more details about authentication. +The :ref:`authentication` documentation goes into more details about +authentication. API === diff --git a/docs/index.rst b/docs/index.rst index d778b9b..e4928e9 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -13,7 +13,7 @@ Contents: install.rst getting_started.rst - auth_tutorial.rst + authentication.rst api.rst client.rst models.rst diff --git a/tweepy/auth.py b/tweepy/auth.py index b6c7aa8..27f1bab 100644 --- a/tweepy/auth.py +++ b/tweepy/auth.py @@ -22,6 +22,11 @@ log = logging.getLogger(__name__) class OAuth1UserHandler: + """OAuth 1.0a User Context authentication handler + + .. versionchanged:: 4.5 + Renamed from :class:`OAuthHandler` + """ def __init__(self, consumer_key, consumer_secret, access_token=None, access_token_secret=None, callback=None): @@ -63,7 +68,7 @@ class OAuth1UserHandler: def get_authorization_url(self, signin_with_twitter=False, access_type=None): - """Get the authorization URL to redirect the user""" + """Get the authorization URL to redirect the user to""" try: if signin_with_twitter: url = self._get_oauth_url('authenticate') @@ -79,8 +84,8 @@ class OAuth1UserHandler: raise TweepyException(e) def get_access_token(self, verifier=None): - """After user has authorized the request token, get access token - with user supplied verifier. + """After user has authorized the app, get access token and secret with + verifier """ try: url = self._get_oauth_url('access_token') @@ -98,6 +103,10 @@ class OAuth1UserHandler: raise TweepyException(e) def set_access_token(self, key, secret): + """ + .. deprecated:: 4.5 + Set through initialization instead. + """ self.access_token = key self.access_token_secret = secret @@ -120,6 +129,12 @@ class OAuthHandler(OAuth1UserHandler): class OAuth2AppHandler: + """OAuth 2.0 Bearer Token (App-Only) using API / Consumer key and secret + authentication handler + + .. versionchanged:: 4.5 + Renamed from :class:`AppAuthHandler` + """ def __init__(self, consumer_key, consumer_secret): self.consumer_key = consumer_key @@ -158,6 +173,10 @@ class AppAuthHandler(OAuth2AppHandler): class OAuth2BearerHandler(AuthBase): + """OAuth 2.0 Bearer Token (App-Only) authentication handler + + .. versionadded:: 4.5 + """ def __init__(self, bearer_token): self.bearer_token = bearer_token @@ -171,6 +190,11 @@ class OAuth2BearerHandler(AuthBase): class OAuth2UserHandler(OAuth2Session): + """OAuth 2.0 Authorization Code Flow with PKCE (User Context) + authentication handler + + .. versionadded:: 4.5 + """ def __init__(self, *, client_id, redirect_uri, scope, client_secret=None): super().__init__(client_id, redirect_uri=redirect_uri, scope=scope) @@ -180,6 +204,7 @@ class OAuth2UserHandler(OAuth2Session): self.auth = None def get_authorization_url(self): + """Get the authorization URL to redirect the user to""" self.code_verifier = secrets.token_urlsafe(128)[:128] code_challenge = urlsafe_b64encode( sha256(self.code_verifier.encode("ASCII")).digest() @@ -191,6 +216,9 @@ class OAuth2UserHandler(OAuth2Session): return authorization_url def fetch_token(self, authorization_response): + """After user has authorized the app, fetch access token with + authorization response URL + """ return super().fetch_token( "https://api.twitter.com/2/oauth2/token", authorization_response=authorization_response, -- 2.25.1