Skip to content

Commit a027698

Browse files
author
Landon Gilbert-Bland
committed
Helper functions for verifying JWTs in a request (#131)
This is useful for using flask before_requests, or creating your own decorators while utilizing flask_jwt_extended features.
1 parent 5d7868c commit a027698

File tree

6 files changed

+163
-35
lines changed

6 files changed

+163
-35
lines changed

docs/api.rst

+16
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,22 @@ Protected endpoint decorators
3434
.. autofunction:: jwt_optional
3535

3636

37+
.. _Verify Tokens in Request:
38+
39+
Verify Tokens in Request
40+
~~~~~~~~~~~~~~~~~~~~~~~~
41+
These perform the same actions as the protected endpoint decorators, without
42+
actually decorating a function. These are very useful if you want to create
43+
your own decorators on top of flask jwt extended (such as role_required), or
44+
if you want to hook some of this extensions functionality into a flask
45+
before_request handler.
46+
47+
.. autofunction:: verify_jwt_in_request
48+
.. autofunction:: verify_jwt_in_request_optional
49+
.. autofunction:: verify_fresh_jwt_in_request
50+
.. autofunction:: verify_jwt_refresh_token_in_request
51+
52+
3753
Utilities
3854
~~~~~~~~~
3955
.. autofunction:: create_access_token

docs/custom_decorators.rst

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
Custom Decorators
2+
=================
3+
4+
You can create your own decorators that extend the functionality of the
5+
decorators provided by this extension. For example, you may want to create
6+
your own decorator that verifies a JWT is present as well as verifying that
7+
this token has sufficient permissions/roles to access an endpoint.
8+
9+
:ref:`Verify Tokens in Request` is a list of functions that can be
10+
used to build your own decorators (these are also what all the default
11+
decorators provided by this extension use internally).
12+
13+
Here is an example of how this might look.
14+
15+
.. literalinclude:: ../examples/custom_decorators.py
16+
17+

docs/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Flask-JWT-Extended's Documentation
1616
add_custom_data_claims
1717
tokens_from_complex_object
1818
complex_objects_from_token
19+
custom_decorators
1920
refresh_tokens
2021
token_freshness
2122
changing_default_behavior

examples/custom_decorators.py

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from functools import wraps
2+
3+
from flask import Flask, jsonify, request
4+
from flask_jwt_extended import (
5+
JWTManager, verify_jwt_in_request, create_access_token,
6+
get_jwt_claims
7+
)
8+
9+
app = Flask(__name__)
10+
11+
app.config['JWT_SECRET_KEY'] = 'super-secret' # Change this!
12+
jwt = JWTManager(app)
13+
14+
15+
16+
# Here is a custom decorator that verifies the JWT is present in
17+
# the request, as well as insuring that this user has a role of
18+
# `admin` in the access token
19+
def admin_required(fn):
20+
@wraps(fn)
21+
def wrapper(*args, **kwargs):
22+
verify_jwt_in_request()
23+
claims = get_jwt_claims()
24+
if claims['roles'] != 'admin':
25+
return jsonify(msg='Admins only!'), 403
26+
else:
27+
return fn(*args, **kwargs)
28+
return wrapper
29+
30+
31+
@jwt.user_claims_loader
32+
def add_claims_to_access_token(identity):
33+
if identity == 'admin':
34+
return {'roles': 'admin'}
35+
else:
36+
return {'roles': 'pesant'}
37+
38+
39+
@app.route('/login', methods=['POST'])
40+
def login():
41+
username = request.json.get('username', None)
42+
access_token = create_access_token(username)
43+
return jsonify(access_token=access_token)
44+
45+
46+
@app.route('/protected', methods=['GET'])
47+
@admin_required
48+
def protected():
49+
return jsonify(secret_message="go banana!")
50+
51+
if __name__ == '__main__':
52+
app.run()

flask_jwt_extended/__init__.py

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
from .jwt_manager import JWTManager
22
from .view_decorators import (
3-
jwt_required, fresh_jwt_required, jwt_refresh_token_required, jwt_optional
3+
fresh_jwt_required, jwt_optional, jwt_refresh_token_required, jwt_required,
4+
verify_fresh_jwt_in_request, verify_jwt_in_request,
5+
verify_jwt_in_request_optional, verify_jwt_refresh_token_in_request
46
)
57
from .utils import (
6-
create_refresh_token, create_access_token, get_jwt_identity,
7-
get_jwt_claims, set_access_cookies, set_refresh_cookies,
8-
unset_jwt_cookies, unset_access_cookies, unset_refresh_cookies,
9-
get_raw_jwt, get_current_user, current_user, get_jti, decode_token,
10-
get_csrf_token
8+
create_access_token, create_refresh_token, current_user, decode_token,
9+
get_csrf_token, get_current_user, get_jti, get_jwt_claims, get_jwt_identity,
10+
get_raw_jwt, set_access_cookies, set_refresh_cookies, unset_access_cookies,
11+
unset_jwt_cookies, unset_refresh_cookies
1112
)
1213

1314
__version__ = '3.8.2'

flask_jwt_extended/view_decorators.py

+70-29
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,72 @@
1919
)
2020

2121

22+
def verify_jwt_in_request():
23+
"""
24+
Ensure that the requeste has a valid access token. This does not check the
25+
freshness of the access token. Raises an appropiate exception there is
26+
no token or if the token is invalid.
27+
"""
28+
if request.method not in config.exempt_methods:
29+
jwt_data = _decode_jwt_from_request(request_type='access')
30+
ctx_stack.top.jwt = jwt_data
31+
verify_token_claims(jwt_data)
32+
_load_user(jwt_data[config.identity_claim_key])
33+
34+
35+
def verify_jwt_in_request_optional():
36+
"""
37+
Optionally check if this request has a valid access token. If an access
38+
token in present in the request, :func:`~flask_jwt_extended.get_jwt_identity`
39+
will return the identity of the access token. If no access token is
40+
present in the request, this simply returns, and
41+
:func:`~flask_jwt_extended.get_jwt_identity` will return `None` instead.
42+
43+
If there is an invalid access token in the request (expired, tampered with,
44+
etc), this will still raise the appropiate exception.
45+
"""
46+
try:
47+
if request.method not in config.exempt_methods:
48+
jwt_data = _decode_jwt_from_request(request_type='access')
49+
ctx_stack.top.jwt = jwt_data
50+
verify_token_claims(jwt_data)
51+
_load_user(jwt_data[config.identity_claim_key])
52+
except (NoAuthorizationError, InvalidHeaderError):
53+
pass
54+
55+
56+
def verify_fresh_jwt_in_request():
57+
"""
58+
Ensure that the requeste has a valid and fresh access token. Raises an
59+
appropiate exception if there is no token, the token is invalid, or the
60+
token is not marked as fresh.
61+
"""
62+
if request.method not in config.exempt_methods:
63+
jwt_data = _decode_jwt_from_request(request_type='access')
64+
ctx_stack.top.jwt = jwt_data
65+
fresh = jwt_data['fresh']
66+
if isinstance(fresh, bool):
67+
if not fresh:
68+
raise FreshTokenRequired('Fresh token required')
69+
else:
70+
now = timegm(datetime.utcnow().utctimetuple())
71+
if fresh < now:
72+
raise FreshTokenRequired('Fresh token required')
73+
verify_token_claims(jwt_data)
74+
_load_user(jwt_data[config.identity_claim_key])
75+
76+
77+
def verify_jwt_refresh_token_in_request():
78+
"""
79+
Ensure that the requeste has a valid refresh token. Raises an appropiate
80+
exception if there is no token or the token is invalid.
81+
"""
82+
if request.method not in config.exempt_methods:
83+
jwt_data = _decode_jwt_from_request(request_type='refresh')
84+
ctx_stack.top.jwt = jwt_data
85+
_load_user(jwt_data[config.identity_claim_key])
86+
87+
2288
def jwt_required(fn):
2389
"""
2490
A decorator to protect a Flask endpoint.
@@ -31,11 +97,7 @@ def jwt_required(fn):
3197
"""
3298
@wraps(fn)
3399
def wrapper(*args, **kwargs):
34-
if request.method not in config.exempt_methods:
35-
jwt_data = _decode_jwt_from_request(request_type='access')
36-
ctx_stack.top.jwt = jwt_data
37-
verify_token_claims(jwt_data)
38-
_load_user(jwt_data[config.identity_claim_key])
100+
verify_jwt_in_request()
39101
return fn(*args, **kwargs)
40102
return wrapper
41103

@@ -56,13 +118,7 @@ def jwt_optional(fn):
56118
"""
57119
@wraps(fn)
58120
def wrapper(*args, **kwargs):
59-
try:
60-
jwt_data = _decode_jwt_from_request(request_type='access')
61-
ctx_stack.top.jwt = jwt_data
62-
verify_token_claims(jwt_data)
63-
_load_user(jwt_data[config.identity_claim_key])
64-
except (NoAuthorizationError, InvalidHeaderError):
65-
pass
121+
verify_jwt_in_request_optional()
66122
return fn(*args, **kwargs)
67123
return wrapper
68124

@@ -79,19 +135,7 @@ def fresh_jwt_required(fn):
79135
"""
80136
@wraps(fn)
81137
def wrapper(*args, **kwargs):
82-
if request.method not in config.exempt_methods:
83-
jwt_data = _decode_jwt_from_request(request_type='access')
84-
ctx_stack.top.jwt = jwt_data
85-
fresh = jwt_data['fresh']
86-
if isinstance(fresh, bool):
87-
if not fresh:
88-
raise FreshTokenRequired('Fresh token required')
89-
else:
90-
now = timegm(datetime.utcnow().utctimetuple())
91-
if fresh < now:
92-
raise FreshTokenRequired('Fresh token required')
93-
verify_token_claims(jwt_data)
94-
_load_user(jwt_data[config.identity_claim_key])
138+
verify_fresh_jwt_in_request()
95139
return fn(*args, **kwargs)
96140
return wrapper
97141

@@ -105,10 +149,7 @@ def jwt_refresh_token_required(fn):
105149
"""
106150
@wraps(fn)
107151
def wrapper(*args, **kwargs):
108-
if request.method not in config.exempt_methods:
109-
jwt_data = _decode_jwt_from_request(request_type='refresh')
110-
ctx_stack.top.jwt = jwt_data
111-
_load_user(jwt_data[config.identity_claim_key])
152+
verify_jwt_refresh_token_in_request()
112153
return fn(*args, **kwargs)
113154
return wrapper
114155

0 commit comments

Comments
 (0)