Skip to content

Commit

Permalink
Use the order of values in JWT_TOKEN_LOCATION to set order of precede…
Browse files Browse the repository at this point in the history
…nce (#256)

* Use the order of values in JWT_TOKEN_LOCATION to set order of precedence

* Pep line length to make github happy

* Remove redundant config check in locations loop
  • Loading branch information
stephendwolff authored and vimalloc committed Jul 3, 2019
1 parent d4a34e5 commit e1d51a5
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 12 deletions.
4 changes: 3 additions & 1 deletion docs/options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ General Options:
``JWT_TOKEN_LOCATION`` Where to look for a JWT when processing a request. The
options are ``'headers'``, ``'cookies'``, ``'query_string'``, or ``'json'``. You can pass
in a sequence or a set to check more then one location, such as:
``('headers', 'cookies')``. Defaults to ``['headers']``
``('headers', 'cookies')``. Defaults to ``['headers']``.
The order sets the precedence, so that if a valid token is
found in an earlier location in this list, the request is authenticated.
``JWT_ACCESS_TOKEN_EXPIRES`` How long an access token should live before it expires. This
takes any value that can be safely added to a ``datetime.datetime`` object, including
``datetime.timedelta``, `dateutil.relativedelta <https://dateutil.readthedocs.io/en/stable/relativedelta.html>`_,
Expand Down
23 changes: 15 additions & 8 deletions flask_jwt_extended/view_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,14 +247,21 @@ def _decode_jwt_from_json(request_type):
def _decode_jwt_from_request(request_type):
# All the places we can get a JWT from in this request
get_encoded_token_functions = []
if config.jwt_in_cookies:
get_encoded_token_functions.append(lambda: _decode_jwt_from_cookies(request_type))
if config.jwt_in_query_string:
get_encoded_token_functions.append(_decode_jwt_from_query_string)
if config.jwt_in_headers:
get_encoded_token_functions.append(_decode_jwt_from_headers)
if config.jwt_in_json:
get_encoded_token_functions.append(lambda: _decode_jwt_from_json(request_type))

locations = config.token_location

# add the functions in the order specified in JWT_TOKEN_LOCATION
for location in locations:
if location == 'cookies':
get_encoded_token_functions.append(
lambda: _decode_jwt_from_cookies(request_type))
if location == 'query_string':
get_encoded_token_functions.append(_decode_jwt_from_query_string)
if location == 'headers':
get_encoded_token_functions.append(_decode_jwt_from_headers)
if location == 'json':
get_encoded_token_functions.append(
lambda: _decode_jwt_from_json(request_type))

# Try to find the token from one of these locations. It only needs to exist
# in one place to be valid (not every location).
Expand Down
51 changes: 48 additions & 3 deletions tests/test_multiple_token_locations.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ def test_json_access(app):
@pytest.mark.parametrize("options", [
(['cookies', 'headers'], ('Missing JWT in cookies or headers (Missing cookie '
'"access_token_cookie"; Missing Authorization Header)')),
(['json', 'query_string'], ('Missing JWT in json or query_string (Missing "jwt" '
'query paramater; Invalid content-type. Must be '
'application/json.)')),
(['json', 'query_string'], ('Missing JWT in json or query_string (Invalid '
'content-type. Must be application/json.; '
'Missing "jwt" query paramater)')),
])
def test_no_jwt_in_request(app, options):
token_locations, expected_err = options
Expand All @@ -84,3 +84,48 @@ def test_no_jwt_in_request(app, options):
response = test_client.get('/protected')
assert response.status_code == 401
assert response.get_json() == {'msg': expected_err}


@pytest.mark.parametrize("options", [
(['cookies', 'headers'], 200, None, {'foo': 'bar'}),
(['headers', 'cookies'], 200, None, {'foo': 'bar'}),
])
def test_order_of_jwt_locations_in_request(app, options):
""" test order doesn't matter if at least one valid token is set"""
token_locations, status_code, expected_err, expected_dict = options
app.config['JWT_TOKEN_LOCATION'] = token_locations
test_client = app.test_client()
test_client.get('/cookie_login')
response = test_client.get('/protected')

assert response.status_code == status_code
if expected_dict:
assert response.get_json() == expected_dict
else:
assert response.get_json() == {'msg': expected_err}


@pytest.mark.parametrize("options", [
(['cookies', 'headers'], 200, None, {'foo': 'bar'}),
(['headers', 'cookies'], 422, ('Invalid header padding'), None),
])
def test_order_of_jwt_locations_with_one_invalid_token_in_request(app, options):
""" test order doesn't matter if at least one valid token is set"""
token_locations, status_code, expected_err, expected_dict = options
app.config['JWT_TOKEN_LOCATION'] = token_locations
test_client = app.test_client()

with app.test_request_context():
access_token = create_access_token('username')
# invalidate the token, to check token location precedence
access_token = "000000{}".format(access_token[5:])
access_headers = {'Authorization': 'Bearer {}'.format(access_token)}
# set valid cookies
test_client.get('/cookie_login')
response = test_client.get('/protected', headers=access_headers)

assert response.status_code == status_code
if expected_dict:
assert response.get_json() == expected_dict
else:
assert response.get_json() == {'msg': expected_err}

0 comments on commit e1d51a5

Please sign in to comment.