authlib/oauth2/rfc6749/grants/implicit.py
import logging
from authlib.common.urls import add_params_to_uri
from .base import BaseGrant, AuthorizationEndpointMixin
from ..errors import (
OAuth2Error,
UnauthorizedClientError,
AccessDeniedError,
)
log = logging.getLogger(__name__)
class ImplicitGrant(BaseGrant, AuthorizationEndpointMixin):
"""The implicit grant type is used to obtain access tokens (it does not
support the issuance of refresh tokens) and is optimized for public
clients known to operate a particular redirection URI. These clients
are typically implemented in a browser using a scripting language
such as JavaScript.
Since this is a redirection-based flow, the client must be capable of
interacting with the resource owner's user-agent (typically a web
browser) and capable of receiving incoming requests (via redirection)
from the authorization server.
Unlike the authorization code grant type, in which the client makes
separate requests for authorization and for an access token, the
client receives the access token as the result of the authorization
request.
The implicit grant type does not include client authentication, and
relies on the presence of the resource owner and the registration of
the redirection URI. Because the access token is encoded into the
redirection URI, it may be exposed to the resource owner and other
applications residing on the same device::
+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-- & Redirection URI --->| |
| User- | | Authorization |
| Agent -|----(B)-- User authenticates -->| Server |
| | | |
| |<---(C)--- Redirection URI ----<| |
| | with Access Token +---------------+
| | in Fragment
| | +---------------+
| |----(D)--- Redirection URI ---->| Web-Hosted |
| | without Fragment | Client |
| | | Resource |
| (F) |<---(E)------- Script ---------<| |
| | +---------------+
+-|--------+
| |
(A) (G) Access Token
| |
^ v
+---------+
| |
| Client |
| |
+---------+
"""
#: authorization_code grant type has authorization endpoint
AUTHORIZATION_ENDPOINT = True
#: Allowed client auth methods for token endpoint
TOKEN_ENDPOINT_AUTH_METHODS = ['none']
RESPONSE_TYPES = {'token'}
GRANT_TYPE = 'implicit'
ERROR_RESPONSE_FRAGMENT = True
def validate_authorization_request(self):
"""The client constructs the request URI by adding the following
parameters to the query component of the authorization endpoint URI
using the "application/x-www-form-urlencoded" format.
Per `Section 4.2.1`_.
response_type
REQUIRED. Value MUST be set to "token".
client_id
REQUIRED. The client identifier as described in Section 2.2.
redirect_uri
OPTIONAL. As described in Section 3.1.2.
scope
OPTIONAL. The scope of the access request as described by
Section 3.3.
state
RECOMMENDED. An opaque value used by the client to maintain
state between the request and callback. The authorization
server includes this value when redirecting the user-agent back
to the client. The parameter SHOULD be used for preventing
cross-site request forgery as described in Section 10.12.
The client directs the resource owner to the constructed URI using an
HTTP redirection response, or by other means available to it via the
user-agent.
For example, the client directs the user-agent to make the following
HTTP request using TLS:
.. code-block:: http
GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com
.. _`Section 4.2.1`: https://tools.ietf.org/html/rfc6749#section-4.2.1
"""
# ignore validate for response_type, since it is validated by
# check_authorization_endpoint
# The implicit grant type is optimized for public clients
client = self.authenticate_token_endpoint_client()
log.debug('Validate authorization request of %r', client)
redirect_uri = self.validate_authorization_redirect_uri(
self.request, client)
response_type = self.request.response_type
if not client.check_response_type(response_type):
raise UnauthorizedClientError(
'The client is not authorized to use '
'"response_type={}"'.format(response_type),
state=self.request.state,
redirect_uri=redirect_uri,
redirect_fragment=True,
)
try:
self.request.client = client
self.validate_requested_scope()
self.execute_hook('after_validate_authorization_request')
except OAuth2Error as error:
error.redirect_uri = redirect_uri
error.redirect_fragment = True
raise error
return redirect_uri
def create_authorization_response(self, redirect_uri, grant_user):
"""If the resource owner grants the access request, the authorization
server issues an access token and delivers it to the client by adding
the following parameters to the fragment component of the redirection
URI using the "application/x-www-form-urlencoded" format.
Per `Section 4.2.2`_.
access_token
REQUIRED. The access token issued by the authorization server.
token_type
REQUIRED. The type of the token issued as described in
Section 7.1. Value is case insensitive.
expires_in
RECOMMENDED. The lifetime in seconds of the access token. For
example, the value "3600" denotes that the access token will
expire in one hour from the time the response was generated.
If omitted, the authorization server SHOULD provide the
expiration time via other means or document the default value.
scope
OPTIONAL, if identical to the scope requested by the client;
otherwise, REQUIRED. The scope of the access token as
described by Section 3.3.
state
REQUIRED if the "state" parameter was present in the client
authorization request. The exact value received from the
client.
The authorization server MUST NOT issue a refresh token.
For example, the authorization server redirects the user-agent by
sending the following HTTP response:
.. code-block:: http
HTTP/1.1 302 Found
Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
&state=xyz&token_type=example&expires_in=3600
Developers should note that some user-agents do not support the
inclusion of a fragment component in the HTTP "Location" response
header field. Such clients will require using other methods for
redirecting the client than a 3xx redirection response -- for
example, returning an HTML page that includes a 'continue' button
with an action linked to the redirection URI.
.. _`Section 4.2.2`: https://tools.ietf.org/html/rfc6749#section-4.2.2
:param redirect_uri: Redirect to the given URI for the authorization
:param grant_user: if resource owner granted the request, pass this
resource owner, otherwise pass None.
:returns: (status_code, body, headers)
"""
state = self.request.state
if grant_user:
self.request.user = grant_user
token = self.generate_token(
user=grant_user,
scope=self.request.scope,
include_refresh_token=False,
)
log.debug('Grant token %r to %r', token, self.request.client)
self.save_token(token)
self.execute_hook('process_token', token=token)
params = [(k, token[k]) for k in token]
if state:
params.append(('state', state))
uri = add_params_to_uri(redirect_uri, params, fragment=True)
headers = [('Location', uri)]
return 302, '', headers
else:
raise AccessDeniedError(
state=state,
redirect_uri=redirect_uri,
redirect_fragment=True
)