Skip to content

Commit b077a73

Browse files
committed
Allow change of header name and type, cookie header initial commit
1 parent 9056b88 commit b077a73

File tree

4 files changed

+84
-37
lines changed

4 files changed

+84
-37
lines changed

flask_jwt_extended/config.py

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,67 @@
11
import datetime
22
from flask import current_app
33

4-
# Defaults
4+
# TODO move this to the docs
5+
# blacklist storage options (simplekv). If using a storage option that supports
6+
# the simplekv.TimeToLiveMixin (example: redis, memcached), the TTL will be
7+
# automatically set to 15 minutes after the token expires (to account for
8+
# clock drift between different jwt providers/consumers).
9+
#
10+
# See: http://pythonhosted.org/simplekv/index.html#simplekv.TimeToLiveMixin
511

6-
# Authorize header type, what we are expecting to see in the auth header
7-
AUTH_HEADER = 'Bearer'
812

9-
# How long an access token will live before it expires.
10-
ACCESS_TOKEN_EXPIRES = datetime.timedelta(minutes=15)
13+
# Where to look for the JWT. Available options are cookie and header
14+
REQUEST_JWT_LOCATION = 'header'
15+
16+
# Options for where to get the JWT if using a header approach
17+
HEADER_NAME = 'Authorization'
18+
HEADER_TYPE = 'Bearer'
19+
20+
# Options for where to get and handling JWTs if using a cookie approach
21+
COOKIE_ACCESS_TOKEN_NAME = 'access_token'
22+
COOKIE_REFRESH_TOKEN_NAME = 'refresh_token'
23+
COOKIE_CSRF_DOUBLE_SUBMIT = False
24+
COOKIE_XSRF_ACCESS_NAME = 'xsrf_access_token'
25+
COOKIE_XSRF_REFRESH_NAME = 'xsrf_refresh_token'
1126

12-
# How long the refresh token will live before it expires
27+
# How long an a token will live before they expire.
28+
ACCESS_TOKEN_EXPIRES = datetime.timedelta(minutes=15)
1329
REFRESH_TOKEN_EXPIRES = datetime.timedelta(days=30)
1430

1531
# What algorithm to use to sign the token. See here for a list of options:
16-
# https://github.com/jpadilla/pyjwt/blob/master/jwt/api_jwt.py
32+
# https://github.com/jpadilla/pyjwt/blob/master/jwt/api_jwt.py (note that
33+
# public private key is not yet supported)
1734
ALGORITHM = 'HS256'
1835

19-
# Blacklist enabled
36+
# Options for blacklisting/revoking tokens
2037
BLACKLIST_ENABLED = False
21-
22-
# blacklist storage options (simplekv). If using a storage option that supports
23-
# the simplekv.TimeToLiveMixin (example: redis, memcached), the TTL will be
24-
# automatically set to 15 minutes after the token expires (to account for
25-
# clock drift between different jwt providers/consumers).
26-
#
27-
# See: http://pythonhosted.org/simplekv/index.html#simplekv.TimeToLiveMixin
2838
BLACKLIST_STORE = None
39+
BLACKLIST_TOKEN_CHECKS = 'refresh' # valid options are 'all', and 'refresh'
40+
2941

30-
# blacklist check requests. Possible values are all and refresh
31-
BLACKLIST_TOKEN_CHECKS = 'refresh'
42+
def get_jwt_header_name():
43+
name = current_app.config.get('JWT_HEADER_NAME', HEADER_NAME)
44+
if not name:
45+
raise RuntimeError("JWT_HEADER_NAME must be set")
46+
return name
3247

3348

34-
def get_auth_header():
35-
return current_app.config.get('JWT_AUTH_HEADER', AUTH_HEADER)
49+
def get_jwt_header_type():
50+
return current_app.config.get('JWT_HEADER_TYPE', HEADER_TYPE)
3651

3752

3853
def get_access_expires():
39-
return current_app.config.get('JWT_ACCESS_TOKEN_EXPIRES', ACCESS_TOKEN_EXPIRES)
54+
delta = current_app.config.get('JWT_ACCESS_TOKEN_EXPIRES', ACCESS_TOKEN_EXPIRES)
55+
if not isinstance(delta, datetime.timedelta):
56+
raise RuntimeError('JWT_ACCESS_TOKEN_EXPIRES must be a datetime.timedelta')
57+
return delta
4058

4159

4260
def get_refresh_expires():
43-
return current_app.config.get('JWT_REFRESH_TOKEN_EXPIRES', REFRESH_TOKEN_EXPIRES)
61+
delta = current_app.config.get('JWT_REFRESH_TOKEN_EXPIRES', REFRESH_TOKEN_EXPIRES)
62+
if not isinstance(delta, datetime.timedelta):
63+
raise RuntimeError('JWT_REFRESH_TOKEN_EXPIRES must be a datetime.timedelta')
64+
return delta
4465

4566

4667
def get_algorithm():

flask_jwt_extended/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from flask import _request_ctx_stack as ctx_stack
1515

1616
from flask_jwt_extended.config import get_access_expires, get_refresh_expires, \
17-
get_algorithm, get_blacklist_enabled, get_blacklist_checks, get_auth_header
17+
get_algorithm, get_blacklist_enabled, get_blacklist_checks, get_jwt_header_type
1818
from flask_jwt_extended.exceptions import JWTEncodeError, JWTDecodeError, \
1919
InvalidHeaderError, NoAuthHeaderError, WrongTokenError, RevokedTokenError, \
2020
FreshTokenRequired
@@ -143,7 +143,7 @@ def _decode_jwt_from_request():
143143
raise NoAuthHeaderError("Missing Authorization Header")
144144

145145
# Make sure the header is valid
146-
expected_header = get_auth_header()
146+
expected_header = get_jwt_header_type()
147147
parts = auth_header.split()
148148
if not expected_header:
149149
if len(parts) != 1:

tests/test_config.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from flask_jwt_extended.config import get_access_expires, get_refresh_expires, \
88
get_algorithm, get_blacklist_enabled, get_blacklist_store, \
9-
get_blacklist_checks, get_auth_header
9+
get_blacklist_checks, get_jwt_header_type, get_jwt_header_name
1010
from flask_jwt_extended import JWTManager
1111

1212

@@ -26,7 +26,8 @@ def test_default_configs(self):
2626
self.assertEqual(get_blacklist_enabled(), False)
2727
self.assertEqual(get_blacklist_store(), None)
2828
self.assertEqual(get_blacklist_checks(), 'refresh')
29-
self.assertEqual(get_auth_header(), 'Bearer')
29+
self.assertEqual(get_jwt_header_name(), 'Authorization')
30+
self.assertEqual(get_jwt_header_type(), 'Bearer')
3031

3132
def test_override_configs(self):
3233
self.app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(minutes=5)
@@ -35,7 +36,8 @@ def test_override_configs(self):
3536
self.app.config['JWT_BLACKLIST_ENABLED'] = True
3637
self.app.config['JWT_BLACKLIST_STORE'] = simplekv.memory.DictStore()
3738
self.app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = 'all'
38-
self.app.config['JWT_AUTH_HEADER'] = 'JWT'
39+
self.app.config['JWT_HEADER_NAME'] = 'Auth'
40+
self.app.config['JWT_HEADER_TYPE'] = 'JWT'
3941

4042
with self.app.test_request_context():
4143
self.assertEqual(get_access_expires(), timedelta(minutes=5))
@@ -44,4 +46,18 @@ def test_override_configs(self):
4446
self.assertEqual(get_blacklist_enabled(), True)
4547
self.assertIsInstance(get_blacklist_store(), simplekv.memory.DictStore)
4648
self.assertEqual(get_blacklist_checks(), 'all')
47-
self.assertEqual(get_auth_header(), 'JWT')
49+
self.assertEqual(get_jwt_header_name(), 'Auth')
50+
self.assertEqual(get_jwt_header_type(), 'JWT')
51+
52+
self.app.config['JWT_HEADER_NAME'] = ''
53+
self.app.config['JWT_ACCESS_TOKEN_EXPIRES'] = 'banana'
54+
self.app.config['JWT_REFRESH_TOKEN_EXPIRES'] = 'banana'
55+
56+
self.app.testing = True # Propagate exceptions
57+
with self.app.test_request_context():
58+
with self.assertRaises(RuntimeError):
59+
get_jwt_header_name()
60+
with self.assertRaises(RuntimeError):
61+
get_access_expires()
62+
with self.assertRaises(RuntimeError):
63+
get_refresh_expires()

tests/test_protected_endpoints.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ def _jwt_post(self, url, jwt):
6262
data = json.loads(response.get_data(as_text=True))
6363
return status_code, data
6464

65-
def _jwt_get(self, url, jwt, auth_header='Bearer'):
66-
auth_header = '{} {}'.format(auth_header, jwt).strip()
67-
response = self.client.get(url, headers={'Authorization': auth_header})
65+
def _jwt_get(self, url, jwt, header_name='Authorization', header_type='Bearer'):
66+
header_type = '{} {}'.format(header_type, jwt).strip()
67+
response = self.client.get(url, headers={header_name: header_type})
6868
status_code = response.status_code
6969
data = json.loads(response.get_data(as_text=True))
7070
return status_code, data
@@ -279,22 +279,32 @@ def claims():
279279
self.assertEqual(status, 200)
280280
self.assertEqual(data, {'username': 'test', 'claims': {'foo': 'bar'}})
281281

282-
def test_different_auth_header(self):
282+
def test_different_headers(self):
283283
response = self.client.post('/auth/login')
284284
data = json.loads(response.get_data(as_text=True))
285285
access_token = data['access_token']
286286

287-
self.app.config['JWT_AUTH_HEADER'] = 'JWT'
288-
status, data = self._jwt_get('/protected', access_token, auth_header='JWT')
287+
self.app.config['JWT_HEADER_TYPE'] = 'JWT'
288+
status, data = self._jwt_get('/protected', access_token, header_type='JWT')
289289
self.assertEqual(data, {'msg': 'hello world'})
290290
self.assertEqual(status, 200)
291291

292-
self.app.config['JWT_AUTH_HEADER'] = ''
293-
status, data = self._jwt_get('/protected', access_token, auth_header='')
292+
self.app.config['JWT_HEADER_TYPE'] = ''
293+
status, data = self._jwt_get('/protected', access_token, header_type='')
294294
self.assertEqual(data, {'msg': 'hello world'})
295295
self.assertEqual(status, 200)
296296

297-
self.app.config['JWT_AUTH_HEADER'] = ''
298-
status, data = self._jwt_get('/protected', access_token, auth_header='Bearer')
297+
self.app.config['JWT_HEADER_TYPE'] = ''
298+
status, data = self._jwt_get('/protected', access_token, header_type='Bearer')
299299
self.assertIn('msg', data)
300300
self.assertEqual(status, 422)
301+
302+
self.app.config['JWT_HEADER_TYPE'] = 'Bearer'
303+
self.app.config['JWT_HEADER_NAME'] = 'Auth'
304+
status, data = self._jwt_get('/protected', access_token, header_name='Auth')
305+
self.assertIn('msg', data)
306+
self.assertEqual(status, 401)
307+
308+
status, data = self._jwt_get('/protected', access_token, header_name='Authorization')
309+
self.assertEqual(data, {'msg': 'hello world'})
310+
self.assertEqual(status, 200)

0 commit comments

Comments
 (0)