From bb853cca70848440979318207999f3635ca81eb0 Mon Sep 17 00:00:00 2001 From: Ib Lundgren Date: Wed, 22 May 2013 14:27:34 +0100 Subject: [PATCH 01/10] Move bulk of README to docs and prepare RTD links. --- README.rst | 186 ++++++--------------------------------- docs/oauth1_workflow.rst | 166 ++++++++++++++++++++++++++++++++++ 2 files changed, 195 insertions(+), 157 deletions(-) create mode 100644 docs/oauth1_workflow.rst diff --git a/README.rst b/README.rst index 60ce8735..0570235f 100644 --- a/README.rst +++ b/README.rst @@ -3,176 +3,49 @@ Requests-OAuthlib This project provides first-class OAuth library support for `Requests `_. -The OAuth workflow ------------------- +The OAuth 1 workflow +-------------------- -OAuth can seem overly complicated and it sure has its quirks. Luckily, +OAuth 1 can seem overly complicated and it sure has its quirks. Luckily, requests_oauthlib hides most of these and let you focus at the task at hand. -You will be forced to go through a few steps when you are using OAuth. Below is an -example of the most common OAuth workflow using HMAC-SHA1 signed requests where -the signature is supplied in the Authorization header. - -The example assumes an interactive prompt which is good for demonstration but in -practice you will likely be using a web application (which makes authorizing much -less awkward since you can simply redirect). - -0. Manual client signup with the OAuth provider (i.e. Google, Twitter) to get - a set of client credentials. Usually a client key and secret. Client might sometimes - be referred to as consumer. For example: - -.. code-block:: pycon - - >>> from __future__ import unicode_literals - >>> import requests - >>> from requests_oauthlib import OAuth1 - - >>> client_key = '...' - >>> client_secret = '...' - -1. Obtain a request token which will identify you (the client) in the next step. - At this stage you will only need your client key and secret. - -.. code-block:: pycon - - >>> oauth = OAuth1(client_key, client_secret=client_secret) - >>> request_token_url = 'https://api.twitter.com/oauth/request_token' - >>> r = requests.post(url=request_token_url, auth=oauth) - >>> r.content - "oauth_token=Z6eEdO8MOmk394WozF5oKyuAv855l4Mlqo7hhlSLik&oauth_token_secret=Kd75W4OQfb2oJTV0vzGzeXftVAwgMnEK9MumzYcM" - >>> from urlparse import parse_qs - >>> credentials = parse_qs(r.content) - >>> resource_owner_key = credentials.get('oauth_token')[0] - >>> resource_owner_secret = credentials.get('oauth_token_secret')[0] - -2. Obtain authorization from the user (resource owner) to access their protected - resources (images, tweets, etc.). This is commonly done by redirecting the - user to a specific url to which you add the request token as a query parameter. - Note that not all services will give you a verifier even if they should. Also - the oauth_token given here will be the same as the one in the previous step. - -.. code-block:: pycon - - >>> authorize_url = 'https://api.twitter.com/oauth/authorize?oauth_token=' - >>> authorize_url = authorize_url + resource_owner_key - >>> print 'Please go here and authorize,', authorize_url - >>> verifier = raw_input('Please input the verifier') - -3. Obtain an access token from the OAuth provider. Save this token as it can be - re-used later. In this step we will re-use most of the credentials obtained - uptil this point. - -.. code-block:: pycon - - >>> oauth = OAuth1(client_key, - client_secret=client_secret, - resource_owner_key=resource_owner_key, - resource_owner_secret=resource_owner_secret, - verifier=verifier) - >>> access_token_url = 'https://api.twitter.com/oauth/access_token' - >>> r = requests.post(url=access_token_url, auth=oauth) - >>> r.content - "oauth_token=6253282-eWudHldSbIaelX7swmsiHImEL4KinwaGloHANdrY&oauth_token_secret=2EEfA6BG3ly3sR3RjE0IBSnlQu4ZrUzPiYKmrkVU" - >>> credentials = parse_qs(r.content) - >>> resource_owner_key = credentials.get('oauth_token')[0] - >>> resource_owner_secret = credentials.get('oauth_token_secret')[0] - -4. Access protected resources. OAuth1 access tokens typically do not expire - and may be re-used until revoked by the user or yourself. +Accessing protected resources using requests_oauthlib is as simple as: .. code-block:: pycon - >>> oauth = OAuth1(client_key, - client_secret=client_secret, - resource_owner_key=resource_owner_key, - resource_owner_secret=resource_owner_secret) + >>> from requests_oauthlib import OAuth1Session + >>> twitter = OAuth1Session('client_key', + client_secret='client_secret', + resource_owner_key='resource_owner_key', + resource_owner_secret='resource_owner_secret') >>> url = 'https://api.twitter.com/1/account/settings.json' - >>> r = requests.get(url=url, auth=oauth) - >>> # Enjoy =) - - -Signature placement - header, query or body? --------------------------------------------- - -OAuth takes many forms, so let's take a look at a few different forms: - -.. code-block:: python - - import requests - from requests_oauthlib import OAuth1 + >>> r = twitter.get(url) - url = u'https://api.twitter.com/1/account/settings.json' +Before accessing resources you will need to obtain a few credentials from your +provider (i.e. Twitter) and authorization from the user for whom you wish to +retrieve resources for. You can read all about this in the full +`OAuth 1 workflow guide on RTD `_. - client_key = u'...' - client_secret = u'...' - resource_owner_key = u'...' - resource_owner_secret = u'...' +The OAuth 2 workflow +-------------------- +OAuth 2 is generally simpler than OAuth 1 but comes in more flavours. The most +common being the Authorization Code Grant, also known as the WebApplication +flow. -Header signing (recommended): +Fetching a protected resource after obtaining an access token can be as simple as: -.. code-block:: python - - headeroauth = OAuth1(client_key, client_secret, - resource_owner_key, resource_owner_secret, - signature_type='auth_header') - r = requests.get(url, auth=headeroauth) - - - -Query signing: - -.. code-block:: python - - queryoauth = OAuth1(client_key, client_secret, - resource_owner_key, resource_owner_secret, - signature_type='query') - r = requests.get(url, auth=queryoauth) - - -Body signing: - -.. code-block:: python - - bodyoauth = OAuth1(client_key, client_secret, - resource_owner_key, resource_owner_secret, - signature_type='body') - - r = requests.post(url, auth=bodyoauth) - - -Signature types - HMAC (most common), RSA, Plaintext ----------------------------------------------------- - -OAuth1 defaults to using HMAC and examples can be found in the previous -sections. - -Plaintext work on the same credentials as HMAC and the only change you will -need to make when using it is to add signature_type='PLAINTEXT' -to the OAuth1 constructor: - -.. code-block:: python - - headeroauth = OAuth1(client_key, client_secret, - resource_owner_key, resource_owner_secret, - signature_method='PLAINTEXT') - -RSA is different in that it does not use client_secret nor -resource_owner_secret. Instead it uses public and private keys. The public key -is provided to the OAuth provider during client registration. The private key -is used to sign requests. The previous section can be summarized as: - -.. code-block:: python - - key = open("your_rsa_key.pem").read() +.. code-block:: pycon - queryoauth = OAuth1(client_key, signature_method=SIGNATURE_RSA, - rsa_key=key, signature_type='query') - headeroauth = OAuth1(client_key, signature_method=SIGNATURE_RSA, - rsa_key=key, signature_type='auth_header') - bodyoauth = OAuth1(client_key, signature_method=SIGNATURE_RSA, - rsa_key=key, signature_type='body') + >>> from requests_oauthlib import OAuth2Session + >>> google = OAuth2Session('client_id', token='token') + >>> url = 'https://www.googleapis.com/oauth2/v1/userinfo' + >>> r = google.get(url) +Before accessing resources you will need to obtain a few credentials from your +provider (i.e. Twitter) and authorization from the user for whom you wish to +retrieve resources for. You can read all about this in the full +`OAuth 2 workflow guide on RTD `_. Installation ------------- @@ -182,4 +55,3 @@ To install requests and requests_oauthlib you can use pip: .. code-block:: bash $ pip install requests requests_oauthlib - diff --git a/docs/oauth1_workflow.rst b/docs/oauth1_workflow.rst new file mode 100644 index 00000000..4e63d050 --- /dev/null +++ b/docs/oauth1_workflow.rst @@ -0,0 +1,166 @@ +OAuth 1 Workflow +================ + +You will be forced to go through a few steps when you are using OAuth. Below is an +example of the most common OAuth workflow using HMAC-SHA1 signed requests where +the signature is supplied in the Authorization header. + +The example assumes an interactive prompt which is good for demonstration but in +practice you will likely be using a web application (which makes authorizing much +less awkward since you can simply redirect). + +0. Manual client signup with the OAuth provider (i.e. Google, Twitter) to get + a set of client credentials. Usually a client key and secret. Client might sometimes + be referred to as consumer. For example: + +.. code-block:: pycon + + >>> from __future__ import unicode_literals + >>> import requests + >>> from requests_oauthlib import OAuth1 + + >>> client_key = '...' + >>> client_secret = '...' + +1. Obtain a request token which will identify you (the client) in the next step. + At this stage you will only need your client key and secret. + +.. code-block:: pycon + + >>> oauth = OAuth1(client_key, client_secret=client_secret) + >>> request_token_url = 'https://api.twitter.com/oauth/request_token' + >>> r = requests.post(url=request_token_url, auth=oauth) + >>> r.content + "oauth_token=Z6eEdO8MOmk394WozF5oKyuAv855l4Mlqo7hhlSLik&oauth_token_secret=Kd75W4OQfb2oJTV0vzGzeXftVAwgMnEK9MumzYcM" + >>> from urlparse import parse_qs + >>> credentials = parse_qs(r.content) + >>> resource_owner_key = credentials.get('oauth_token')[0] + >>> resource_owner_secret = credentials.get('oauth_token_secret')[0] + +2. Obtain authorization from the user (resource owner) to access their protected + resources (images, tweets, etc.). This is commonly done by redirecting the + user to a specific url to which you add the request token as a query parameter. + Note that not all services will give you a verifier even if they should. Also + the oauth_token given here will be the same as the one in the previous step. + +.. code-block:: pycon + + >>> authorize_url = 'https://api.twitter.com/oauth/authorize?oauth_token=' + >>> authorize_url = authorize_url + resource_owner_key + >>> print 'Please go here and authorize,', authorize_url + >>> verifier = raw_input('Please input the verifier') + +3. Obtain an access token from the OAuth provider. Save this token as it can be + re-used later. In this step we will re-use most of the credentials obtained + uptil this point. + +.. code-block:: pycon + + >>> oauth = OAuth1(client_key, + client_secret=client_secret, + resource_owner_key=resource_owner_key, + resource_owner_secret=resource_owner_secret, + verifier=verifier) + >>> access_token_url = 'https://api.twitter.com/oauth/access_token' + >>> r = requests.post(url=access_token_url, auth=oauth) + >>> r.content + "oauth_token=6253282-eWudHldSbIaelX7swmsiHImEL4KinwaGloHANdrY&oauth_token_secret=2EEfA6BG3ly3sR3RjE0IBSnlQu4ZrUzPiYKmrkVU" + >>> credentials = parse_qs(r.content) + >>> resource_owner_key = credentials.get('oauth_token')[0] + >>> resource_owner_secret = credentials.get('oauth_token_secret')[0] + +4. Access protected resources. OAuth1 access tokens typically do not expire + and may be re-used until revoked by the user or yourself. + +.. code-block:: pycon + + >>> oauth = OAuth1(client_key, + client_secret=client_secret, + resource_owner_key=resource_owner_key, + resource_owner_secret=resource_owner_secret) + >>> url = 'https://api.twitter.com/1/account/settings.json' + >>> r = requests.get(url=url, auth=oauth) + >>> # Enjoy =) + + +Signature placement - header, query or body? +-------------------------------------------- + +OAuth takes many forms, so let's take a look at a few different forms: + +.. code-block:: python + + import requests + from requests_oauthlib import OAuth1 + + url = u'https://api.twitter.com/1/account/settings.json' + + client_key = u'...' + client_secret = u'...' + resource_owner_key = u'...' + resource_owner_secret = u'...' + + +Header signing (recommended): + +.. code-block:: python + + headeroauth = OAuth1(client_key, client_secret, + resource_owner_key, resource_owner_secret, + signature_type='auth_header') + r = requests.get(url, auth=headeroauth) + + + +Query signing: + +.. code-block:: python + + queryoauth = OAuth1(client_key, client_secret, + resource_owner_key, resource_owner_secret, + signature_type='query') + r = requests.get(url, auth=queryoauth) + + +Body signing: + +.. code-block:: python + + bodyoauth = OAuth1(client_key, client_secret, + resource_owner_key, resource_owner_secret, + signature_type='body') + + r = requests.post(url, auth=bodyoauth) + + +Signature types - HMAC (most common), RSA, Plaintext +---------------------------------------------------- + +OAuth1 defaults to using HMAC and examples can be found in the previous +sections. + +Plaintext work on the same credentials as HMAC and the only change you will +need to make when using it is to add signature_type='PLAINTEXT' +to the OAuth1 constructor: + +.. code-block:: python + + headeroauth = OAuth1(client_key, client_secret, + resource_owner_key, resource_owner_secret, + signature_method='PLAINTEXT') + +RSA is different in that it does not use client_secret nor +resource_owner_secret. Instead it uses public and private keys. The public key +is provided to the OAuth provider during client registration. The private key +is used to sign requests. The previous section can be summarized as: + +.. code-block:: python + + key = open("your_rsa_key.pem").read() + + queryoauth = OAuth1(client_key, signature_method=SIGNATURE_RSA, + rsa_key=key, signature_type='query') + headeroauth = OAuth1(client_key, signature_method=SIGNATURE_RSA, + rsa_key=key, signature_type='auth_header') + bodyoauth = OAuth1(client_key, signature_method=SIGNATURE_RSA, + rsa_key=key, signature_type='body') From b46deb551fa05261636a457d33769fe2b5552a32 Mon Sep 17 00:00:00 2001 From: Ib Lundgren Date: Wed, 22 May 2013 16:57:08 +0100 Subject: [PATCH 02/10] OAuth2 WebApp workflow. --- docs/oauth2_workflow.rst | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 docs/oauth2_workflow.rst diff --git a/docs/oauth2_workflow.rst b/docs/oauth2_workflow.rst new file mode 100644 index 00000000..3af92261 --- /dev/null +++ b/docs/oauth2_workflow.rst @@ -0,0 +1,5 @@ +OAuth 2 Workflow +================ + +TODO: demonstrate all steps of a oauth 2 webpplication workflow +TODO: outline the various available flows From 0679b687f18470aee653977ae4b88e050a1ffda6 Mon Sep 17 00:00:00 2001 From: Ib Lundgren Date: Wed, 22 May 2013 17:13:24 +0100 Subject: [PATCH 03/10] OAuth2 workflow for real + placeholder for more to come. --- docs/oauth2_workflow.rst | 66 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/docs/oauth2_workflow.rst b/docs/oauth2_workflow.rst index 3af92261..957ea188 100644 --- a/docs/oauth2_workflow.rst +++ b/docs/oauth2_workflow.rst @@ -1,5 +1,67 @@ OAuth 2 Workflow ================ -TODO: demonstrate all steps of a oauth 2 webpplication workflow -TODO: outline the various available flows +0. Obtaining credentials from your OAuth provider manually. You will need + at minimum a ``client_id`` but likely also a ``client_secret``. During + this process you might also be required to register a default redirect + URI to be used by your application: + +.. code-block:: pycon + + >>> client_id = 'your_client_id' + >>> client_secret = 'your_client_secret' + >>> redirect_uri = 'https://your.callback/uri' + +1. User authorization through redirection. First we will create an + authorization url from the base URL given by the provider and + the credentials previously obtained. In addition most providers will + request that you ask for access to a certain scope, in this example + we will ask Google for access to the email address of the user and the + users profile. + +.. code-block:: pycon + + # Note that these are Google specific scopes + >>> scope = ['https://www.googleapis.com/auth/userinfo.email', + 'https://www.googleapis.com/auth/userinfo.profile'] + >>> oauth = OAuth2Session(client_id, redirect_uri=redirect_uri, + scope=scope) + >>> authorization_url, state = oauth.authorization_url( + 'https://accounts.google.com/o/oauth2/auth', + # access_type and approval_prompt are Google specific extra + # parameters. + access_type="offline", approval_prompt="force") + + >>> print 'Please go to %s and authorize access.' % authorization_url + >>> authorization_response = raw_input('Enter the full callback URL') + +2. Fetch an access token from the provider using the authorization code + obtained during user authorization: + +.. code-block:: pycon + + >>> token = oauth.fetch_token( + 'https://accounts.google.com/o/oauth2/token', + authorization_response=authorization_response, + # Google specific extra parameter used for client + # authentication + client_secret=secret) + +3. Access protected resources using the access token you just obtained. + For example, get the users profile info. + +.. code-block:: pycon + + >>> r = oauth.get('https://www.googleapis.com/oauth2/v1/userinfo') + >>> # Enjoy =) + + +Available workflows +------------------- + +TODO: outline implicit, password and client credentials flows. + +Refreshing tokens +----------------- + +TODO: demonstrate how to refresh tokens. From f81f6f4526da3c6c671d181dc765399b0f70eaf0 Mon Sep 17 00:00:00 2001 From: Ib Lundgren Date: Wed, 22 May 2013 17:13:44 +0100 Subject: [PATCH 04/10] Update OAuth1 guide to show OAuth1Session. --- docs/oauth1_workflow.rst | 69 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 7 deletions(-) diff --git a/docs/oauth1_workflow.rst b/docs/oauth1_workflow.rst index 4e63d050..5f102e42 100644 --- a/docs/oauth1_workflow.rst +++ b/docs/oauth1_workflow.rst @@ -9,13 +9,23 @@ The example assumes an interactive prompt which is good for demonstration but in practice you will likely be using a web application (which makes authorizing much less awkward since you can simply redirect). +The guide will show two ways of carrying out the OAuth1 workflow. One using the +authentication helper OAuth1 and the alternative using OAuth1Session. The latter +is usually more convenient and requires less code. + +Workflow example showing use of both OAuth1 and OAuth1Session +------------------------------------------------------------- + 0. Manual client signup with the OAuth provider (i.e. Google, Twitter) to get a set of client credentials. Usually a client key and secret. Client might sometimes be referred to as consumer. For example: .. code-block:: pycon - >>> from __future__ import unicode_literals + >>> # Using OAuth1Session + >>> from requests_oauthlib import OAuth1Session + + >>> # Using OAuth1 auth helper >>> import requests >>> from requests_oauthlib import OAuth1 @@ -27,8 +37,18 @@ less awkward since you can simply redirect). .. code-block:: pycon - >>> oauth = OAuth1(client_key, client_secret=client_secret) >>> request_token_url = 'https://api.twitter.com/oauth/request_token' + + >>> # Using OAuth1Session + >>> oauth = OAuth1Session(client_key, client_secret=client_secret) + >>> oauth.fetch_request_token(request_token_url) + { + "oauth_token": "Z6eEdO8MOmk394WozF5oKyuAv855l4Mlqo7hhlSLik", + "oauth_token_secret": "Kd75W4OQfb2oJTV0vzGzeXftVAwgMnEK9MumzYcM" + } + + >>> # Using OAuth1 auth helper + >>> oauth = OAuth1(client_key, client_secret=client_secret) >>> r = requests.post(url=request_token_url, auth=oauth) >>> r.content "oauth_token=Z6eEdO8MOmk394WozF5oKyuAv855l4Mlqo7hhlSLik&oauth_token_secret=Kd75W4OQfb2oJTV0vzGzeXftVAwgMnEK9MumzYcM" @@ -45,7 +65,20 @@ less awkward since you can simply redirect). .. code-block:: pycon - >>> authorize_url = 'https://api.twitter.com/oauth/authorize?oauth_token=' + >>> base_authorization_url = 'https://api.twitter.com/oauth/authorize' + + >>> # Using OAuth1Session + >>> authorization_url = oauth.authorization_url( + >>> print 'Please go here and authorize,', authorize_url + >>> redirect_response = raw_input('Paste the full redirect URL here.') + >>> oauth_session.parse_authorization_response(redirect_response) + { + "oauth_token": "Z6eEdO8MOmk394WozF5oKyuAv855l4Mlqo7hhlSLik", + "oauth_verifier": "sdflk3450FASDLJasd2349dfs" + } + + >>> # Using OAuth1 auth helper + >>> authorize_url = base_authorization_url + '?oauth_token=' >>> authorize_url = authorize_url + resource_owner_key >>> print 'Please go here and authorize,', authorize_url >>> verifier = raw_input('Please input the verifier') @@ -56,12 +89,26 @@ less awkward since you can simply redirect). .. code-block:: pycon + >>> access_token_url = 'https://api.twitter.com/oauth/access_token' + + >>> # Using OAuth1Session + >>> oauth = OAuth1Session(client_key, + client_secret=client_secret, + resource_owner_key=resource_owner_key, + resource_owner_secret=resource_owner_secret, + verifier=verifier) + >>> oauth.fetch_access_token(access_token_url) + { + "oauth_token": "6253282-eWudHldSbIaelX7swmsiHImEL4KinwaGloHANdrY", + "oauth_token_secret": "2EEfA6BG3ly3sR3RjE0IBSnlQu4ZrUzPiYKmrkVU" + } + + >>> # Using OAuth1 auth helper >>> oauth = OAuth1(client_key, client_secret=client_secret, resource_owner_key=resource_owner_key, resource_owner_secret=resource_owner_secret, verifier=verifier) - >>> access_token_url = 'https://api.twitter.com/oauth/access_token' >>> r = requests.post(url=access_token_url, auth=oauth) >>> r.content "oauth_token=6253282-eWudHldSbIaelX7swmsiHImEL4KinwaGloHANdrY&oauth_token_secret=2EEfA6BG3ly3sR3RjE0IBSnlQu4ZrUzPiYKmrkVU" @@ -74,13 +121,21 @@ less awkward since you can simply redirect). .. code-block:: pycon + >>> protected_url = 'https://api.twitter.com/1/account/settings.json' + + >>> # Using OAuth1Session + >>> oauth = OAuth1Session(client_key, + client_secret=client_secret, + resource_owner_key=resource_owner_key, + resource_owner_secret=resource_owner_secret) + >>> r = oauth.get(protected_url) + + >>> # Using OAuth1 auth helper >>> oauth = OAuth1(client_key, client_secret=client_secret, resource_owner_key=resource_owner_key, resource_owner_secret=resource_owner_secret) - >>> url = 'https://api.twitter.com/1/account/settings.json' - >>> r = requests.get(url=url, auth=oauth) - >>> # Enjoy =) + >>> r = requests.get(url=protected_url, auth=oauth) Signature placement - header, query or body? From 9fd979e2dd56294f6e012763a59df4813c38a3a7 Mon Sep 17 00:00:00 2001 From: Ib Lundgren Date: Wed, 22 May 2013 17:59:45 +0100 Subject: [PATCH 05/10] Add workflow sections to index. --- docs/index.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/index.rst b/docs/index.rst index 8a6b9dbb..363876cf 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -11,6 +11,8 @@ Contents: .. toctree:: :maxdepth: 2 + oauth1_workflow + oauth2_workflow api From 67a002030649aadb88ee87aa2196a387e31cd671 Mon Sep 17 00:00:00 2001 From: Ib Lundgren Date: Thu, 23 May 2013 08:44:33 +0100 Subject: [PATCH 06/10] RTD link fixes. --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 0570235f..12a75976 100644 --- a/README.rst +++ b/README.rst @@ -24,7 +24,7 @@ Accessing protected resources using requests_oauthlib is as simple as: Before accessing resources you will need to obtain a few credentials from your provider (i.e. Twitter) and authorization from the user for whom you wish to retrieve resources for. You can read all about this in the full -`OAuth 1 workflow guide on RTD `_. +`OAuth 1 workflow guide on RTD `_. The OAuth 2 workflow -------------------- @@ -43,9 +43,9 @@ Fetching a protected resource after obtaining an access token can be as simple a >>> r = google.get(url) Before accessing resources you will need to obtain a few credentials from your -provider (i.e. Twitter) and authorization from the user for whom you wish to +provider (i.e. Google) and authorization from the user for whom you wish to retrieve resources for. You can read all about this in the full -`OAuth 2 workflow guide on RTD `_. +`OAuth 2 workflow guide on RTD `_. Installation ------------- From 6c481d57d52e265353eb133a3e69bff7da20894b Mon Sep 17 00:00:00 2001 From: Ib Lundgren Date: Thu, 23 May 2013 09:50:00 +0100 Subject: [PATCH 07/10] Refreshing tokens + other flows coming soon. --- docs/oauth2_workflow.rst | 101 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 3 deletions(-) diff --git a/docs/oauth2_workflow.rst b/docs/oauth2_workflow.rst index 957ea188..0eb63439 100644 --- a/docs/oauth2_workflow.rst +++ b/docs/oauth2_workflow.rst @@ -1,6 +1,10 @@ OAuth 2 Workflow ================ +The steps below outline how to use the default WebApplication flow to +obtain an access token and fetch a protected resource. In this example +the provider is Google and the procted resource the user profile. + 0. Obtaining credentials from your OAuth provider manually. You will need at minimum a ``client_id`` but likely also a ``client_secret``. During this process you might also be required to register a default redirect @@ -36,7 +40,7 @@ OAuth 2 Workflow >>> authorization_response = raw_input('Enter the full callback URL') 2. Fetch an access token from the provider using the authorization code - obtained during user authorization: + obtained during user authorization. .. code-block:: pycon @@ -59,9 +63,100 @@ OAuth 2 Workflow Available workflows ------------------- -TODO: outline implicit, password and client credentials flows. +There are four core work flows: + +1. Authorization Code Grant (WebApplication flow) - see section above. +2. Implicit Grant (MobileApplication flow) - docs coming soon. +3. Resource Owner Password Credentials Grant (LegacyApplication flow) - docs coming soon. +4. Client Credentials Grant (BackendApplication flow) - docs coming soon. Refreshing tokens ----------------- -TODO: demonstrate how to refresh tokens. +Certain providers will give you a ``refresh_token`` along with the +``access_token``. These can be used to directly fetch new access tokens without +going through the normal OAuth workflow. ``requests-oauthlib`` provides three +methods of obtaining refresh tokens. All of these are dependant on you +specifying an accurate ``expires_in`` in the token. + +``expires_in`` is a credential given with the access and refresh token +indiciating in how many seconds from now the access token expires. Commonly, +access tokens expire after an hour an the ``expires_in`` would be ``3600``. +Without this it is impossible for ``requests-oauthlib`` to know when a token +is expired as the status code of a request failing due to token expiration is +not defined. + +If you are not interested in token refreshing, always pass in a positive value +for ``expires_in`` or omit it entirely. + +**0. The token we will use throughout the three examples.**. + +.. code-block:: pycon + + >>> token = { + ... 'access_token': 'eswfld123kjhn1v5423', + ... 'refresh_token': 'asdfkljh23490sdf', + ... 'token_type': 'Bearer', + ... 'expires_in': '-30', # initially 3600, need to be updated by you + ... } + >>> client_id = 'foo' + >>> refresh_url = 'https://provider.com/token' + >>> protected_url = 'https://provider.com/secret' + + >>> # most providers will ask you for extra credentials to be passed along + >>> # when refreshing tokens, usually for authentication purposes. + >>> extra = { + ... 'client_id': client_id, + ... 'client_secret': 'potato', + ... } + + >>> # After updating the token you will most likely want to save it. + >>> def token_saver(token): + ... # save token in database / session + +**1. Need to refresh is indicated through a raised error.** + +This is the most basic version in which an error is raised when refresh +is necessary but refreshing is done manually. + +.. code-block:: pycon + + >>> from requests_oauthlib import OAuth2Session + >>> from oauthlib.oauth2 import TokenExpiredError + >>> try: + ... client = OAuth2Session(client_id, token=token) + ... r = client.get(protected_url) + >>> except TokenExpiredError as e: + ... token = client.refresh_token(refresh_url, **extra) + ... token_saver(token) + >>> client = OAuth2Session(client_id, token=token) + >>> r = client.get(protected_url) + +**2. Automatic refresh is indicated through a raised error.** + +This is the, arguably awkward, middle between the basic and convenient refresh +methods in which a token is automatically refreshed, but saving the new token +is done manually. + +.. code-block:: pycon + + >>> from requests_oauthlib import OAuth2Session, TokenUpdated + >>> try: + ... client = OAuth2Session(client_id, token=token, + ... auto_refresh_kwargs=extra, auto_refresh_url=refresh_url) + ... r = client.get(protected_url) + >>> except TokenUpdated as e: + ... token_saver(e.token) + +**3. (Recommended) Automatic refresh and token update, no errors, no worries.** + +The third and recommended method will automatically fetch refresh tokens and +save them. It requires no exception catching and results in clean code. Remember +however that you still need to update ``expires_in`` to trigger the refresh. + +.. code-block:: pycon + + >>> from requests_oauthlib import OAuth2Session + >>> client = OAuth2Session(client_id, token=token, auto_refresh_url=refresh_url, + ... auto_refresh_kwargs=extra, token_updater=token_saver) + >>> r = client.get(protected_url) From 5df630e7a5ead2ae3aa77feaf56eaf78e1338607 Mon Sep 17 00:00:00 2001 From: Ib Lundgren Date: Thu, 23 May 2013 11:55:32 +0100 Subject: [PATCH 08/10] Fix minor errors in oauth 1 workflow. --- docs/oauth1_workflow.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/oauth1_workflow.rst b/docs/oauth1_workflow.rst index 5f102e42..f529a05d 100644 --- a/docs/oauth1_workflow.rst +++ b/docs/oauth1_workflow.rst @@ -68,9 +68,9 @@ Workflow example showing use of both OAuth1 and OAuth1Session >>> base_authorization_url = 'https://api.twitter.com/oauth/authorize' >>> # Using OAuth1Session - >>> authorization_url = oauth.authorization_url( - >>> print 'Please go here and authorize,', authorize_url - >>> redirect_response = raw_input('Paste the full redirect URL here.') + >>> authorization_url = oauth.authorization_url(base_authorization_url) + >>> print 'Please go here and authorize,', authoriation_url + >>> redirect_response = raw_input('Paste the full redirect URL here: ') >>> oauth_session.parse_authorization_response(redirect_response) { "oauth_token": "Z6eEdO8MOmk394WozF5oKyuAv855l4Mlqo7hhlSLik", From 9f1e8948eb2884e2012d1d74fd74c94981398b67 Mon Sep 17 00:00:00 2001 From: Ib Lundgren Date: Tue, 9 Jul 2013 21:59:28 +0100 Subject: [PATCH 09/10] Make subsections actions. --- docs/oauth2_workflow.rst | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/oauth2_workflow.rst b/docs/oauth2_workflow.rst index 0eb63439..2f5f3512 100644 --- a/docs/oauth2_workflow.rst +++ b/docs/oauth2_workflow.rst @@ -89,7 +89,8 @@ not defined. If you are not interested in token refreshing, always pass in a positive value for ``expires_in`` or omit it entirely. -**0. The token we will use throughout the three examples.**. +(ALL) Define the token, token saver and needed credentials +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: pycon @@ -114,7 +115,8 @@ for ``expires_in`` or omit it entirely. >>> def token_saver(token): ... # save token in database / session -**1. Need to refresh is indicated through a raised error.** +(First) Define Try-Catch TokenExpiredError on each request +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This is the most basic version in which an error is raised when refresh is necessary but refreshing is done manually. @@ -132,7 +134,8 @@ is necessary but refreshing is done manually. >>> client = OAuth2Session(client_id, token=token) >>> r = client.get(protected_url) -**2. Automatic refresh is indicated through a raised error.** +(Second) Define automatic refresh is automatic but update token manually +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This is the, arguably awkward, middle between the basic and convenient refresh methods in which a token is automatically refreshed, but saving the new token @@ -148,7 +151,8 @@ is done manually. >>> except TokenUpdated as e: ... token_saver(e.token) -**3. (Recommended) Automatic refresh and token update, no errors, no worries.** +(Third, Recommended) Define automatic refresh and update +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The third and recommended method will automatically fetch refresh tokens and save them. It requires no exception catching and results in clean code. Remember From 5c5f7d686a294254bce82412dc3fd63c2c750508 Mon Sep 17 00:00:00 2001 From: Ib Lundgren Date: Tue, 9 Jul 2013 22:05:15 +0100 Subject: [PATCH 10/10] Table of contents --- docs/oauth2_workflow.rst | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/oauth2_workflow.rst b/docs/oauth2_workflow.rst index 2f5f3512..0090ffc5 100644 --- a/docs/oauth2_workflow.rst +++ b/docs/oauth2_workflow.rst @@ -1,9 +1,16 @@ OAuth 2 Workflow ================ -The steps below outline how to use the default WebApplication flow to +.. contents:: + :depth: 3 + :local: + +Web Application Flow +-------------------- + +The steps below outline how to use the default Authorization Grant Type flow to obtain an access token and fetch a protected resource. In this example -the provider is Google and the procted resource the user profile. +the provider is Google and the protected resource the user profile. 0. Obtaining credentials from your OAuth provider manually. You will need at minimum a ``client_id`` but likely also a ``client_secret``. During @@ -134,8 +141,8 @@ is necessary but refreshing is done manually. >>> client = OAuth2Session(client_id, token=token) >>> r = client.get(protected_url) -(Second) Define automatic refresh is automatic but update token manually -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +(Second) Define automatic token refresh automatic but update manually +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This is the, arguably awkward, middle between the basic and convenient refresh methods in which a token is automatically refreshed, but saving the new token @@ -151,8 +158,8 @@ is done manually. >>> except TokenUpdated as e: ... token_saver(e.token) -(Third, Recommended) Define automatic refresh and update -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +(Third, Recommended) Define automatic token refresh and update +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The third and recommended method will automatically fetch refresh tokens and save them. It requires no exception catching and results in clean code. Remember