Skip to content

Commit 93f187f

Browse files
authored
doc: Document claims sub and jti (#1088)
* Add documentation for "sub" (Subject) and "jti" (JWT ID) claims in usage.rst * Refine documentation for "sub" and "jti" claims in usage.rst for clarity and detail * fix typos * add changelog entries * fix submenu order * fix failing doctest * remove rudandant type case
1 parent f2d0ebe commit 93f187f

File tree

3 files changed

+58
-3
lines changed

3 files changed

+58
-3
lines changed

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Added
2020

2121
- Docs: Add example of using leeway with nbf by @djw8605 in `#1034 <https://github.com/jpadilla/pyjwt/pull/1034>`__
2222
- Docs: Refactored docs with ``autodoc``; added ``PyJWS`` and ``jwt.algorithms`` docs by @pachewise in `#1045 <https://github.com/jpadilla/pyjwt/pull/1045>`__
23+
- Docs: Documentation improvements for "sub" and "jti" claims by @cleder in `#1088 <https://github.com/jpadilla/pyjwt/pull/1088>`
2324

2425
`v2.10.1 <https://github.com/jpadilla/pyjwt/compare/2.10.0...2.10.1>`__
2526
-----------------------------------------------------------------------

docs/usage.rst

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ By default the ``typ`` is attaching to the headers. In case when you don't need
111111
... "secret",
112112
... algorithm="HS256",
113113
... headers={"typ": None},
114-
... )
115-
114+
... ) # doctest: +ELLIPSIS
115+
'eyJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9...'
116116
117117
Reading the Claimset without Validation
118118
---------------------------------------
@@ -161,6 +161,8 @@ how they should be used. PyJWT supports these registered claim names:
161161
- "iss" (Issuer) Claim
162162
- "aud" (Audience) Claim
163163
- "iat" (Issued At) Claim
164+
- "sub" (Subject) Claim
165+
- "jti" (JWT ID) Claim
164166

165167
Expiration Time Claim (exp)
166168
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -350,6 +352,58 @@ Issued At Claim (iat)
350352
>>> token = jwt.encode({"iat": 1371720939}, "secret")
351353
>>> token = jwt.encode({"iat": datetime.datetime.now(tz=timezone.utc)}, "secret")
352354
355+
Subject Claim (sub)
356+
~~~~~~~~~~~~~~~~~~~
357+
358+
The "sub" (subject) claim identifies the principal that is the subject of the JWT.
359+
The subject value MUST either be scoped to be locally unique in the context of the issuer or be globally unique.
360+
Use of this claim is OPTIONAL.
361+
362+
.. code-block:: pycon
363+
364+
>>> payload = {"some": "payload", "sub": "1234567890"}
365+
>>> token = jwt.encode(payload, "secret")
366+
>>> decoded = jwt.decode(token, "secret", algorithms=["HS256"])
367+
>>> decoded["sub"]
368+
'1234567890'
369+
370+
Think of the `sub` claim as the **"who"** of the JWT.
371+
It identifies the subject of the token — the user or entity that the token is about.
372+
The claims inside a JWT are essentially statements about this subject.
373+
374+
For example, if you have a JWT for a logged-in user, the `sub` claim would typically be their unique user ID, like `1234567890`.
375+
This value needs to be unique within your application's context so you can reliably identify who the token belongs to.
376+
While the `sub` claim is optional, it's a fundamental part of most JWT-based authentication systems.
377+
378+
JWT ID Claim (jti)
379+
~~~~~~~~~~~~~~~~~~
380+
381+
The "jti" (JWT ID) claim provides a unique identifier for the JWT.
382+
The identifier value MUST be assigned in a manner that ensures that there is a negligible probability that the same value will be accidentally assigned to a different data object.
383+
If the application uses multiple issuers, collisions MUST be prevented among values produced by different issuers as well.
384+
The "jti" value is a case-sensitive string.
385+
Use of this claim is OPTIONAL.
386+
387+
.. code-block:: pycon
388+
389+
>>> import uuid
390+
>>> payload = {"some": "payload", "jti": str(uuid.uuid4())}
391+
>>> token = jwt.encode(payload, "secret")
392+
>>> decoded = jwt.decode(token, "secret", algorithms=["HS256"])
393+
>>> decoded["jti"] # doctest: +SKIP
394+
'3fa85f64-5717-4562-b3fc-2c963f66afa6'
395+
396+
The `jti` claim is giving your JWT a unique identifier.
397+
Think of it like a serial number for the token.
398+
This ID must be assigned in a way that makes it virtually impossible for two different tokens to have the same `jti` value.
399+
A common practice is to use a Universally Unique Identifier (UUID).
400+
401+
The `jti` claim is used to **prevent replay attacks**.
402+
A replay attack happens when a bad actor intercepts a valid token and uses it to make a request again.
403+
By storing the `jti` of every token you've already processed in a database or cache, you can check if a token has been used before.
404+
If a token with a previously-seen `jti` shows up, you can reject the request, stopping the attack.
405+
406+
353407
Requiring Presence of Claims
354408
----------------------------
355409

jwt/algorithms.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -793,7 +793,7 @@ def __init__(self, **kwargs: Any) -> None:
793793
def prepare_key(self, key: AllowedOKPKeys | str | bytes) -> AllowedOKPKeys:
794794
if not isinstance(key, (str, bytes)):
795795
self.check_crypto_key_type(key)
796-
return cast("AllowedOKPKeys", key)
796+
return key
797797

798798
key_str = key.decode("utf-8") if isinstance(key, bytes) else key
799799
key_bytes = key.encode("utf-8") if isinstance(key, str) else key

0 commit comments

Comments
 (0)