JWT Access Token - jbrucker/home-log GitHub Wiki
The backend service uses a JWT access token for protected endpoints.
The payload of the JWT contains (as of this writing) these fields:
user_id
id of the authenticated user (Note: this field is usually namedsub
)exp
an integer timestamp containing a timezone-aware datetime value when token expires.
The expiration key must be exp
or else the jose
JWT library will not check for expiration.
The code in app/utils/auth.py
uses 3 settings (app.core.config.settings
).
Settings
reads the values from env vars.
Settings | Env Var | Explanation |
---|---|---|
jwt_algorithm |
JWT_ALGORITHM |
Hashing algorithm to use. "HS256", "ES256", or "RS256". |
secret_key |
SECRET_KEY |
256-bit secret key. For HS256 use a 32-byte random number. |
access_token_expire_minutes |
same in uppercase | Access token lifetime, in minutes. |
Compute Timestamp for Expiration Date/time
Let: expire_minutes
be the lifetime of the token, in minutes.
from datetime import datetime, timezone
# Set expiration time. Use UTC time
expire = datetime.now(timezone.utc) + timedelta(minutes=expire_minutes)
# Must be int value, in seconds. Use int(timestamp) to remove fractional seconds.
expire_timestamp = int(expire.timestamp())
# Payload with expiration
payload = {
"sub": "user_id, username, or email", # we don't record this.
"user_id": user_id,
"exp": expire_timestamp,
# ... other claims ...
}
token = jwt.encode(payload, SECRET_KEY, algorithm=JWT_ALGORITHM)
To decode:
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
except JWTClaimsError:
# one of the claims in the token is invalid (see docs for available claims)
except ExpiredSignatureError:
# the `exp` value has already expired
except JWTError:
# the signature is invalid
To get a readable datetime
from the payload["exp"]
value:
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
expiry =payload["exp"]
assert isinstance(expiry, int)
# timestamp was recorded in UTC time
expiry_datetime = datetime.fromtimestamp(expiry, tz=timezone.utc)
print("Expires: ", expiry_datetime, "UTC")
# Get the local date/time
local_tz = datetime.now().aszoneinfo().tzinfo
local_time = expiry_datetime.astimezone(tz=local_tz)
print("Local time:", local_time)
JWT Claims
There are several other standard "claims" that can be put in the payload:
Here is a concise table of common JWT registered claims supported by the python-jose
(jose.jwt
) module and their meanings:
Claim | Meaning |
---|---|
aud |
Audience — identifies the recipients the token is intended for |
exp |
Expiration Time — token expiry as UNIX timestamp, must be int |
iss |
Issuer — identifies who issued the token |
sub |
Subject — identifies the subject of the token, e.g., user ID |
nbf |
Not Before — token is not valid before this time |
iat |
Issued At — time the token was issued |
jti |
JWT ID — unique identifier for the token |
jose.jwt.decode()
automatically validates exp
, nbf
, and iat
if present, and will raise exceptions ExpiredSignatureError
or JWTClaimsError
if any are invalid.
You can also include other standard claims or custom claims. Custom claims are application-specific and not interpreted by jose
.
Verify Claims
jose.jwt.decode()
automatically validates exp
, nbf
, and iat
if present, and will raise exceptions ExpiredSignatureError
or JWTClaimsError
.
To verify other claims, use:
from jose import jwt
# Example inputs
token = "your.jwt.token"
key = "your-secret-key"
algorithms = ["HS256"]
# Decode with verification of specific claims
payload = jwt.decode(
token,
SECRET_KEY,
algorithms=[JWT_ALGORITHM], # can list multiple algorithms to try
audience="your-audience", # Verifies the 'aud' claim
issuer="your-issuer", # Verifies the 'iss' claim
options={ # configure claim verification
"verify_exp": True,
"verify_nbf": True,
"verify_iat": False,
"verify_aud": True,
"verify_iss": True,
}
)
How to Get a Random Number for HS256 Secret Key
The HS256 hash requires a 32-byte random number as secret key. Use any of these
- Command line:
openssl rand --hex 32
- Python
os.urandom(32)
- Python
secrets.token_hex(32)
from Pythonsecrets
module
Important: In your unit tests, create a new SECRET_KEY
in code; don't use the value from your .env
file.
Test output might expose the "real" secret key.
What to return if token expired?
The app should return HTTP 401 Unauthorized with this header field:
WWW-Authenticate: Bearer error="invalid_token", error_description="Token has expired"
according to RFC 7235.
Reference
https://jwt.io has a concise introduction to JWT and interactive JWT decoder.
Python 'jose' project on pypi provides JWE (Web Encryption), JWK (Web Key), JWS (Web Signatures), and JWT.
Python 'jose' on ReadTheDocs is outdated (2015) and incomplete.