summaryrefslogtreecommitdiff
path: root/docs/oauth2/endpoints.rst
blob: 8ce433fc19d0ffe42870b32fb2ce63ed761f3eef (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
=================
OAuth 2 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.

-------------
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::

        # 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::

        # 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:

--------------
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::

        # 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:

---------------------------
Authorizing resource access
---------------------------

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::

        # 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:


----------------
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: