1 # -*- coding: utf-8 -*-
3 oauthlib.oauth2.rfc6749.grant_types
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6 from __future__ import unicode_literals, absolute_import
11 from oauthlib import common
12 from oauthlib.uri_validate import is_absolute_uri
14 from .base import GrantTypeBase
16 from ..request_validator import RequestValidator
18 log = logging.getLogger(__name__)
21 class AuthorizationCodeGrant(GrantTypeBase):
23 """`Authorization Code Grant`_
25 The authorization code grant type is used to obtain both access
26 tokens and refresh tokens and is optimized for confidential clients.
27 Since this is a redirection-based flow, the client must be capable of
28 interacting with the resource owner's user-agent (typically a web
29 browser) and capable of receiving incoming requests (via redirection)
30 from the authorization server::
40 +----|-----+ Client Identifier +---------------+
41 | -+----(A)-- & Redirection URI ---->| |
42 | User- | | Authorization |
43 | Agent -+----(B)-- User authenticates --->| Server |
45 | -+----(C)-- Authorization Code ---<| |
46 +-|----|---+ +---------------+
52 | |>---(D)-- Authorization Code ---------' |
53 | Client | & Redirection URI |
55 | |<---(E)----- Access Token -------------------'
56 +---------+ (w/ Optional Refresh Token)
58 Note: The lines illustrating steps (A), (B), and (C) are broken into
59 two parts as they pass through the user-agent.
61 Figure 3: Authorization Code Flow
63 The flow illustrated in Figure 3 includes the following steps:
65 (A) The client initiates the flow by directing the resource owner's
66 user-agent to the authorization endpoint. The client includes
67 its client identifier, requested scope, local state, and a
68 redirection URI to which the authorization server will send the
69 user-agent back once access is granted (or denied).
71 (B) The authorization server authenticates the resource owner (via
72 the user-agent) and establishes whether the resource owner
73 grants or denies the client's access request.
75 (C) Assuming the resource owner grants access, the authorization
76 server redirects the user-agent back to the client using the
77 redirection URI provided earlier (in the request or during
78 client registration). The redirection URI includes an
79 authorization code and any local state provided by the client
82 (D) The client requests an access token from the authorization
83 server's token endpoint by including the authorization code
84 received in the previous step. When making the request, the
85 client authenticates with the authorization server. The client
86 includes the redirection URI used to obtain the authorization
87 code for verification.
89 (E) The authorization server authenticates the client, validates the
90 authorization code, and ensures that the redirection URI
91 received matches the URI used to redirect the client in
92 step (C). If valid, the authorization server responds back with
93 an access token and, optionally, a refresh token.
95 .. _`Authorization Code Grant`: http://tools.ietf.org/html/rfc6749#section-4.1
98 def __init__(self, request_validator=None):
99 self.request_validator = request_validator or RequestValidator()
101 def create_authorization_code(self, request):
102 """Generates an authorization grant represented as a dictionary."""
103 grant = {'code': common.generate_token()}
104 if hasattr(request, 'state') and request.state:
105 grant['state'] = request.state
106 log.debug('Created authorization code grant %r for request %r.',
110 def create_authorization_response(self, request, token_handler):
112 The client constructs the request URI by adding the following
113 parameters to the query component of the authorization endpoint URI
114 using the "application/x-www-form-urlencoded" format, per `Appendix B`_:
117 REQUIRED. Value MUST be set to "code".
119 REQUIRED. The client identifier as described in `Section 2.2`_.
121 OPTIONAL. As described in `Section 3.1.2`_.
123 OPTIONAL. The scope of the access request as described by
126 RECOMMENDED. An opaque value used by the client to maintain
127 state between the request and callback. The authorization
128 server includes this value when redirecting the user-agent back
129 to the client. The parameter SHOULD be used for preventing
130 cross-site request forgery as described in `Section 10.12`_.
132 The client directs the resource owner to the constructed URI using an
133 HTTP redirection response, or by other means available to it via the
136 :param request: oauthlib.commong.Request
137 :param token_handler: A token handler instace, for example of type
138 oauthlib.oauth2.BearerToken.
139 :returns: headers, body, status
140 :raises: FatalClientError on invalid redirect URI or client id.
141 ValueError if scopes are not set on the request object.
145 >>> from your_validator import your_validator
146 >>> request = Request('https://example.com/authorize?client_id=valid'
147 ... '&redirect_uri=http%3A%2F%2Fclient.com%2F')
148 >>> from oauthlib.common import Request
149 >>> from oauthlib.oauth2 import AuthorizationCodeGrant, BearerToken
150 >>> token = BearerToken(your_validator)
151 >>> grant = AuthorizationCodeGrant(your_validator)
152 >>> grant.create_authorization_response(request, token)
153 Traceback (most recent call last):
154 File "<stdin>", line 1, in <module>
155 File "oauthlib/oauth2/rfc6749/grant_types.py", line 513, in create_authorization_response
156 raise ValueError('Scopes must be set on post auth.')
157 ValueError: Scopes must be set on post auth.
158 >>> request.scopes = ['authorized', 'in', 'some', 'form']
159 >>> grant.create_authorization_response(request, token)
160 (u'http://client.com/?error=invalid_request&error_description=Missing+response_type+parameter.', None, None, 400)
161 >>> request = Request('https://example.com/authorize?client_id=valid'
162 ... '&redirect_uri=http%3A%2F%2Fclient.com%2F'
163 ... '&response_type=code')
164 >>> request.scopes = ['authorized', 'in', 'some', 'form']
165 >>> grant.create_authorization_response(request, token)
166 (u'http://client.com/?code=u3F05aEObJuP2k7DordviIgW5wl52N', None, None, 200)
167 >>> # If the client id or redirect uri fails validation
168 >>> grant.create_authorization_response(request, token)
169 Traceback (most recent call last):
170 File "<stdin>", line 1, in <module>
171 File "oauthlib/oauth2/rfc6749/grant_types.py", line 515, in create_authorization_response
172 >>> grant.create_authorization_response(request, token)
173 File "oauthlib/oauth2/rfc6749/grant_types.py", line 591, in validate_authorization_request
174 oauthlib.oauth2.rfc6749.errors.InvalidClientIdError
176 .. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
177 .. _`Section 2.2`: http://tools.ietf.org/html/rfc6749#section-2.2
178 .. _`Section 3.1.2`: http://tools.ietf.org/html/rfc6749#section-3.1.2
179 .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
180 .. _`Section 10.12`: http://tools.ietf.org/html/rfc6749#section-10.12
183 # request.scopes is only mandated in post auth and both pre and
184 # post auth use validate_authorization_request
185 if not request.scopes:
186 raise ValueError('Scopes must be set on post auth.')
188 self.validate_authorization_request(request)
189 log.debug('Pre resource owner authorization validation ok for %r.',
192 # If the request fails due to a missing, invalid, or mismatching
193 # redirection URI, or if the client identifier is missing or invalid,
194 # the authorization server SHOULD inform the resource owner of the
195 # error and MUST NOT automatically redirect the user-agent to the
196 # invalid redirection URI.
197 except errors.FatalClientError as e:
198 log.debug('Fatal client error during validation of %r. %r.',
202 # If the resource owner denies the access request or if the request
203 # fails for reasons other than a missing or invalid redirection URI,
204 # the authorization server informs the client by adding the following
205 # parameters to the query component of the redirection URI using the
206 # "application/x-www-form-urlencoded" format, per Appendix B:
207 # http://tools.ietf.org/html/rfc6749#appendix-B
208 except errors.OAuth2Error as e:
209 log.debug('Client error during validation of %r. %r.', request, e)
210 request.redirect_uri = request.redirect_uri or self.error_uri
211 return {'Location': common.add_params_to_uri(request.redirect_uri, e.twotuples)}, None, 302
213 grant = self.create_authorization_code(request)
214 log.debug('Saving grant %r for %r.', grant, request)
215 self.request_validator.save_authorization_code(
216 request.client_id, grant, request)
217 return {'Location': common.add_params_to_uri(request.redirect_uri, grant.items())}, None, 302
219 def create_token_response(self, request, token_handler):
220 """Validate the authorization code.
222 The client MUST NOT use the authorization code more than once. If an
223 authorization code is used more than once, the authorization server
224 MUST deny the request and SHOULD revoke (when possible) all tokens
225 previously issued based on that authorization code. The authorization
226 code is bound to the client identifier and redirection URI.
229 'Content-Type': 'application/json',
230 'Cache-Control': 'no-store',
231 'Pragma': 'no-cache',
234 self.validate_token_request(request)
235 log.debug('Token request validation ok for %r.', request)
236 except errors.OAuth2Error as e:
237 log.debug('Client error during validation of %r. %r.', request, e)
238 return headers, e.json, e.status_code
240 token = token_handler.create_token(request, refresh_token=True)
241 self.request_validator.invalidate_authorization_code(
242 request.client_id, request.code, request)
243 return headers, json.dumps(token), 200
245 def validate_authorization_request(self, request):
246 """Check the authorization request for normal and fatal errors.
248 A normal error could be a missing response_type parameter or the client
249 attempting to access scope it is not allowed to ask authorization for.
250 Normal errors can safely be included in the redirection URI and
251 sent back to the client.
253 Fatal errors occur when the client_id or redirect_uri is invalid or
254 missing. These must be caught by the provider and handled, how this
255 is done is outside of the scope of OAuthLib but showing an error
256 page describing the issue is a good idea.
259 # First check for fatal errors
261 # If the request fails due to a missing, invalid, or mismatching
262 # redirection URI, or if the client identifier is missing or invalid,
263 # the authorization server SHOULD inform the resource owner of the
264 # error and MUST NOT automatically redirect the user-agent to the
265 # invalid redirection URI.
267 # First check duplicate parameters
268 for param in ('client_id', 'response_type', 'redirect_uri', 'scope', 'state'):
270 duplicate_params = request.duplicate_params
272 raise errors.InvalidRequestFatalError(description='Unable to parse query string', request=request)
273 if param in duplicate_params:
274 raise errors.InvalidRequestFatalError(description='Duplicate %s parameter.' % param, request=request)
276 # REQUIRED. The client identifier as described in Section 2.2.
277 # http://tools.ietf.org/html/rfc6749#section-2.2
278 if not request.client_id:
279 raise errors.MissingClientIdError(request=request)
281 if not self.request_validator.validate_client_id(request.client_id, request):
282 raise errors.InvalidClientIdError(request=request)
284 # OPTIONAL. As described in Section 3.1.2.
285 # http://tools.ietf.org/html/rfc6749#section-3.1.2
286 log.debug('Validating redirection uri %s for client %s.',
287 request.redirect_uri, request.client_id)
288 if request.redirect_uri is not None:
289 request.using_default_redirect_uri = False
290 log.debug('Using provided redirect_uri %s', request.redirect_uri)
291 if not is_absolute_uri(request.redirect_uri):
292 raise errors.InvalidRedirectURIError(request=request)
294 if not self.request_validator.validate_redirect_uri(
295 request.client_id, request.redirect_uri, request):
296 raise errors.MismatchingRedirectURIError(request=request)
298 request.redirect_uri = self.request_validator.get_default_redirect_uri(
299 request.client_id, request)
300 request.using_default_redirect_uri = True
301 log.debug('Using default redirect_uri %s.', request.redirect_uri)
302 if not request.redirect_uri:
303 raise errors.MissingRedirectURIError(request=request)
305 # Then check for normal errors.
307 # If the resource owner denies the access request or if the request
308 # fails for reasons other than a missing or invalid redirection URI,
309 # the authorization server informs the client by adding the following
310 # parameters to the query component of the redirection URI using the
311 # "application/x-www-form-urlencoded" format, per Appendix B.
312 # http://tools.ietf.org/html/rfc6749#appendix-B
314 # Note that the correct parameters to be added are automatically
315 # populated through the use of specific exceptions.
318 if request.response_type is None:
319 raise errors.MissingResponseTypeError(request=request)
320 # Value MUST be set to "code".
321 elif request.response_type != 'code':
322 raise errors.UnsupportedResponseTypeError(request=request)
324 if not self.request_validator.validate_response_type(request.client_id,
325 request.response_type,
326 request.client, request):
328 log.debug('Client %s is not authorized to use response_type %s.',
329 request.client_id, request.response_type)
330 raise errors.UnauthorizedClientError(request=request)
332 # OPTIONAL. The scope of the access request as described by Section 3.3
333 # http://tools.ietf.org/html/rfc6749#section-3.3
334 self.validate_scopes(request)
336 return request.scopes, {
337 'client_id': request.client_id,
338 'redirect_uri': request.redirect_uri,
339 'response_type': request.response_type,
340 'state': request.state,
344 def validate_token_request(self, request):
345 # REQUIRED. Value MUST be set to "authorization_code".
346 if request.grant_type != 'authorization_code':
347 raise errors.UnsupportedGrantTypeError(request=request)
349 if request.code is None:
350 raise errors.InvalidRequestError(
351 description='Missing code parameter.', request=request)
353 for param in ('client_id', 'grant_type', 'redirect_uri'):
354 if param in request.duplicate_params:
355 raise errors.InvalidRequestError(description='Duplicate %s parameter.' % param,
358 if self.request_validator.client_authentication_required(request):
359 # If the client type is confidential or the client was issued client
360 # credentials (or assigned other authentication requirements), the
361 # client MUST authenticate with the authorization server as described
363 # http://tools.ietf.org/html/rfc6749#section-3.2.1
364 if not self.request_validator.authenticate_client(request):
365 log.debug('Client authentication failed, %r.', request)
366 raise errors.InvalidClientError(request=request)
367 elif not self.request_validator.authenticate_client_id(request.client_id, request):
368 # REQUIRED, if the client is not authenticating with the
369 # authorization server as described in Section 3.2.1.
370 # http://tools.ietf.org/html/rfc6749#section-3.2.1
371 log.debug('Client authentication failed, %r.', request)
372 raise errors.InvalidClientError(request=request)
374 if not hasattr(request.client, 'client_id'):
375 raise NotImplementedError('Authenticate client must set the '
376 'request.client.client_id attribute '
377 'in authenticate_client.')
379 # Ensure client is authorized use of this grant type
380 self.validate_grant_type(request)
382 # REQUIRED. The authorization code received from the
383 # authorization server.
384 if not self.request_validator.validate_code(request.client_id,
385 request.code, request.client, request):
386 log.debug('Client, %r (%r), is not allowed access to scopes %r.',
387 request.client_id, request.client, request.scopes)
388 raise errors.InvalidGrantError(request=request)
390 for attr in ('user', 'scopes'):
391 if getattr(request, attr, None) is None:
392 log.debug('request.%s was not set on code validation.', attr)
394 # REQUIRED, if the "redirect_uri" parameter was included in the
395 # authorization request as described in Section 4.1.1, and their
396 # values MUST be identical.
397 if not self.request_validator.confirm_redirect_uri(request.client_id, request.code,
398 request.redirect_uri, request.client):
399 log.debug('Redirect_uri (%r) invalid for client %r (%r).',
400 request.redirect_uri, request.client_id, request.client)
401 raise errors.AccessDeniedError(request=request)