Skip to content

Commit 2805617

Browse files
committed
docs: add token family feature documentation
- Included token_family app in conf.py - Added family_app.rst for token family details - Documented related family settings in settings.rst - Linked family_app in index.rst toctree
1 parent 14da7e7 commit 2805617

File tree

4 files changed

+179
-0
lines changed

4 files changed

+179
-0
lines changed

docs/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def django_configure():
3434
"rest_framework",
3535
"rest_framework_simplejwt",
3636
"rest_framework_simplejwt.token_blacklist",
37+
"rest_framework_simplejwt.token_family",
3738
),
3839
)
3940

docs/family_app.rst

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
.. _family_app:
2+
3+
Family app
4+
===========
5+
6+
The **Token Family** system provides a way to group refresh and access tokens into logical families,
7+
allowing developers to track, manage, and invalidate related tokens as a unit.
8+
9+
This feature is especially useful in enhancing security and traceability in authentication flows.
10+
Each token family is identified by a unique ``family_id``, which is included in the token payload.
11+
This enables the system to:
12+
13+
- Detect and respond to refresh token reuse by invalidating the entire token family.
14+
- Revoke all related tokens at once.
15+
- Enforce expiration policies at the family level via a ``family_exp`` claim.
16+
17+
A new token family is automatically created every time a user successfully obtains a pair of tokens
18+
from the ``TokenObtainPairView`` (i.e., when starting a new session). From that point onward, as long as the user
19+
continues to refresh their tokens, the newly issued access and refresh tokens will retain the same
20+
``family_id`` and ``family_exp`` values. This means all tokens issued as part of a session are
21+
considered to belong to the same token family.
22+
23+
This session-based grouping allows administrators or systems to treat the token family as the unit of trust.
24+
If suspicious activity is detected, the entire session can be invalidated at once by blacklisting the
25+
associated token family.
26+
27+
The Token Family system is optional and customizable. It works best when paired with the
28+
:doc:`/blacklist_app`.
29+
30+
By organizing tokens into families, you gain finer control over user sessions and potential compromises.
31+
For example, when the settings ``BLACKLIST_AFTER_ROTATION`` and ``TOKEN_FAMILY_BLACKLIST_ON_REUSE`` are
32+
set to ``True``, if a refresh token is stolen and used by both the valid user and the attacker,
33+
the system will detect the reuse of the token and automatically blacklist the associated family.
34+
This invalidates every token that shares the same ``family_id`` as the reused refresh token, effectively
35+
cutting off access without waiting for individual token expiration.
36+
37+
-------
38+
39+
Simple JWT includes an app that provides token family functionality. To use
40+
this app, include it in your list of installed apps in ``settings.py``:
41+
42+
.. code-block:: python
43+
44+
# Django project settings.py
45+
46+
...
47+
48+
INSTALLED_APPS = (
49+
...
50+
'rest_framework_simplejwt.token_family',
51+
...
52+
)
53+
54+
Also, make sure to run ``python manage.py migrate`` to run the app's
55+
migrations.
56+
57+
If the token family app is detected in ``INSTALLED_APPS`` and the setting
58+
``TOKEN_FAMILY_ENABLED`` is set to ``True``, Simple JWT will add a new family
59+
to the family list and will also add two new claims, "family_id" and
60+
"family_exp", to the refresh tokens. It will also check that the
61+
family indicated in the token's payload does not appear in a blacklist of
62+
families before it considers it as valid, and it will also check that the
63+
family expiration date ("family_exp") has not passed; if the family is expired,
64+
then the token will be considered as invalid.
65+
66+
The Simple JWT family app implements its family and blacklisted family
67+
lists using two models: ``TokenFamily`` and ``BlacklistedTokenFamily``. Model
68+
admins are defined for both of these models. To add a family to the blacklist,
69+
find its corresponding ``TokenFamily`` record in the admin and use the
70+
admin again to create a ``BlacklistedTokenFamily`` record that points to the
71+
``TokenFamily`` record.
72+
73+
Alternatively, you can blacklist a family by creating a ``FamilyMixin``
74+
subclass instance and calling the instance's ``blacklist_family`` method:
75+
76+
.. code-block:: python
77+
78+
from rest_framework_simplejwt.tokens import RefreshToken
79+
80+
token = RefreshToken(base64_encoded_token_string)
81+
token.blacklist_family()
82+
83+
Keep in mind that the ``base64_encoded_token_string`` should already
84+
contain a family ID claim in its payload.
85+
86+
This will create a unique family and blacklist records for the token's
87+
"family_id" claim or whichever claim is specified by the ``TOKEN_FAMILY_CLAIM`` setting.
88+
89+
90+
In a ``urls.py`` file, you can also include a route for ``TokenFamilyBlacklistView``:
91+
92+
.. code-block:: python
93+
94+
from rest_framework_simplejwt.views import TokenFamilyBlacklistView
95+
96+
urlpatterns = [
97+
...
98+
path('api/token/family/blacklist/', TokenFamilyBlacklistView.as_view(), name='token_family_blacklist'),
99+
...
100+
]
101+
102+
It allows API users to blacklist token families sending them to ``/api/token/family/blacklist/``, for example using curl:
103+
104+
.. code-block:: bash
105+
106+
curl \
107+
-X POST \
108+
-H "Content-Type: application/json" \
109+
-d '{"refresh":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTc0NzI0OTU1MywiaWF0IjoxNzQ3MjQ0MTUzLCJqdGkiOiI1YmMzMjlmMjVkODE0OGFhOTY1ODI1YjgwNDQ1ZDQ5OCIsInVzZXJfaWQiOjIsImZhbWlseV9pZCI6ImMyZGYyM2M1YjU1NjRmYjNhNTA3MjFhYzVkMTljNThmIiwiZmFtaWx5X2V4cCI6MTc0NzI0OTE1M30.4oDOmtkgot_W2mXByKuCyJLi6_xeMZtDQJmHIBXZx98"}' \
110+
http://localhost:8000/api/token/family/blacklist/
111+
112+
The family app also provides a management command, ``flushexpiredfamilies``,
113+
which will delete any families from the token family list and family blacklist that have
114+
expired. The command will not affect families that have ``None`` as their expiration.
115+
You should set up a cron job on your server or hosting platform which
116+
runs this command daily.

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ Contents
5151
creating_tokens_manually
5252
token_types
5353
blacklist_app
54+
family_app
5455
stateless_user_authentication
5556
development_and_contributing
5657
drf_yasg_integration

docs/settings.rst

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ Some of Simple JWT's behavior can be customized through settings variables in
2020
"BLACKLIST_AFTER_ROTATION": False,
2121
"UPDATE_LAST_LOGIN": False,
2222
23+
"TOKEN_FAMILY_ENABLED": False,
24+
"TOKEN_FAMILY_LIFETIME": timedelta(days=30),
25+
"TOKEN_FAMILY_CHECK_ON_ACCESS": False,
26+
"TOKEN_FAMILY_BLACKLIST_ON_REUSE": False,
27+
2328
"ALGORITHM": "HS256",
2429
"SIGNING_KEY": settings.SECRET_KEY,
2530
"VERIFYING_KEY": "",
@@ -43,6 +48,9 @@ Some of Simple JWT's behavior can be customized through settings variables in
4348
4449
"JTI_CLAIM": "jti",
4550
51+
"TOKEN_FAMILY_CLAIM": "family_id",
52+
"TOKEN_FAMILY_EXPIRATION_CLAIM": "family_exp",
53+
4654
"SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp",
4755
"SLIDING_TOKEN_LIFETIME": timedelta(minutes=5),
4856
"SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=1),
@@ -53,6 +61,7 @@ Some of Simple JWT's behavior can be customized through settings variables in
5361
"TOKEN_BLACKLIST_SERIALIZER": "rest_framework_simplejwt.serializers.TokenBlacklistSerializer",
5462
"SLIDING_TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainSlidingSerializer",
5563
"SLIDING_TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSlidingSerializer",
64+
"TOKEN_FAMILY_BLACKLIST_SERIALIZER": "rest_framework_simplejwt.serializers.TokenFamilyBlacklistSerializer",
5665
}
5766
5867
Above, the default values for these settings are shown.
@@ -105,6 +114,46 @@ login (TokenObtainPairView).
105114
a security vulnerability. If you really want this, throttle the endpoint with
106115
DRF at the very least.
107116

117+
``TOKEN_FAMILY_ENABLED``
118+
----------------------------
119+
120+
When set to ``True``, enables the Token Family tracking system. This allows
121+
refresh tokens to be grouped into families using a shared identifier. By default,
122+
this identifier is only included in refresh tokens, but it can also be added to
123+
access tokens if ``TOKEN_FAMILY_CHECK_ON_ACCESS`` is set to ``True``.
124+
Families can be invalidated as a whole, meaning all tokens associated with the
125+
same family will then be considered invalid.
126+
You need to add ``'rest_framework_simplejwt.token_family',`` to your
127+
``INSTALLED_APPS`` in the settings file to use this setting.
128+
129+
This feature is most effective when used in conjunction with the :doc:`/blacklist_app`
130+
131+
Learn more about :doc:`/family_app`.
132+
133+
``TOKEN_FAMILY_LIFETIME``
134+
----------------------------
135+
136+
A ``datetime.timedelta`` object that specifies how long a token family is considered valid.
137+
This ``timedelta`` value is added to the current UTC time during token generation
138+
to obtain the token's default "family_exp" claim value.
139+
This setting can also be set to ``None``, in which case the "family_exp" claim
140+
will not be included in the token payload and the token family will never expire automatically.
141+
In that case, the only way to invalidate the family is by blacklisting it.
142+
143+
``TOKEN_FAMILY_CHECK_ON_ACCESS``
144+
-------------------------------------
145+
146+
When set to ``True``, the token family claims ("family_id" and "family_exp") will be included
147+
in the access token payload. Requests authenticated with access tokens will then verify
148+
that the token's family is valid, meaning it has not expired and has not been blacklisted.
149+
150+
``TOKEN_FAMILY_BLACKLIST_ON_REUSE``
151+
-------------------------------------
152+
153+
When set to ``True``, any detected reuse of a refresh token will trigger blacklisting of
154+
the entire token family. This invalidates all tokens that share the same family identifier.
155+
This feature can be enhanced when used together with ``BLACKLIST_AFTER_ROTATION`` set to ``True``.
156+
108157
``ALGORITHM``
109158
-------------
110159

@@ -259,6 +308,18 @@ identifier is used to identify revoked tokens in the blacklist app. It may be
259308
necessary in some cases to use another claim besides the default "jti" claim to
260309
store such a value.
261310

311+
``TOKEN_FAMILY_CLAIM``
312+
---------------------------
313+
314+
The claim name used to store the token family's unique identifier in the token
315+
payload. Defaults to "family_id".
316+
317+
``TOKEN_FAMILY_EXPIRATION_CLAIM``
318+
-------------------------------------
319+
320+
The claim name used to store the token family's expiration date in the token
321+
payload. Defaults to "family_exp".
322+
262323
``TOKEN_USER_CLASS``
263324
--------------------
264325

0 commit comments

Comments
 (0)