Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/blacklist_and_token_revoking.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.. _Blacklist and Token Revoking:

Blacklist and Token Revoking
============================

Expand Down
13 changes: 13 additions & 0 deletions docs/changing_default_behavior.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,16 @@ You could accomplish this like such:
expires = datetime.timedelta(days=365)
token = create_access_token(username, expires_delta=expires)
return jsonify({'token': token}), 201

You can even disable expiration by setting `expires_delta` to `False`:

.. code-block:: python

@app.route('/create-api-token', methods=['POST'])
@jwt_required
def create_api_token():
username = get_jwt_identity()
token = create_access_token(username, expires_delta=False)
return jsonify({'token': token}), 201

Note that in this case, you should enable token revoking (see :ref:`Blacklist and Token Revoking`).
6 changes: 4 additions & 2 deletions docs/options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ General Options:
in a list to check more then one location, such as: ``['headers', 'cookies']``.
Defaults to ``'headers'``
``JWT_ACCESS_TOKEN_EXPIRES`` How long an access token should live before it expires. This
takes a ``datetime.timedelta``, and defaults to 15 minutes
takes a ``datetime.timedelta``, and defaults to 15 minutes.
Can be set to ``False`` to disable expiration.
``JWT_REFRESH_TOKEN_EXPIRES`` How long a refresh token should live before it expires. This
takes a ``datetime.timedelta``, and defaults to 30 days
takes a ``datetime.timedelta``, and defaults to 30 days.
Can be set to ``False`` to disable expiration.
``JWT_ALGORITHM`` Which algorithm to sign the JWT with. `See here <https://pyjwt.readthedocs.io/en/latest/algorithms.html>`_
for the options. Defaults to ``'HS256'``.
``JWT_SECRET_KEY`` The secret key needed for symmetric based signing algorithms,
Expand Down
8 changes: 4 additions & 4 deletions flask_jwt_extended/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,15 +151,15 @@ def refresh_csrf_header_name(self):
@property
def access_expires(self):
delta = current_app.config['JWT_ACCESS_TOKEN_EXPIRES']
if not isinstance(delta, datetime.timedelta):
raise RuntimeError('JWT_ACCESS_TOKEN_EXPIRES must be a datetime.timedelta')
if not isinstance(delta, datetime.timedelta) and delta is not False:
raise RuntimeError('JWT_ACCESS_TOKEN_EXPIRES must be a datetime.timedelta or False')
return delta

@property
def refresh_expires(self):
delta = current_app.config['JWT_REFRESH_TOKEN_EXPIRES']
if not isinstance(delta, datetime.timedelta):
raise RuntimeError('JWT_REFRESH_TOKEN_EXPIRES must be a datetime.timedelta')
if not isinstance(delta, datetime.timedelta) and delta is not False:
raise RuntimeError('JWT_REFRESH_TOKEN_EXPIRES must be a datetime.timedelta or False')
return delta

@property
Expand Down
11 changes: 8 additions & 3 deletions flask_jwt_extended/tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@ def _encode_jwt(additional_token_data, expires_delta, secret, algorithm):
uid = str(uuid.uuid4())
now = datetime.datetime.utcnow()
token_data = {
'exp': now + expires_delta,
'iat': now,
'nbf': now,
'jti': uid,
}
# If expires_delta is False, the JWT should never expire
# and the 'exp' claim is not set.
if expires_delta:
token_data['exp'] = now + expires_delta
token_data.update(additional_token_data)
encoded_token = jwt.encode(token_data, secret, algorithm).decode('utf-8')
return encoded_token
Expand All @@ -35,7 +38,8 @@ def encode_access_token(identity, secret, algorithm, expires_delta, fresh,
:param secret: Secret key to encode the JWT with
:param algorithm: Which algorithm to encode this JWT with
:param expires_delta: How far in the future this token should expire
(datetime.timedelta)
(set to False to disable expiration)
:type expires_delta: datetime.timedelta or False
:param fresh: If this should be a 'fresh' token or not
:param user_claims: Custom claims to include in this token. This data must
be json serializable
Expand Down Expand Up @@ -69,7 +73,8 @@ def encode_refresh_token(identity, secret, algorithm, expires_delta, csrf,
:param secret: Secret key to encode the JWT with
:param algorithm: Which algorithm to use for the toek
:param expires_delta: How far in the future this token should expire
(datetime.timedelta)
(set to False to disable expiration)
:type expires_delta: datetime.timedelta or False
:param csrf: Whether to include a csrf double submit claim in this token
(boolean)
:param identity_claim_key: Which key should be used to store the identity
Expand Down
10 changes: 6 additions & 4 deletions flask_jwt_extended/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,9 @@ def create_access_token(identity, fresh=False, expires_delta=None):
:func:`~flask_jwt_extended.fresh_jwt_required` endpoints.
Defaults to `False`.
:param expires_delta: A `datetime.timedelta` for how long this token should
last before it expires. If this is None, it will
use the 'JWT_ACCESS_TOKEN_EXPIRES` config value
last before it expires. Set to False to disable
expiration. If this is None, it will use the
'JWT_ACCESS_TOKEN_EXPIRES` config value
(see :ref:`Configuration Options`)
:return: An encoded access token
"""
Expand All @@ -120,8 +121,9 @@ def create_refresh_token(identity, expires_delta=None):
to define a callback function that will be used to pull a
json serializable identity out of the object.
:param expires_delta: A `datetime.timedelta` for how long this token should
last before it expires. If this is None, it will
use the 'JWT_REFRESH_TOKEN_EXPIRES` config value
last before it expires. Set to False to disable
expiration. If this is None, it will use the
'JWT_REFRESH_TOKEN_EXPIRES` config value
(see :ref:`Configuration Options`)
:return: An encoded access token
"""
Expand Down
16 changes: 16 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,14 @@ def test_override_configs(app):
assert config.user_claims_key == 'bar'


def test_tokens_never_expire(app):
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = False
app.config['JWT_REFRESH_TOKEN_EXPIRES'] = False
with app.test_request_context():
assert config.access_expires is False
assert config.refresh_expires is False


# noinspection PyStatementEffect
def test_symmetric_secret_key(app):
with app.test_request_context():
Expand Down Expand Up @@ -208,6 +216,14 @@ def test_invalid_config_options(app):
with pytest.raises(RuntimeError):
config.refresh_expires

app.config['JWT_ACCESS_TOKEN_EXPIRES'] = True
with pytest.raises(RuntimeError):
config.access_expires

app.config['JWT_REFRESH_TOKEN_EXPIRES'] = True
with pytest.raises(RuntimeError):
config.refresh_expires

app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = 'banana'
with pytest.raises(RuntimeError):
config.blacklist_checks
Expand Down
9 changes: 9 additions & 0 deletions tests/test_decode_tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,15 @@ def test_expired_token(app):
decode_token(refresh_token)


def test_never_expire_token(app):
with app.test_request_context():
access_token = create_access_token('username', expires_delta=False)
refresh_token = create_refresh_token('username', expires_delta=False)
for token in (access_token, refresh_token):
decoded = decode_token(token)
assert 'exp' not in decoded


def test_alternate_identity_claim(app, default_access_token):
app.config['JWT_IDENTITY_CLAIM'] = 'sub'

Expand Down