summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIb Lundgren <ib.lundgren@gmail.com>2013-09-17 15:18:04 +0100
committerIb Lundgren <ib.lundgren@gmail.com>2013-09-17 15:18:04 +0100
commit3a30df69d19f56d1eeedcba9c0742186e85aac85 (patch)
treefec4621968c46afcd1546203ef3e08f7407b28a2
parent8b441dc66514873b70f1bff857c841f03da10dcd (diff)
downloadoauthlib-3a30df69d19f56d1eeedcba9c0742186e85aac85.tar.gz
Restructure API docs to mimic code structure more.
-rw-r--r--docs/index.rst13
-rw-r--r--docs/oauth1/client.rst6
-rw-r--r--docs/oauth1/endpoints.rst12
-rw-r--r--docs/oauth1/endpoints/access_token.rst5
-rw-r--r--docs/oauth1/endpoints/authorization.rst5
-rw-r--r--docs/oauth1/endpoints/endpoints.rst16
-rw-r--r--docs/oauth1/endpoints/request_token.rst5
-rw-r--r--docs/oauth1/endpoints/resource.rst5
-rw-r--r--docs/oauth1/oauth1.rst2
-rw-r--r--docs/oauth1/server.rst5
-rw-r--r--docs/oauth2/clients/backendapplicationclient.rst4
-rw-r--r--docs/oauth2/clients/baseclient.rst4
-rw-r--r--docs/oauth2/clients/client.rst6
-rw-r--r--docs/oauth2/clients/legacyapplicationclient.rst4
-rw-r--r--docs/oauth2/clients/mobileapplicationclient.rst4
-rw-r--r--docs/oauth2/clients/webapplicationclient.rst4
-rw-r--r--docs/oauth2/endpoints/authorization.rst123
-rw-r--r--docs/oauth2/endpoints/endpoints.rst29
-rw-r--r--docs/oauth2/endpoints/resource.rst41
-rw-r--r--docs/oauth2/endpoints/revocation.rst26
-rw-r--r--docs/oauth2/endpoints/token.rst86
-rw-r--r--docs/oauth2/grants/grants.rst33
-rw-r--r--docs/oauth2/oauth2.rst4
-rw-r--r--docs/oauth2/server.rst690
-rw-r--r--docs/oauth2/tokens/bearer.rst13
-rw-r--r--docs/oauth2/tokens/jwt.rst7
-rw-r--r--docs/oauth2/tokens/mac.rst8
-rw-r--r--docs/oauth2/tokens/saml.rst7
-rw-r--r--docs/oauth2/tokens/tokens.rst18
-rw-r--r--oauthlib/oauth1/rfc5849/endpoints/access_token.py2
-rw-r--r--oauthlib/oauth1/rfc5849/endpoints/authorization.py2
-rw-r--r--oauthlib/oauth1/rfc5849/endpoints/request_token.py2
-rw-r--r--oauthlib/oauth1/rfc5849/endpoints/resource.py2
33 files changed, 852 insertions, 341 deletions
diff --git a/docs/index.rst b/docs/index.rst
index eb81b2c..c44333a 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -15,15 +15,18 @@ For news and discussions please check out our `G+ OAuthLib community`_.
.. _`G+ OAuthLib community`: https://plus.google.com/communities/101889017375384052571
.. toctree::
- :maxdepth: 2
+ :maxdepth: 1
+ feature_matrix
+ faq
+ contributing
oauth_1_versus_oauth_2
+
+.. toctree::
+ :maxdepth: 3
+
oauth1/oauth1
oauth2/oauth2
- contributing
- faq
- feature_matrix
-
Indices and tables
==================
diff --git a/docs/oauth1/client.rst b/docs/oauth1/client.rst
index 8d0f121..868a7ad 100644
--- a/docs/oauth1/client.rst
+++ b/docs/oauth1/client.rst
@@ -1,6 +1,6 @@
-=========================
-OAuth 1: Using the Client
-=========================
+================
+Using the Client
+================
**Are you using requests?**
diff --git a/docs/oauth1/endpoints.rst b/docs/oauth1/endpoints.rst
index 0b76212..7634554 100644
--- a/docs/oauth1/endpoints.rst
+++ b/docs/oauth1/endpoints.rst
@@ -10,6 +10,12 @@ used either independently or in a combination. They depend on the use of a
See :doc:`preconfigured_servers` for available composite endpoints/servers.
+RequestTokenEndpoint
+--------------------
+
+.. autoclass:: oauthlib.oauth1.RequestTokenEndpoint
+ :members:
+
AuthorizationEndpoint
---------------------
@@ -22,12 +28,6 @@ AccessTokenEndpoint
.. autoclass:: oauthlib.oauth1.AccessTokenEndpoint
:members:
-RequestTokenEndpoint
---------------------
-
-.. autoclass:: oauthlib.oauth1.RequestTokenEndpoint
- :members:
-
ResourceEndpoint
----------------
diff --git a/docs/oauth1/endpoints/access_token.rst b/docs/oauth1/endpoints/access_token.rst
new file mode 100644
index 0000000..d2ceba1
--- /dev/null
+++ b/docs/oauth1/endpoints/access_token.rst
@@ -0,0 +1,5 @@
+Access Token
+------------
+
+.. autoclass:: oauthlib.oauth1.AccessTokenEndpoint
+ :members:
diff --git a/docs/oauth1/endpoints/authorization.rst b/docs/oauth1/endpoints/authorization.rst
new file mode 100644
index 0000000..98f788c
--- /dev/null
+++ b/docs/oauth1/endpoints/authorization.rst
@@ -0,0 +1,5 @@
+Authorization
+-------------
+
+.. autoclass:: oauthlib.oauth1.AuthorizationEndpoint
+ :members:
diff --git a/docs/oauth1/endpoints/endpoints.rst b/docs/oauth1/endpoints/endpoints.rst
new file mode 100644
index 0000000..d869824
--- /dev/null
+++ b/docs/oauth1/endpoints/endpoints.rst
@@ -0,0 +1,16 @@
+Provider endpoints
+==================
+
+Each endpoint is responsible for one step in the OAuth 1 workflow. They can be
+used either independently or in a combination. They depend on the use of a
+:doc:`../validator`.
+
+See :doc:`../preconfigured_servers` for available composite endpoints/servers.
+
+.. toctree::
+ :maxdepth: 2
+
+ request_token
+ authorization
+ access_token
+ resource
diff --git a/docs/oauth1/endpoints/request_token.rst b/docs/oauth1/endpoints/request_token.rst
new file mode 100644
index 0000000..48be472
--- /dev/null
+++ b/docs/oauth1/endpoints/request_token.rst
@@ -0,0 +1,5 @@
+Request Token
+-------------
+
+.. autoclass:: oauthlib.oauth1.RequestTokenEndpoint
+ :members:
diff --git a/docs/oauth1/endpoints/resource.rst b/docs/oauth1/endpoints/resource.rst
new file mode 100644
index 0000000..95fccf6
--- /dev/null
+++ b/docs/oauth1/endpoints/resource.rst
@@ -0,0 +1,5 @@
+Resource authorization
+----------------------
+
+.. autoclass:: oauthlib.oauth1.ResourceEndpoint
+ :members:
diff --git a/docs/oauth1/oauth1.rst b/docs/oauth1/oauth1.rst
index 64d8a3e..263328d 100644
--- a/docs/oauth1/oauth1.rst
+++ b/docs/oauth1/oauth1.rst
@@ -6,4 +6,4 @@ OAuth 1.0
client
server
-
+ endpoints/endpoints
diff --git a/docs/oauth1/server.rst b/docs/oauth1/server.rst
index 8fcfd2d..fec8e40 100644
--- a/docs/oauth1/server.rst
+++ b/docs/oauth1/server.rst
@@ -1,5 +1,6 @@
-OAuth 1: Creating a Provider
-============================
+===================
+Creating a Provider
+===================
OAuthLib is a dependency free library that may be used with any web
framework. That said, there are framework specific helper libraries
diff --git a/docs/oauth2/clients/backendapplicationclient.rst b/docs/oauth2/clients/backendapplicationclient.rst
index e0f4774..11bfaed 100644
--- a/docs/oauth2/clients/backendapplicationclient.rst
+++ b/docs/oauth2/clients/backendapplicationclient.rst
@@ -1,5 +1,5 @@
-Client Credentials Grant flow (BackendApplicationClient)
---------------------------------------------------------
+BackendApplicationClient
+------------------------
.. autoclass:: oauthlib.oauth2.BackendApplicationClient
:members:
diff --git a/docs/oauth2/clients/baseclient.rst b/docs/oauth2/clients/baseclient.rst
index 122af7f..48dcb48 100644
--- a/docs/oauth2/clients/baseclient.rst
+++ b/docs/oauth2/clients/baseclient.rst
@@ -1,5 +1,5 @@
-Base Client (Client)
---------------------
+Base Client
+-----------
.. autoclass:: oauthlib.oauth2.Client
:members:
diff --git a/docs/oauth2/clients/client.rst b/docs/oauth2/clients/client.rst
index 8486f3d..9b9d289 100644
--- a/docs/oauth2/clients/client.rst
+++ b/docs/oauth2/clients/client.rst
@@ -1,6 +1,6 @@
-======================
-OAuth 2: Using Clients
-======================
+=============
+Using Clients
+=============
OAuthLib supports all four core grant types defined in the OAuth 2 RFC and
will continue to add more as they are defined. For more information on how
diff --git a/docs/oauth2/clients/legacyapplicationclient.rst b/docs/oauth2/clients/legacyapplicationclient.rst
index 8b85608..062a6a7 100644
--- a/docs/oauth2/clients/legacyapplicationclient.rst
+++ b/docs/oauth2/clients/legacyapplicationclient.rst
@@ -1,5 +1,5 @@
-Resource Owner Password Credentials Grant flow (LegacyApplicationClient)
-------------------------------------------------------------------------
+LegacyApplicationClient
+-----------------------
.. autoclass:: oauthlib.oauth2.LegacyApplicationClient
:members:
diff --git a/docs/oauth2/clients/mobileapplicationclient.rst b/docs/oauth2/clients/mobileapplicationclient.rst
index cd96377..66394d9 100644
--- a/docs/oauth2/clients/mobileapplicationclient.rst
+++ b/docs/oauth2/clients/mobileapplicationclient.rst
@@ -1,5 +1,5 @@
-Implicit Grant flow (MobileApplicationClient)
----------------------------------------------
+MobileApplicationClient
+-----------------------
.. autoclass:: oauthlib.oauth2.MobileApplicationClient
:members:
diff --git a/docs/oauth2/clients/webapplicationclient.rst b/docs/oauth2/clients/webapplicationclient.rst
index a29081b..ecbf994 100644
--- a/docs/oauth2/clients/webapplicationclient.rst
+++ b/docs/oauth2/clients/webapplicationclient.rst
@@ -1,5 +1,5 @@
-Authorization Code Grant flow (WebApplicationClient)
-----------------------------------------------------
+WebApplicationClient
+--------------------
.. autoclass:: oauthlib.oauth2.WebApplicationClient
:members:
diff --git a/docs/oauth2/endpoints/authorization.rst b/docs/oauth2/endpoints/authorization.rst
new file mode 100644
index 0000000..3c42387
--- /dev/null
+++ b/docs/oauth2/endpoints/authorization.rst
@@ -0,0 +1,123 @@
+=============
+Authorization
+=============
+
+Authorization can be either explicit or implicit. The former require the user to
+actively authorize the client by being redirected to the authorization endpoint.
+There he/she is usually presented by a form and asked to either accept or deny
+access to certain scopes. These scopes can be thought of as Access Control Lists
+that are tied to certain privileges and categories of resources, such as write
+access to their status feed or read access to their profile. It is vital that
+the implications of granting access to a certain scope is very clear in the
+authorization form presented to the user. It is up to the provider to allow the
+user agree to all, a few or none of the scopes. Being flexible here is a great
+benefit to the user at the cost of added complexity in both the provider and
+clients.
+
+Implicit authorization happens when the authorization happens before the OAuth
+flow, such as the user giving the client his/her password and username, or if
+there is a very high level of trust between the user, client and provider and no
+explicit authorization is necessary.
+
+Examples of explicit authorization is the Authorization Code Grant and the
+Implicit Grant.
+
+Examples of implicit authorization is the Resource Owner Password Credentials
+Grant and the Client Credentials Grant.
+
+**Pre Authorization Request**
+ OAuth is known for it's authorization page where the user accepts or denies
+ access to a certain client and set of scopes. Before presenting the user
+ with such a form you need to ensure the credentials the client supplied in
+ the redirection to this page are valid.
+
+ .. code-block:: python
+
+ # Initial setup
+ from your_validator import your_validator
+ server = WebApplicationServer(your_validator)
+
+ # Validate request
+ uri = 'https://example.com/authorize?client_id=foo&state=xyz
+ headers, body, http_method = {}, '', 'GET'
+
+ from oauthlib.oauth2 import FatalClientError
+ from your_framework import redirect
+ try:
+ scopes, credentials = server.validate_authorization_request(
+ uri, http_method, body, headers)
+ # scopes will hold default scopes for client, i.e.
+ ['https://example.com/userProfile', 'https://example.com/pictures']
+
+ # credentials is a dictionary of
+ {
+ 'client_id': 'foo',
+ 'redirect_uri': 'https://foo.com/welcome_back',
+ 'response_type': 'code',
+ 'state': 'randomstring',
+ }
+ # these credentials will be needed in the post authorization view and
+ # should be persisted between. None of them are secret but take care
+ # to ensure their integrety if embedding them in the form or cookies.
+ from your_datastore import persist_credentials
+ persist_credentials(credentials)
+
+ # Present user with a nice form where client (id foo) request access to
+ # his default scopes (omitted from request), after which you will
+ # redirect to his default redirect uri (omitted from request).
+
+ except FatalClientError as e:
+ # this is your custom error page
+ from your_view_helpers import error_to_response
+ return error_to_response(e)
+
+
+**Post Authorization Request**
+ Generally, this is where you handle the submitted form. Rather than using
+ ``validate_authorization_request`` we use ``create_authorization_response``
+ which in the case of Authorization Code Grant embed an authorization code in
+ the client provided redirect uri.
+
+ .. code-block:: python
+
+ # Initial setup
+ from your_validator import your_validator
+ server = WebApplicationServer(your_validator)
+
+ # Validate request
+ uri = 'https://example.com/post_authorize?client_id=foo
+ headers, body, http_method = {}, '', 'GET'
+
+ # Fetch the credentials saved in the pre authorization phase
+ from your_datastore import fetch_credentials
+ credentials = fetch_credentials()
+
+ # Fetch authorized scopes from the request
+ from your_framework import request
+ scopes = request.POST.get('scopes')
+
+ from oauthlib.oauth2 import FatalClientError, OAuth2Error
+ from your_framework import http_response
+ http_response(body, status=status, headers=headers)
+ try:
+ headers, body, status = server.create_authorization_response(
+ uri, http_method, body, headers, scopes, credentials)
+ # headers = {'Location': 'https://foo.com/welcome_back?code=somerandomstring&state=xyz'}, this might change to include suggested headers related
+ # to cache best practices etc.
+ # body = '', this might be set in future custom grant types
+ # status = 302, suggested HTTP status code
+
+ return http_response(body, status=status, headers=headers)
+
+ except FatalClientError as e:
+ # this is your custom error page
+ from your_view_helpers import error_to_response
+ return error_to_response(e)
+
+ except OAuth2Error as e:
+ # Less grave errors will be reported back to client
+ client_redirect_uri = credentials.get('redirect_uri')
+ redirect(e.in_uri(client_redirect_uri))
+
+.. autoclass:: oauthlib.oauth2.AuthorizationEndpoint
+ :members:
diff --git a/docs/oauth2/endpoints/endpoints.rst b/docs/oauth2/endpoints/endpoints.rst
new file mode 100644
index 0000000..42b1deb
--- /dev/null
+++ b/docs/oauth2/endpoints/endpoints.rst
@@ -0,0 +1,29 @@
+Provider Endpoints
+==================
+
+Endpoints in OAuth 2 are targets with a specific responsibility and often
+associated with a particular URL. Because of this the word endpoint might be
+used interchangably from the endpoint url.
+
+There main three responsibilities in an OAuth 2 flow is to authorize access to a
+certain users resources to a client, to supply said client with a token
+embodying this authorization and to verify that the token is valid when the
+client attempts to access thee user resources on their behalf.
+
+.. toctree::
+ :maxdepth: 2
+
+ authorization
+ token
+ resource
+ revocation
+
+There are three different endpoints, the authorization endpoint which mainly
+handles user authorization, the token endpoint which provides tokens and the
+resource endpoint which provides access to protected resources. It is to the
+endpoints you will feed requests and get back an almost complete response. This
+process is simplified for you using a decorator such as the django one described
+later.
+
+The main purpose of the endpoint in OAuthLib is to figure out which grant type
+or token to dispatch the request to.
diff --git a/docs/oauth2/endpoints/resource.rst b/docs/oauth2/endpoints/resource.rst
new file mode 100644
index 0000000..a5ff885
--- /dev/null
+++ b/docs/oauth2/endpoints/resource.rst
@@ -0,0 +1,41 @@
+======================
+Resource authorization
+======================
+
+Resource endpoints verify that the token presented is valid and granted access
+to the scopes associated with the resource in question.
+
+**Request Verfication**
+ Each view may set certain scopes under which it is bound. Only requests
+ that present an access token bound to the correct scopes may access the
+ view. Access tokens are commonly embedded in the authorization header but
+ may appear in the query or the body as well.
+
+ .. code-block:: python
+
+ # Initial setup
+ from your_validator import your_validator
+ server = WebApplicationServer(your_validator)
+
+ # Per view scopes
+ required_scopes = ['https://example.com/userProfile']
+
+ # Validate request
+ uri = 'https://example.com/userProfile?access_token=sldafh309sdf'
+ headers, body, http_method = {}, '', 'GET'
+
+ valid, oauthlib_request = server.verify_request(
+ uri, http_method, body, headers, required_scopes)
+
+ # oauthlib_request has a few convenient attributes set such as
+ # oauthlib_request.client = the client associated with the token
+ # oauthlib_request.user = the user associated with the token
+ # oauthlib_request.scopes = the scopes bound to this token
+
+ if valid:
+ # return the protected resource / view
+ else:
+ # return an http forbidden 403
+
+.. autoclass:: oauthlib.oauth2.ResourceEndpoint
+ :members:
diff --git a/docs/oauth2/endpoints/revocation.rst b/docs/oauth2/endpoints/revocation.rst
new file mode 100644
index 0000000..a5ca9ba
--- /dev/null
+++ b/docs/oauth2/endpoints/revocation.rst
@@ -0,0 +1,26 @@
+================
+Token revocation
+================
+
+Revocation endpoints invalidate access and refresh tokens upon client request.
+They are commonly part of the authorization endpoint.
+
+.. code-block:: python
+
+ # Initial setup
+ from your_validator import your_validator
+ server = WebApplicationServer(your_validator)
+
+ # Token revocation
+ uri = 'https://example.com/revoke_token'
+ headers, body, http_method = {}, 'token=sldafh309sdf', 'POST'
+
+ headers, body, status = server.create_revocation_response(uri,
+ headers=headers, body=body, http_method=http_method)
+
+ from your_framework import http_response
+ http_response(body, status=status, headers=headers)
+
+
+.. autoclass:: oauthlib.oauth2.RevocationEndpoint
+ :members:
diff --git a/docs/oauth2/endpoints/token.rst b/docs/oauth2/endpoints/token.rst
new file mode 100644
index 0000000..bf1a256
--- /dev/null
+++ b/docs/oauth2/endpoints/token.rst
@@ -0,0 +1,86 @@
+==============
+Token creation
+==============
+
+Token endpoints issue tokens to clients who have already been authorized access,
+be it by explicit actions from the user or implicitely. The token response is
+well defined and typically consist of an unguessable access token, the token
+type, its expiration from now in seconds and depending on the scenario, a
+refresh token to be used to fetch new access tokens without authorization.
+
+One argument for OAuth 2 being more scalable than OAuth 1 is that tokens may
+contain hidden information. A provider may embed information such as client
+identifier, user identifier, expiration times, etc. in the token by encrypting
+it. This trades a slight increase in work required to decrypt the token but
+frees the necessary database lookups otherwise required, thus improving latency
+substantially. OAuthlib currently does not provide a method for creating
+crypto-tokens but may do in the future.
+
+The standard token type, Bearer, does not require that the provider bind a
+specific client to the token. Not binding clients to tokens allow for anonymized
+tokens which unless you are certain you need them, are a bad idea.
+
+**Token Request**
+ A POST request used in most grant types but with a varied setup of
+ credentials. If you wish to embed extra credentials in the request, i.e. for
+ later use in validation or when creating the token, you can use the
+ ``credentials`` argument in ``create_token_response``.
+
+ All responses are in json format and the headers argument returned by
+ ``create_token_response`` will contain a few suggested headers related to
+ content type and caching.
+
+ .. code-block:: python
+
+ # Initial setup
+ from your_validator import your_validator
+ server = WebApplicationServer(your_validator)
+
+ # Validate request
+ uri = 'https://example.com/token'
+ http_method = 'POST'
+ body = 'authorization_code=somerandomstring&'
+ 'grant_type=authorization_code&'
+ # Clients authenticate through a method of your choosing, for example
+ # using HTTP Basic Authentication
+ headers = { 'Authorization': 'Basic ksjdhf923sf' }
+
+ # Extra credentials you wish to include
+ credentials = {'client_ip': '1.2.3.4'}
+
+ headers, body, status = server.create_token_response(
+ uri, http_method, body, headers, credentials)
+
+ # headers will contain some suggested headers to add to your response
+ {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ 'Cache-Control': 'no-store',
+ 'Pragma': 'no-cache',
+ }
+ # body will contain the token in json format and expiration from now
+ # in seconds.
+ {
+ 'access_token': 'sldafh309sdf',
+ 'refresh_token': 'alsounguessablerandomstring',
+ 'expires_in': 3600,
+ 'scopes': [
+ 'https://example.com/userProfile',
+ 'https://example.com/pictures'
+ ],
+ 'token_type': 'Bearer'
+ }
+ # body will contain an error code and possibly an error description if
+ # the request failed, also in json format.
+ {
+ 'error': 'invalid_grant_type',
+ 'description': 'athorizatoin_coed is not a valid grant type'
+ }
+ # status will be a suggested status code, 200 on ok, 400 on bad request
+ # and 401 if client is trying to use an invalid authorization code,
+ # fail to authenticate etc.
+
+ from your_framework import http_response
+ http_response(body, status=status, headers=headers)
+
+.. autoclass:: oauthlib.oauth2.TokenEndpoint
+ :members:
diff --git a/docs/oauth2/grants/grants.rst b/docs/oauth2/grants/grants.rst
new file mode 100644
index 0000000..1018d55
--- /dev/null
+++ b/docs/oauth2/grants/grants.rst
@@ -0,0 +1,33 @@
+===========
+Grant types
+===========
+
+.. toctree::
+ :maxdepth: 2
+
+ authcode
+ implicit
+ password
+ credentials
+
+Grant types are what make OAuth 2 so flexible. The Authorization Code grant is
+very similar to OAuth 1 (with less crypto), the Implicit grant serves less
+secure applications such as mobile applications, the Resource Owner Password
+Credentials grant allows for legacy applications to incrementally transition to
+OAuth 2, the Client Credentials grant is excellent for embedded services and
+backend applications.
+
+The main purpose of the grant types is to authorize access to protected
+resources in various ways with different security credentials.
+
+Naturally, OAuth 2 allows for extension grant types to be defined and OAuthLib
+attempts to cater for easy inclusion of this as much as possible.
+
+Certain grant types allow the issuing of refresh tokens which will allow a
+client to request new tokens for as long as you as provider allow them too. In
+general, OAuth 2 tokens should expire quickly and rather than annoying the user
+by require them to go through the authorization redirect loop you may use the
+refresh token to get a new access token. Refresh tokens, contrary to what their
+name suggest, are components of a grant type rather than token types (like
+Bearer tokens), much like the authorization code in the authorization code
+grant.
diff --git a/docs/oauth2/oauth2.rst b/docs/oauth2/oauth2.rst
index 4f56c82..2c92182 100644
--- a/docs/oauth2/oauth2.rst
+++ b/docs/oauth2/oauth2.rst
@@ -4,6 +4,8 @@ OAuth 2.0
.. toctree::
:maxdepth: 2
- overview
clients/client
server
+ endpoints/endpoints
+ grants/grants
+ tokens/tokens
diff --git a/docs/oauth2/server.rst b/docs/oauth2/server.rst
index cba56d1..fca6c3c 100644
--- a/docs/oauth2/server.rst
+++ b/docs/oauth2/server.rst
@@ -1,6 +1,6 @@
-============================
-OAuth 2: Creating a Provider
-============================
+===================
+Creating a Provider
+===================
OAuthLib is a dependency free library that may be used with any web
framework. That said, there are framework specific helper libraries
@@ -18,322 +18,392 @@ as well as provide an interface for a backend to store tokens, clients, etc.
.. _`django-oauth-toolkit`: https://github.com/evonove/django-oauth-toolkit
.. _`flask-oauthlib`: https://github.com/lepture/flask-oauthlib
-**1. Create your datastore models**
+.. contents:: Tutorial Contents
+ :depth: 3
- These models will represent various OAuth specific concepts. There are a few
- important links between them that the security of OAuth is based on. Below
- is a suggestion for models and why you need certain properties. There is
- also example Django model fields which should be straightforward to
- translate to other ORMs such as SQLAlchemy and the Appengine Datastore.
+1. Create your datastore models
+-------------------------------
- **User (or Resource Owner)**
- The user of your site which resources might be access by clients upon
- authorization from the user. In our example we will re-use the User
- model provided in django.contrib.auth.models. How the user authenticates
- is orthogonal from OAuth and may be any way you prefer::
+These models will represent various OAuth specific concepts. There are a few
+important links between them that the security of OAuth is based on. Below
+is a suggestion for models and why you need certain properties. There is
+also example Django model fields which should be straightforward to
+translate to other ORMs such as SQLAlchemy and the Appengine Datastore.
- from django.contrib.auth.models import User
+User (or Resource Owner)
+^^^^^^^^^^^^^^^^^^^^^^^^
- **Client (or Consumer)**
- The client interested in accessing protected resources.
+The user of your site which resources might be access by clients upon
+authorization from the user. In our example we will re-use the User
+model provided in django.contrib.auth.models. How the user authenticates
+is orthogonal from OAuth and may be any way you prefer::
- **Client Identifier**:
- Required. The identifier the client will use during the OAuth
- workflow. Structure is up to you and may be a simple UUID::
+ from django.contrib.auth.models import User
- client_id = django.db.models.CharField(max_length=100, unique=True)
+Client (or Consumer)
+^^^^^^^^^^^^^^^^^^^^
- **User**:
- Recommended. It is common practice to link each client with one of
- your existing users. Whether you do associate clients and users or
- not, ensure you are able to protect yourself against malicious
- clients::
+The client interested in accessing protected resources.
- user = django.db.models.ForeignKey(User)
+**Client Identifier**:
- **Grant Type**:
- Required. The grant type the client may utilize. This should only be
- one per client as each grant type has different security properties
- and it is best to keep them separate to avoid mistakes::
+ Required. The identifier the client will use during the OAuth
+ workflow. Structure is up to you and may be a simple UUID.
- # max_length and choices depend on which grants you support
- grant_type = django.db.models.CharField(max_length=18,
- choices=[('authorization_code', 'Authorization code')])
+ .. code-block:: python
+
+ client_id = django.db.models.CharField(max_length=100, unique=True)
+
+**User**:
+
+ Recommended. It is common practice to link each client with one of
+ your existing users. Whether you do associate clients and users or
+ not, ensure you are able to protect yourself against malicious
+ clients.
+
+ .. code-block:: python
- **Response Type**:
- Required, if using a grant type with an associated response type
- (eg. Authorization Code Grant) or using a grant which only utilizes
- response types (eg. Implicit Grant)::
+ user = django.db.models.ForeignKey(User)
- # max_length and choices depend on which response types you support
- response_type = django.db.models.CharField(max_length=4,
- choices=[('code', 'Authorization code')])
+**Grant Type**:
- **Scopes**:
- Required. The list of scopes the client may request access to. If
- you allow multiple types of grants this will vary related to their
- different security properties. For example, the Implicit Grant might
- only allow read-only scopes but the Authorization Grant also allow
- writes::
+ Required. The grant type the client may utilize. This should only be
+ one per client as each grant type has different security properties
+ and it is best to keep them separate to avoid mistakes.
- # You could represent it either as a list of keys or by serializing
- # the scopes into a string.
- scopes = django.db.models.TextField()
+ .. code-block:: python
- # You might also want to mark a certain set of scopes as default
- # scopes in case the client does not specify any in the authorization
- default_scopes = django.db.models.TextField()
+ # max_length and choices depend on which response types you support
+ grant_type = django.db.models.CharField(max_length=18,
+ choices=[('authorization_code', 'Authorization code')])
- **Redirect URIs**:
- These are the absolute URIs that a client may use to redirect to after
- authorization. You should never allow a client to redirect to a URI
- that has not previously been registered::
+**Response Type**:
- # You could represent the URIs either as a list of keys or by
- # serializing them into a string.
- redirect_uris = django.db.models.TextField()
+ Required, if using a grant type with an associated response type
+ (eg. Authorization Code Grant) or using a grant which only utilizes
+ response types (eg. Implicit Grant).
- # You might also want to mark a certain URI as default in case the
- # client does not specify any in the authorization
- default_redirect_uri = django.db.models.TextField()
+ .. code-block:: python
- **Bearer Token (OAuth 2 Standard Token)**
- The most common type of OAuth 2 token. Through the documentation this
- will be considered an object with several properties, such as token type
- and expiration date, and distinct from the access token it contains.
- Think of OAuth 2 tokens as containers and access tokens and refresh
- tokens as text.
+ # max_length and choices depend on which response types you support
+ response_type = django.db.models.CharField(max_length=4,
+ choices=[('code', 'Authorization code')])
- **Client**:
- Association with the client to whom the token was given::
+**Scopes**:
- client = django.db.models.ForeignKey(Client)
+ Required. The list of scopes the client may request access to. If
+ you allow multiple types of grants this will vary related to their
+ different security properties. For example, the Implicit Grant might
+ only allow read-only scopes but the Authorization Grant also allow
+ writes.
- **User**:
- Association with the user to which protected resources this token
- grants access::
+ .. code-block:: python
- user = django.db.models.ForeignKey(User)
+ # You could represent it either as a list of keys or by serializing
+ # the scopes into a string.
+ scopes = django.db.models.TextField()
- **Scopes**:
- Scopes to which the token is bound. Attempt to access protected
- resources outside these scopes will be denied::
+ # You might also want to mark a certain set of scopes as default
+ # scopes in case the client does not specify any in the authorization
+ default_scopes = django.db.models.TextField()
- # You could represent it either as a list of keys or by serializing
- # the scopes into a string.
- scopes = django.db.models.TextField()
+**Redirect URIs**:
- **Access Token**:
- An unguessable unique string of characters::
+ These are the absolute URIs that a client may use to redirect to after
+ authorization. You should never allow a client to redirect to a URI
+ that has not previously been registered.
- access_token = django.db.models.CharField(max_length=100, unique=True)
+ .. code-block:: python
- **Refresh Token**:
- An unguessable unique string of characters. This token is only
- supplied to confidential clients. For example the Authorization Code
- Grant or the Resource Owner Password Credentials Grant::
+ # You could represent the URIs either as a list of keys or by
+ # serializing them into a string.
+ redirect_uris = django.db.models.TextField()
- refresh_token = django.db.models.CharField(max_length=100, unique=True)
+ # You might also want to mark a certain URI as default in case the
+ # client does not specify any in the authorization
+ default_redirect_uri = django.db.models.TextField()
- **Expiration time**:
- Exact time of expiration. Commonly this is one hour after creation::
+Bearer Token (OAuth 2 Standard Token)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- expires_at = django.db.models.DateTimeField()
+The most common type of OAuth 2 token. Through the documentation this
+will be considered an object with several properties, such as token type
+and expiration date, and distinct from the access token it contains.
+Think of OAuth 2 tokens as containers and access tokens and refresh
+tokens as text.
- **Authorization Code**
- This is specific to the Authorization Code grant and represent the
- temporary credential granted to the client upon successful
- authorization. It will later be exchanged for an access token, when that
- is done it should cease to exist. It should have a limited life time,
- less than ten minutes. This model is similar to the Bearer Token as it
- mainly acts a temporary storage of properties to later be transferred to
- the token.
+**Client**:
- **Client**:
- Association with the client to whom the token was given::
+ Association with the client to whom the token was given.
- client = django.db.models.ForeignKey(Client)
+ .. code-block:: python
- **User**:
- Association with the user to which protected resources this token
- grants access::
+ client = django.db.models.ForeignKey(Client)
- user = django.db.models.ForeignKey(User)
+**User**:
- **Scopes**:
- Scopes to which the token is bound. Attempt to access protected
- resources outside these scopes will be denied::
+ Association with the user to which protected resources this token
+ grants access.
- # You could represent it either as a list of keys or by serializing
- # the scopes into a string.
- scopes = django.db.models.TextField()
+ .. code-block:: python
- **Authorization Code**:
- An unguessable unique string of characters::
+ user = django.db.models.ForeignKey(User)
- code = django.db.models.CharField(max_length=100, unique=True)
+**Scopes**:
- **Expiration time**:
- Exact time of expiration. Commonly this is under ten minutes after
- creation::
+ Scopes to which the token is bound. Attempt to access protected
+ resources outside these scopes will be denied.
- expires_at = django.db.models.DateTimeField()
+ .. code-block:: python
-**2. Implement a validator**
+ # You could represent it either as a list of keys or by serializing
+ # the scopes into a string.
+ scopes = django.db.models.TextField()
- The majority of the work involved in implementing an OAuth 2 provider
- relates to mapping various validation and persistence methods to a storage
- backend. The not very accurately named interface you will need to implement
- is called a :doc:`RequestValidator <validator>` (name suggestions welcome).
+**Access Token**:
- An example of a very basic implementation of the validate_client_id method
- can be seen below::
+ An unguessable unique string of characters.
- from oauthlib.oauth2 import RequestValidator
+ .. code-block:: python
- # From the previous section on models
- from my_models import Client
+ access_token = django.db.models.CharField(max_length=100, unique=True)
- class MyRequestValidator(RequestValidator):
+**Refresh Token**:
- def validate_client_id(self, client_id, request):
- try:
- Client.objects.get(client_id=client_id)
- return True
- except Client.DoesNotExist:
- return False
+ An unguessable unique string of characters. This token is only
+ supplied to confidential clients. For example the Authorization Code
+ Grant or the Resource Owner Password Credentials Grant.
- The full API you will need to implement is available in the
- :doc:`RequestValidator <validator>` section. You might not need to implement
- all methods depending on which grant types you wish to support. A skeleton
- validator listing the methods required for the WebApplicationServer is
- available in the `examples`_ folder on GitHub.
+ .. code-block:: python
- .. _`examples`: https://github.com/idan/oauthlib/blob/master/examples/skeleton_oauth2_web_application_server.py
+ refresh_token = django.db.models.CharField(max_length=100, unique=True)
- Relevant sections include:
+**Expiration time**:
- .. toctree::
- :maxdepth: 1
+ Exact time of expiration. Commonly this is one hour after creation.
- validator
- security
+ .. code-block:: python
+ expires_at = django.db.models.DateTimeField()
-**3. Create your composite endpoint**
+Authorization Code
+^^^^^^^^^^^^^^^^^^
- Each of the endpoints can function independently from each other, however
- for this example it is easier to consider them as one unit. An example of a
- pre-configured all-in-one Authorization Code Grant endpoint is given below::
+This is specific to the Authorization Code grant and represent the
+temporary credential granted to the client upon successful
+authorization. It will later be exchanged for an access token, when that
+is done it should cease to exist. It should have a limited life time,
+less than ten minutes. This model is similar to the Bearer Token as it
+mainly acts a temporary storage of properties to later be transferred to
+the token.
- # From the previous section on validators
- from my_validator import MyRequestValidator
+**Client**:
- from oauthlib.oauth2 import WebApplicationServer
+ Association with the client to whom the token was given.
- validator = MyRequestValidator()
- server = WebApplicationServer(validator)
+ .. code-block:: python
- Relevant sections include:
+ client = django.db.models.ForeignKey(Client)
- .. toctree::
- :maxdepth: 1
+**User**:
- preconfigured_servers
+ Association with the user to which protected resources this token
+ grants access.
+ .. code-block:: python
-**4. Create your endpoint views**
+ user = django.db.models.ForeignKey(User)
- We are implementing support for the Authorization Code Grant and will
- therefore need two views for the authorization, pre- and post-authorization
- together with the token view. We also include an error page to redirect
- users to if the client supplied invalid credentials in their redirection,
- for example an invalid redirect URI.
+**Scopes**:
- The example using Django but should be transferable to any framework.
+ Scopes to which the token is bound. Attempt to access protected
+ resources outside these scopes will be denied.
.. code-block:: python
- # Handles GET and POST requests to /authorize
- class AuthorizationView(View):
+ # You could represent it either as a list of keys or by serializing
+ # the scopes into a string.
+ scopes = django.db.models.TextField()
+
+**Authorization Code**:
+
+ An unguessable unique string of characters.
+
+ .. code-block:: python
+
+ code = django.db.models.CharField(max_length=100, unique=True)
+
+**Expiration time**:
+
+ Exact time of expiration. Commonly this is under ten minutes after
+ creation.
+
+ .. code-block:: python
+
+ expires_at = django.db.models.DateTimeField()
+
+2. Implement a validator
+------------------------
+
+The majority of the work involved in implementing an OAuth 2 provider
+relates to mapping various validation and persistence methods to a storage
+backend. The not very accurately named interface you will need to implement
+is called a :doc:`RequestValidator <validator>` (name suggestions welcome).
+
+An example of a very basic implementation of the validate_client_id method
+can be seen below.
+
+.. code-block:: python
+
+ from oauthlib.oauth2 import RequestValidator
+
+ # From the previous section on models
+ from my_models import Client
+
+ class MyRequestValidator(RequestValidator):
+
+ def validate_client_id(self, client_id, request):
+ try:
+ Client.objects.get(client_id=client_id)
+ return True
+ except Client.DoesNotExist:
+ return False
+
+The full API you will need to implement is available in the
+:doc:`RequestValidator <validator>` section. You might not need to implement
+all methods depending on which grant types you wish to support. A skeleton
+validator listing the methods required for the WebApplicationServer is
+available in the `examples`_ folder on GitHub.
+
+.. _`examples`: https://github.com/idan/oauthlib/blob/master/examples/skeleton_oauth2_web_application_server.py
+
+Relevant sections include:
- def __init__(self):
- # Using the server from previous section
- self._authorization_endpoint = server
+.. toctree::
+ :maxdepth: 1
- def get(self, request):
- # You need to define extract_params and make sure it does not
- # include file like objects waiting for input. In Django this
- # is request.META['wsgi.input'] and request.META['wsgi.errors']
- uri, http_method, body, headers = extract_params(request)
+ validator
+ security
- try:
- scopes, credentials = self._authorization_endpoint.validate_authorization_request(
- uri, http_method, body, headers)
- # Not necessarily in session but they need to be
- # accessible in the POST view after form submit.
- request.session['oauth2_credentials'] = credentials
+3. Create your composite endpoint
+---------------------------------
- # You probably want to render a template instead.
- response = HttpResponse()
- response.write('<h1> Authorize access to %s </h1>' % client_id)
- response.write('<form method="POST" action="/authorize">')
- for scope in scopes or []:
- response.write('<input type="checkbox" name="scopes" ' +
- 'value="%s"/> %s' % (scope, scope))
- response.write('<input type="submit" value="Authorize"/>')
- return response
+Each of the endpoints can function independently from each other, however
+for this example it is easier to consider them as one unit. An example of a
+pre-configured all-in-one Authorization Code Grant endpoint is given below.
- # Errors that should be shown to the user on the provider website
- except errors.FatalClientError as e:
- return response_from_error(e)
+.. code-block:: python
- # Errors embedded in the redirect URI back to the client
- except errors.OAuth2Error as e:
- return HttpResponseRedirect(e.in_uri(e.redirect_uri))
+ # From the previous section on validators
+ from my_validator import MyRequestValidator
- @csrf_exempt
- def post(self, request):
- uri, http_method, body, headers = extract_params(request)
+ from oauthlib.oauth2 import WebApplicationServer
- # The scopes the user actually authorized, i.e. checkboxes
- # that were selected.
- scopes = request.POST.getlist(['scopes'])
+ validator = MyRequestValidator()
+ server = WebApplicationServer(validator)
- # Extra credentials we need in the validator
- credentials = {'user': request.user}
+Relevant sections include:
- # The previously stored (in authorization GET view) credentials
- credentials.update(request.session.get('oauth2_credentials', {}))
+.. toctree::
+ :maxdepth: 1
- try:
- headers, body, status = self._authorization_endpoint.create_authorization_response(
- uri, http_method, body, headers, scopes, credentials)
- return response_from_return(headers, body, status)
+ preconfigured_servers
- except errors.FatalClientError as e:
- return response_from_error(e)
- # Handles requests to /token
- class TokenView(View):
+4. Create your endpoint views
+-----------------------------
- def __init__(self):
- # Using the server from previous section
- self._token_endpoint = server
+We are implementing support for the Authorization Code Grant and will
+therefore need two views for the authorization, pre- and post-authorization
+together with the token view. We also include an error page to redirect
+users to if the client supplied invalid credentials in their redirection,
+for example an invalid redirect URI.
- def post(self, request):
- uri, http_method, body, headers = extract_params(request)
+The example using Django but should be transferable to any framework.
- # If you wish to include request specific extra credentials for
- # use in the validator, do so here.
- credentials = {'foo': 'bar'}
+.. code-block:: python
- headers, body, status = self._token_endpoint.create_token_response(
- uri, http_method, body, headers, credentials)
+ # Handles GET and POST requests to /authorize
+ class AuthorizationView(View):
- # All requests to /token will return a json response, no redirection.
- return response_from_return(headers, body, status)
+ def __init__(self):
+ # Using the server from previous section
+ self._authorization_endpoint = server
+
+ def get(self, request):
+ # You need to define extract_params and make sure it does not
+ # include file like objects waiting for input. In Django this
+ # is request.META['wsgi.input'] and request.META['wsgi.errors']
+ uri, http_method, body, headers = extract_params(request)
+
+ try:
+ scopes, credentials = self._authorization_endpoint.validate_authorization_request(
+ uri, http_method, body, headers)
+
+ # Not necessarily in session but they need to be
+ # accessible in the POST view after form submit.
+ request.session['oauth2_credentials'] = credentials
+
+ # You probably want to render a template instead.
+ response = HttpResponse()
+ response.write('<h1> Authorize access to %s </h1>' % client_id)
+ response.write('<form method="POST" action="/authorize">')
+ for scope in scopes or []:
+ response.write('<input type="checkbox" name="scopes" ' +
+ 'value="%s"/> %s' % (scope, scope))
+ response.write('<input type="submit" value="Authorize"/>')
+ return response
+
+ # Errors that should be shown to the user on the provider website
+ except errors.FatalClientError as e:
+ return response_from_error(e)
+
+ # Errors embedded in the redirect URI back to the client
+ except errors.OAuth2Error as e:
+ return HttpResponseRedirect(e.in_uri(e.redirect_uri))
+
+ @csrf_exempt
+ def post(self, request):
+ uri, http_method, body, headers = extract_params(request)
+
+ # The scopes the user actually authorized, i.e. checkboxes
+ # that were selected.
+ scopes = request.POST.getlist(['scopes'])
+
+ # Extra credentials we need in the validator
+ credentials = {'user': request.user}
+
+ # The previously stored (in authorization GET view) credentials
+ credentials.update(request.session.get('oauth2_credentials', {}))
+
+ try:
+ headers, body, status = self._authorization_endpoint.create_authorization_response(
+ uri, http_method, body, headers, scopes, credentials)
+ return response_from_return(headers, body, status)
+
+ except errors.FatalClientError as e:
+ return response_from_error(e)
+
+ # Handles requests to /token
+ class TokenView(View):
+
+ def __init__(self):
+ # Using the server from previous section
+ self._token_endpoint = server
+
+ def post(self, request):
+ uri, http_method, body, headers = extract_params(request)
+
+ # If you wish to include request specific extra credentials for
+ # use in the validator, do so here.
+ credentials = {'foo': 'bar'}
+
+ headers, body, status = self._token_endpoint.create_token_response(
+ uri, http_method, body, headers, credentials)
+
+ # All requests to /token will return a json response, no redirection.
+ return response_from_return(headers, body, status)
def response_from_return(headers, body, status):
response = HttpResponse(content=body, status=status)
@@ -345,83 +415,91 @@ as well as provide an interface for a backend to store tokens, clients, etc.
return HttpResponseBadRequest('Evil client is unable to send a proper request. Error is: ' + e.description)
+5. Protect your APIs using scopes
+---------------------------------
-**5. Protect your APIs using scopes**
+Let's define a decorator we can use to protect the views.
- Let's define a decorator we can use to protect the views.
+.. code-block:: python
- .. code-block:: python
+ class OAuth2ProviderDecorator(object):
+
+ def __init__(self, resource_endpoint):
+ self._resource_endpoint = resource_endpoint
+
+ def protected_resource_view(self, scopes=None):
+ def decorator(f):
+ @functools.wraps(f)
+ def wrapper(request):
+ # Get the list of scopes
+ try:
+ scopes_list = scopes(request)
+ except TypeError:
+ scopes_list = scopes
+
+ uri, http_method, body, headers = extract_params(request)
+
+ valid, r = self._resource_endpoint.verify_request(
+ uri, http_method, body, headers, scopes_list)
+
+ # For convenient parameter access in the view
+ add_params(request, {
+ 'client': r.client,
+ 'user': r.user,
+ 'scopes': r.scopes
+ })
+ if valid:
+ return f(request)
+ else:
+ # Framework specific HTTP 403
+ return HttpResponseForbidden()
+ return wrapper
+ return decorator
+
+ provider = OAuth2ProviderDecorator(server)
+
+At this point you are ready to protect your API views with OAuth. Take some
+time to come up with a good set of scopes as they can be very powerful in
+controlling access.
+
+.. code-block:: python
+
+ @provider.protected_resource_view(scopes=['images'])
+ def i_am_protected(request, client, resource_owner):
+ # One of your many OAuth 2 protected resource views
+ # Returns whatever you fancy
+ # May be bound to various scopes of your choosing
+ return HttpResponse('pictures of cats')
+
+The set of scopes that protects a view may also be dynamically configured
+at runtime by a function, rather then by a list.
+
+.. code-block:: python
+
+ def dynamic_scopes(request):
+ # Place code here to dynamically determine the scopes
+ # and return as a list
+ return ['images']
+
+ @provider.protected_resource_view(scopes=dynamic_scopes)
+ def i_am_also_protected(request, client, resource_owner, **kwargs)
+ # A view that has its views functionally set.
+ return HttpResponse('pictures of cats')
+
+6. Let us know how it went!
+---------------------------
+
+Drop a line in our `G+ community`_ or open a `GitHub issue`_ =)
+
+.. _`G+ community`: https://plus.google.com/communities/101889017375384052571
+.. _`GitHub issue`: https://github.com/idan/oauthlib/issues/new
+
+If you run into issues it can be helpful to enable debug logging.
+
+.. code-block:: python
- class OAuth2ProviderDecorator(object):
-
- def __init__(self, resource_endpoint):
- self._resource_endpoint = resource_endpoint
-
- def protected_resource_view(self, scopes=None):
- def decorator(f):
- @functools.wraps(f)
- def wrapper(request):
- # Get the list of scopes
- try:
- scopes_list = scopes(request)
- except TypeError:
- scopes_list = scopes
-
- uri, http_method, body, headers = extract_params(request)
-
- valid, r = self._resource_endpoint.verify_request(
- uri, http_method, body, headers, scopes_list)
-
- # For convenient parameter access in the view
- add_params(request, {
- 'client': r.client,
- 'user': r.user,
- 'scopes': r.scopes
- })
- if valid:
- return f(request)
- else:
- # Framework specific HTTP 403
- return HttpResponseForbidden()
- return wrapper
- return decorator
-
- provider = OAuth2ProviderDecorator(server)
-
- At this point you are ready to protect your API views with OAuth. Take some
- time to come up with a good set of scopes as they can be very powerful in
- controlling access::
-
- @provider.protected_resource_view(scopes=['images'])
- def i_am_protected(request, client, resource_owner, **kwargs):
- # One of your many OAuth 2 protected resource views
- # Returns whatever you fancy
- # May be bound to various scopes of your choosing
- return HttpResponse('pictures of cats')
-
- The set of scopes that protects a view may also be dynamically configured
- at runtime by a function, rather then by a list::
-
- def dynamic_scopes(request):
- # Place code here to dynamically determine the scopes
- # and return as a list
- return ['images']
-
- @provider.protected_resource_view(scopes=dynamic_scopes)
- def i_am_also_protected(request, client, resource_owner, **kwargs)
- # A view that has its views functionally set.
- return HttpResponse('pictures of cats')
-
-**6. Let us know how it went!**
-
- Drop a line in our `G+ community`_ or open a `GitHub issue`_ =)
-
- .. _`G+ community`: https://plus.google.com/communities/101889017375384052571
- .. _`GitHub issue`: https://github.com/idan/oauthlib/issues/new
-
- If you run into issues it can be helpful to enable debug logging::
-
- import logging
- log = logging.getLogger('oauthlib')
- log.addHandler(logging.StreamHandler(sys.stdout))
- log.setLevel(logging.DEBUG)
+ import logging
+ import sys
+ log = logging.getLogger('oauthlib')
+ log.addHandler(logging.StreamHandler(sys.stdout))
+ log.setLevel(logging.DEBUG)
diff --git a/docs/oauth2/tokens/bearer.rst b/docs/oauth2/tokens/bearer.rst
new file mode 100644
index 0000000..8c6270d
--- /dev/null
+++ b/docs/oauth2/tokens/bearer.rst
@@ -0,0 +1,13 @@
+=============
+Bearer Tokens
+=============
+
+The most common OAuth 2 token type. It provides very little in terms of security
+and relies heavily upon the ability of the client to keep the token secret.
+
+Bearer tokens are the default setting with all configured endpoints. Generally
+you will not need to ever construct a token yourself as the provided servers
+will do so for you.
+
+.. autoclass:: oauthlib.oauth2.BearerToken
+ :members:
diff --git a/docs/oauth2/tokens/jwt.rst b/docs/oauth2/tokens/jwt.rst
new file mode 100644
index 0000000..87aed11
--- /dev/null
+++ b/docs/oauth2/tokens/jwt.rst
@@ -0,0 +1,7 @@
+==========
+JWT Tokens
+==========
+
+Not yet implemented. Track progress in `GitHub issue 50`_.
+
+.. _`GitHub issue 50`: https://github.com/idan/oauthlib/issues/50
diff --git a/docs/oauth2/tokens/mac.rst b/docs/oauth2/tokens/mac.rst
new file mode 100644
index 0000000..4986819
--- /dev/null
+++ b/docs/oauth2/tokens/mac.rst
@@ -0,0 +1,8 @@
+==========
+MAC tokens
+==========
+
+Not yet implemented. Track progress in `GitHub issue 29`_. Might never be
+supported depending on whether the work on the specification is resumed or not.
+
+.. _`GitHub issue 29`: https://github.com/idan/oauthlib/issues/29
diff --git a/docs/oauth2/tokens/saml.rst b/docs/oauth2/tokens/saml.rst
new file mode 100644
index 0000000..9a00937
--- /dev/null
+++ b/docs/oauth2/tokens/saml.rst
@@ -0,0 +1,7 @@
+===========
+SAML Tokens
+===========
+
+Not yet implemented. Track progress in `GitHub issue 49`_.
+
+.. _`GitHub issue 49`: https://github.com/idan/oauthlib/issues/49
diff --git a/docs/oauth2/tokens/tokens.rst b/docs/oauth2/tokens/tokens.rst
new file mode 100644
index 0000000..3273f77
--- /dev/null
+++ b/docs/oauth2/tokens/tokens.rst
@@ -0,0 +1,18 @@
+======
+Tokens
+======
+
+The main token type of OAuth 2 is Bearer tokens and that is what OAuthLib
+currently supports. Other tokens, such as JWT, SAML and possibly MAC (if the
+spec matures) can easily be added (and will be in due time).
+
+The purpose of a token is to authorize access to protected resources to a client
+(i.e. your G+ feed).
+
+.. toctree::
+ :maxdepth: 2
+
+ bearer
+ saml
+ jwt
+ mac
diff --git a/oauthlib/oauth1/rfc5849/endpoints/access_token.py b/oauthlib/oauth1/rfc5849/endpoints/access_token.py
index ab953a4..870a675 100644
--- a/oauthlib/oauth1/rfc5849/endpoints/access_token.py
+++ b/oauthlib/oauth1/rfc5849/endpoints/access_token.py
@@ -22,7 +22,7 @@ class AccessTokenEndpoint(BaseEndpoint):
Typical use is to instantiate with a request validator and invoke the
``create_access_token_response`` from a view function. The tuple returned
has all information necessary (body, status, headers) to quickly form
- and return a proper response. See :doc:`validator` for details on which
+ and return a proper response. See :doc:`/oauth1/validator` for details on which
validator methods to implement for this endpoint.
"""
diff --git a/oauthlib/oauth1/rfc5849/endpoints/authorization.py b/oauthlib/oauth1/rfc5849/endpoints/authorization.py
index 5b78299..a7bbe29 100644
--- a/oauthlib/oauth1/rfc5849/endpoints/authorization.py
+++ b/oauthlib/oauth1/rfc5849/endpoints/authorization.py
@@ -33,7 +33,7 @@ class AuthorizationEndpoint(BaseEndpoint):
validate the request, create a verifier as well as prepare the final
redirection URI used to send the user back to the client.
- See :doc:`validator` for details on which validator methods to implement
+ See :doc:`/oauth1/validator` for details on which validator methods to implement
for this endpoint.
"""
diff --git a/oauthlib/oauth1/rfc5849/endpoints/request_token.py b/oauthlib/oauth1/rfc5849/endpoints/request_token.py
index 424e33f..5c598e1 100644
--- a/oauthlib/oauth1/rfc5849/endpoints/request_token.py
+++ b/oauthlib/oauth1/rfc5849/endpoints/request_token.py
@@ -22,7 +22,7 @@ class RequestTokenEndpoint(BaseEndpoint):
Typical use is to instantiate with a request validator and invoke the
``create_request_token_response`` from a view function. The tuple returned
has all information necessary (body, status, headers) to quickly form
- and return a proper response. See :doc:`validator` for details on which
+ and return a proper response. See :doc:`/oauth1/validator` for details on which
validator methods to implement for this endpoint.
"""
diff --git a/oauthlib/oauth1/rfc5849/endpoints/resource.py b/oauthlib/oauth1/rfc5849/endpoints/resource.py
index fe3295c..7718852 100644
--- a/oauthlib/oauth1/rfc5849/endpoints/resource.py
+++ b/oauthlib/oauth1/rfc5849/endpoints/resource.py
@@ -23,7 +23,7 @@ class ResourceEndpoint(BaseEndpoint):
view. If invalid create and return an error response directly from the
decorator.
- See :doc:`validator` for details on which validator methods to implement
+ See :doc:`/oauth1/validator` for details on which validator methods to implement
for this endpoint.
An example decorator::