Skip to content

Commit f57ca90

Browse files
authored
Fix documentation around identity needing to be a string (#558)
Previously we allowed identity to be any data that was JSON serializable, however it turns out that is in violation of the JWT spec, which requires `sub` to be a string. The underlying library that we are using to manage the JWTs (PyJWT) released a new version that is enforcing this behavior, where it didn't before. Because `sub` should be a string per the spec, I've opted to keep that change in this extension, and update the documentation to match this new behavior.
1 parent 85a3750 commit f57ca90

File tree

6 files changed

+23
-15
lines changed

6 files changed

+23
-15
lines changed

docs/automatic_user_loading.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ accessing a protected route. We provide a couple callback functions that make
66
this seamless while working with JWTs.
77

88
The first is :meth:`~flask_jwt_extended.JWTManager.user_identity_loader`, which
9-
will convert any ``User`` object used to create a JWT into a JSON serializable format.
9+
will convert any ``User`` object used to create a JWT into a string.
1010

1111
On the flip side, you can use :meth:`~flask_jwt_extended.JWTManager.user_lookup_loader`
1212
to automatically load your ``User`` object when a JWT is present in the request.

flask_jwt_extended/default_callbacks.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def default_jwt_headers_callback(default_headers) -> dict:
4141
return {}
4242

4343

44-
def default_user_identity_callback(userdata: Any) -> Any:
44+
def default_user_identity_callback(userdata: Any) -> str:
4545
"""
4646
By default, we use the passed in object directly as the jwt identity.
4747
See this for additional info:

flask_jwt_extended/jwt_manager.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -434,15 +434,14 @@ def unauthorized_loader(self, callback: Callable) -> Callable:
434434
def user_identity_loader(self, callback: Callable) -> Callable:
435435
"""
436436
This decorator sets the callback function used to convert an identity to
437-
a JSON serializable format when creating JWTs. This is useful for
438-
using objects (such as SQLAlchemy instances) as the identity when
439-
creating your tokens.
437+
a string when creating JWTs. This is useful for using objects (such as
438+
SQLAlchemy instances) as the identity when creating your tokens.
440439
441440
The decorated function must take **one** argument.
442441
443442
The argument is the identity that was used when creating a JWT.
444443
445-
The decorated function must return JSON serializable data.
444+
The decorated function must return a string.
446445
"""
447446
self._user_identity_callback = callback
448447
return callback

flask_jwt_extended/utils.py

+6-8
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,9 @@ def create_access_token(
139139
Create a new access token.
140140
141141
:param identity:
142-
The identity of this token. It can be any data that is json serializable.
143-
You can use :meth:`~flask_jwt_extended.JWTManager.user_identity_loader`
144-
to define a callback function to convert any object passed in into a json
145-
serializable format.
142+
The identity of this token. This must either be a string, or you must have
143+
defined :meth:`~flask_jwt_extended.JWTManager.user_identity_loader` in order
144+
to convert the object you passed in into a string.
146145
147146
:param fresh:
148147
If this token should be marked as fresh, and can thus access endpoints
@@ -192,10 +191,9 @@ def create_refresh_token(
192191
Create a new refresh token.
193192
194193
:param identity:
195-
The identity of this token. It can be any data that is json serializable.
196-
You can use :meth:`~flask_jwt_extended.JWTManager.user_identity_loader`
197-
to define a callback function to convert any object passed in into a json
198-
serializable format.
194+
The identity of this token. This must either be a string, or you must have
195+
defined :meth:`~flask_jwt_extended.JWTManager.user_identity_loader` in order
196+
to convert the object you passed in into a string.
199197
200198
:param expires_delta:
201199
A ``datetime.timedelta`` for how long this token should last before it expires.

requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ black==23.12.1
22
cryptography==42.0.4
33
Flask==3.0.1
44
pre-commit==3.6.0
5-
PyJWT==2.8.0
5+
PyJWT==2.10.0
66
tox==4.12.1

tests/test_view_decorators.py

+11
Original file line numberDiff line numberDiff line change
@@ -469,3 +469,14 @@ def custom():
469469
response = test_client.get(url, headers=make_headers(token))
470470
assert response.status_code == 200
471471
assert response.get_json() == {"foo": "bar"}
472+
473+
474+
def test_non_string_identity(app):
475+
url = "/protected"
476+
test_client = app.test_client()
477+
with app.test_request_context():
478+
token = create_access_token(1234)
479+
480+
response = test_client.get(url, headers=make_headers(token))
481+
assert response.status_code == 422
482+
assert response.get_json() == {"msg": "Subject must be a string"}

0 commit comments

Comments
 (0)