Skip to content

Commit 560fbf6

Browse files
beenjevimalloc
authored andcommitted
Allow to disable expiration time claim (#106)
* Allow to disable expiration time claim Setting expires_delta to timedelta(0) removes the "exp" claim and creates a token that never expires. Fix issue #105 * Use False to disable expiration This is more intuitive than timedelta(0)
1 parent 29b3f5a commit 560fbf6

8 files changed

+62
-13
lines changed

docs/blacklist_and_token_revoking.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
.. _Blacklist and Token Revoking:
2+
13
Blacklist and Token Revoking
24
============================
35

docs/changing_default_behavior.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,16 @@ You could accomplish this like such:
6868
expires = datetime.timedelta(days=365)
6969
token = create_access_token(username, expires_delta=expires)
7070
return jsonify({'token': token}), 201
71+
72+
You can even disable expiration by setting `expires_delta` to `False`:
73+
74+
.. code-block:: python
75+
76+
@app.route('/create-api-token', methods=['POST'])
77+
@jwt_required
78+
def create_api_token():
79+
username = get_jwt_identity()
80+
token = create_access_token(username, expires_delta=False)
81+
return jsonify({'token': token}), 201
82+
83+
Note that in this case, you should enable token revoking (see :ref:`Blacklist and Token Revoking`).

docs/options.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@ General Options:
2020
in a list to check more then one location, such as: ``['headers', 'cookies']``.
2121
Defaults to ``'headers'``
2222
``JWT_ACCESS_TOKEN_EXPIRES`` How long an access token should live before it expires. This
23-
takes a ``datetime.timedelta``, and defaults to 15 minutes
23+
takes a ``datetime.timedelta``, and defaults to 15 minutes.
24+
Can be set to ``False`` to disable expiration.
2425
``JWT_REFRESH_TOKEN_EXPIRES`` How long a refresh token should live before it expires. This
25-
takes a ``datetime.timedelta``, and defaults to 30 days
26+
takes a ``datetime.timedelta``, and defaults to 30 days.
27+
Can be set to ``False`` to disable expiration.
2628
``JWT_ALGORITHM`` Which algorithm to sign the JWT with. `See here <https://pyjwt.readthedocs.io/en/latest/algorithms.html>`_
2729
for the options. Defaults to ``'HS256'``.
2830
``JWT_SECRET_KEY`` The secret key needed for symmetric based signing algorithms,

flask_jwt_extended/config.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,15 +151,15 @@ def refresh_csrf_header_name(self):
151151
@property
152152
def access_expires(self):
153153
delta = current_app.config['JWT_ACCESS_TOKEN_EXPIRES']
154-
if not isinstance(delta, datetime.timedelta):
155-
raise RuntimeError('JWT_ACCESS_TOKEN_EXPIRES must be a datetime.timedelta')
154+
if not isinstance(delta, datetime.timedelta) and delta is not False:
155+
raise RuntimeError('JWT_ACCESS_TOKEN_EXPIRES must be a datetime.timedelta or False')
156156
return delta
157157

158158
@property
159159
def refresh_expires(self):
160160
delta = current_app.config['JWT_REFRESH_TOKEN_EXPIRES']
161-
if not isinstance(delta, datetime.timedelta):
162-
raise RuntimeError('JWT_REFRESH_TOKEN_EXPIRES must be a datetime.timedelta')
161+
if not isinstance(delta, datetime.timedelta) and delta is not False:
162+
raise RuntimeError('JWT_REFRESH_TOKEN_EXPIRES must be a datetime.timedelta or False')
163163
return delta
164164

165165
@property

flask_jwt_extended/tokens.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,14 @@ def _encode_jwt(additional_token_data, expires_delta, secret, algorithm):
1515
uid = str(uuid.uuid4())
1616
now = datetime.datetime.utcnow()
1717
token_data = {
18-
'exp': now + expires_delta,
1918
'iat': now,
2019
'nbf': now,
2120
'jti': uid,
2221
}
22+
# If expires_delta is False, the JWT should never expire
23+
# and the 'exp' claim is not set.
24+
if expires_delta:
25+
token_data['exp'] = now + expires_delta
2326
token_data.update(additional_token_data)
2427
encoded_token = jwt.encode(token_data, secret, algorithm).decode('utf-8')
2528
return encoded_token
@@ -35,7 +38,8 @@ def encode_access_token(identity, secret, algorithm, expires_delta, fresh,
3538
:param secret: Secret key to encode the JWT with
3639
:param algorithm: Which algorithm to encode this JWT with
3740
:param expires_delta: How far in the future this token should expire
38-
(datetime.timedelta)
41+
(set to False to disable expiration)
42+
:type expires_delta: datetime.timedelta or False
3943
:param fresh: If this should be a 'fresh' token or not
4044
:param user_claims: Custom claims to include in this token. This data must
4145
be json serializable
@@ -69,7 +73,8 @@ def encode_refresh_token(identity, secret, algorithm, expires_delta, csrf,
6973
:param secret: Secret key to encode the JWT with
7074
:param algorithm: Which algorithm to use for the toek
7175
:param expires_delta: How far in the future this token should expire
72-
(datetime.timedelta)
76+
(set to False to disable expiration)
77+
:type expires_delta: datetime.timedelta or False
7378
:param csrf: Whether to include a csrf double submit claim in this token
7479
(boolean)
7580
:param identity_claim_key: Which key should be used to store the identity

flask_jwt_extended/utils.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,9 @@ def create_access_token(identity, fresh=False, expires_delta=None):
100100
:func:`~flask_jwt_extended.fresh_jwt_required` endpoints.
101101
Defaults to `False`.
102102
:param expires_delta: A `datetime.timedelta` for how long this token should
103-
last before it expires. If this is None, it will
104-
use the 'JWT_ACCESS_TOKEN_EXPIRES` config value
103+
last before it expires. Set to False to disable
104+
expiration. If this is None, it will use the
105+
'JWT_ACCESS_TOKEN_EXPIRES` config value
105106
(see :ref:`Configuration Options`)
106107
:return: An encoded access token
107108
"""
@@ -120,8 +121,9 @@ def create_refresh_token(identity, expires_delta=None):
120121
to define a callback function that will be used to pull a
121122
json serializable identity out of the object.
122123
:param expires_delta: A `datetime.timedelta` for how long this token should
123-
last before it expires. If this is None, it will
124-
use the 'JWT_REFRESH_TOKEN_EXPIRES` config value
124+
last before it expires. Set to False to disable
125+
expiration. If this is None, it will use the
126+
'JWT_REFRESH_TOKEN_EXPIRES` config value
125127
(see :ref:`Configuration Options`)
126128
:return: An encoded access token
127129
"""

tests/test_config.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,14 @@ def test_override_configs(app):
129129
assert config.user_claims_key == 'bar'
130130

131131

132+
def test_tokens_never_expire(app):
133+
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = False
134+
app.config['JWT_REFRESH_TOKEN_EXPIRES'] = False
135+
with app.test_request_context():
136+
assert config.access_expires is False
137+
assert config.refresh_expires is False
138+
139+
132140
# noinspection PyStatementEffect
133141
def test_symmetric_secret_key(app):
134142
with app.test_request_context():
@@ -208,6 +216,14 @@ def test_invalid_config_options(app):
208216
with pytest.raises(RuntimeError):
209217
config.refresh_expires
210218

219+
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = True
220+
with pytest.raises(RuntimeError):
221+
config.access_expires
222+
223+
app.config['JWT_REFRESH_TOKEN_EXPIRES'] = True
224+
with pytest.raises(RuntimeError):
225+
config.refresh_expires
226+
211227
app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = 'banana'
212228
with pytest.raises(RuntimeError):
213229
config.blacklist_checks

tests/test_decode_tokens.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,15 @@ def test_expired_token(app):
8484
decode_token(refresh_token)
8585

8686

87+
def test_never_expire_token(app):
88+
with app.test_request_context():
89+
access_token = create_access_token('username', expires_delta=False)
90+
refresh_token = create_refresh_token('username', expires_delta=False)
91+
for token in (access_token, refresh_token):
92+
decoded = decode_token(token)
93+
assert 'exp' not in decoded
94+
95+
8796
def test_alternate_identity_claim(app, default_access_token):
8897
app.config['JWT_IDENTITY_CLAIM'] = 'sub'
8998

0 commit comments

Comments
 (0)