TRENDING
Rows of identical brass-colored apartment mailboxes with small locks and name labels along an orange corridor wall
October 9, 2026
How to Prevent Broken Object Level Authorization (IDOR) in a FastAPI App
Street-level upward view of the Monetary Authority of Singapore building and neighbouring office towers under a pale sky
October 9, 2026
Singapore’s AI Guidelines Turn Independent Review Into a Question of Who Sets the Risk Rating
Cast-iron late Qing dynasty coin minting press with a large flywheel, displayed in a museum case
October 9, 2026
Attackers Hijacked the .gh, .sl and .as Country Domains and Minted HTTPS Certificates for Google
Rows of closed oak library card catalog drawers, each with a brass pull and a blank label holder
October 9, 2026
How to Encrypt PII in Python and Keep It Searchable With Blind Indexes
Close-up of a vintage Western Electric manual telephone switchboard with orange lamps, red patch cords plugged into jacks, a rotary dial and a black handset
October 9, 2026
Microsoft’s Agent Lightning v1.0 Turns Agent Training Into a Sample-Accounting Problem
09 Oct 2026
SXZ.io SXZ.io
  • Home
Search the Site
Popular Searches:
Technology Amazon AI
Recent Posts
Two orange safety relief valves on grey pressure vessels in an industrial plant
How to Add Backpressure and Load Shedding to a Python Service Before Overload Takes It Down
October 8, 2026
Yellow diamond-shaped merging traffic warning sign showing a side road joining a main road
GitHub’s Git Rebuild Turns Repository Durability and Read Scale Into Two Separate Problems
October 8, 2026
A lugworm lying on wet sand and mud at low tide
A Compromised Admin Account Put the Shai-Hulud Worm Into AI Sandbox Maker Tensorlake’s npm SDK
October 8, 2026
SXZ.io SXZ.io
  • Home

Categories

Articles 232 Posts
News 234 Posts
Learning Hub 204 Posts
Home/Learning Hub/How to Build and Verify JSON Web Tokens From Scratch in Python
Learning Hub

How to Build and Verify JSON Web Tokens From Scratch in Python

A hands-on walkthrough of how JWTs really work in Python, including the alg=none forgery attack and how a proper algorithm allowlist stops it.

August 6, 2026 14 Min Read
56

If you have ever called an API with an Authorization: Bearer eyJhbGc... header, you have used a JSON Web Token, or JWT. Most developers treat JWTs as a black box: some library issues one at login, another library checks it on every request, and it just works. That is fine until the day you have to debug a login bug, review a pull request that touches authentication, or explain to a security reviewer why your API trusts a token at all. This tutorial opens the black box. You will build a working JWT encoder and verifier from scratch in Python using nothing but the standard library, wire it into a real API, and then deliberately break it the way real attackers have broken real JWT libraries, before fixing it properly.

Table Of Content

  • What You’ll Build
  • Prerequisites
  • What Is a JWT, Really?
  • Signed, Not Encrypted
  • The Three Segments
  • Registered Claims You’ll Actually Use
  • Step 1: Set Up Your Project
  • Step 2: Encode a Token by Hand
  • Step 3: Verify a Token the Right Way
  • Step 4: Prove Tampering and Expiration Are Actually Caught
  • Step 5: The alg=none Attack, Because Trusting the Header Is Dangerous
  • Step 6: Fix It With an Algorithm Allowlist
  • Step 7: Confirm a Production Library Gets This Right Too
  • Step 8: Wire It Into a Real API
  • Common Mistakes and Gotchas
  • Treating the Payload as Confidential
  • Comparing Signatures With ==
  • Letting the Token Choose Its Own Verification Method
  • The Algorithm Confusion Attack (RS256 to HS256)
  • How to Verify Everything Works End to End
  • Next Steps

By the end, you will understand what a JWT actually contains, why “signed” does not mean “encrypted,” and why one specific design choice, letting the token itself say how it should be checked, has caused real, documented account takeovers. You will also see exactly how a production-grade library like PyJWT closes that gap, so you know what to look for when you eventually stop hand-rolling this and reach for a real library, which is what you should do for anything that touches real user accounts.

What You’ll Build

Working in a single Python project, you will build:

  • A small minijwt module that encodes and verifies HS256-signed JWTs using only hashlib, hmac, base64, and json from the standard library.
  • A deliberately naive verifier that reproduces a real, historical JWT vulnerability, so you can watch it get exploited.
  • A fixed verifier that closes the hole with an algorithm allowlist.
  • A small FastAPI service with a login endpoint and a protected endpoint, tested with real HTTP requests, including a live forgery attempt.

Prerequisites

You should have:

  • Python 3.11 or later, with pip available. This tutorial was built and tested on Python 3.13.14.
  • Comfort reading basic Python (functions, dictionaries, exceptions).
  • No prior JWT knowledge required. We define every term before using it.
  • A terminal. Commands below use Windows PowerShell syntax for activating the virtual environment; substitute source venv/bin/activate on macOS or Linux.

What Is a JWT, Really?

A JSON Web Token is a compact, URL-safe way to carry a set of claims, statements about a user or a session, from one system to another. That is the entire idea. The formal definition lives in RFC 7519, the IETF standard that created the format in 2015.

Signed, Not Encrypted

This is the single most common misconception, so it comes first: a standard JWT is signed, not encrypted. Signing proves the token has not been tampered with and (if verified correctly) that it was issued by someone who knows a secret key. It does not hide the payload. Anyone who intercepts a JWT, a browser extension, a proxy log, a coworker glancing at devtools, can read every claim inside it without knowing any secret. Never put a password, a credit card number, or anything else genuinely secret into a JWT payload. Treat it like a signed postcard, not a sealed envelope.

The Three Segments

A JWT is three base64url-encoded chunks joined by dots: header.payload.signature. Base64url is the same idea as ordinary base64, but it swaps a couple of characters and drops padding so the result is safe to put directly into a URL or an HTTP header without escaping. The header is a small JSON object naming the signing algorithm (for example {"alg":"HS256","typ":"JWT"}). The payload is a JSON object holding the actual claims. The signature is computed over the header and payload together, so changing either one invalidates it.

Registered Claims You’ll Actually Use

RFC 7519 Section 4.1 defines a short list of standard claim names that most systems reuse instead of inventing their own. The ones you will use here are sub (subject, usually a user ID), exp (expiration time, a Unix timestamp after which the token is invalid), iat (issued-at time), and nbf (not-before time, for tokens that should not be accepted yet). None of these are enforced by the JWT format itself; enforcing them is the verifier’s job, which is exactly what you will build in Step 3.

Step 1: Set Up Your Project

Create a project folder and an isolated virtual environment so these packages do not collide with anything else on your machine:

mkdir jwt-from-scratch
cd jwt-from-scratch
python -m venv venv
venv\Scripts\activate
pip install fastapi uvicorn pyjwt requests

You are installing four things: fastapi and uvicorn to build and run the demo API, pyjwt so you can compare your own code against a real production library, and requests to act as an HTTP client when you attack your own API later. (PyJWT can also verify asymmetric algorithms like RS256, but that needs the separate cryptography package; this tutorial sticks to HS256, so you do not need it here.) Confirm the install worked:

python -c "import fastapi, jwt; print(fastapi.__version__, jwt.__version__)"

On the machine used to write this tutorial, that printed 0.141.1 2.13.0. Your versions may be newer; that is fine, nothing here depends on a specific patch release.

Step 2: Encode a Token by Hand

Create minijwt.py. Start with the base64url helpers, since both encoding and decoding need them:

import base64
import hashlib
import hmac
import json
import time


def b64url_encode(data: bytes) -> str:
    return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii")


def b64url_decode(segment: str) -> bytes:
    padding = "=" * (-len(segment) % 4)
    return base64.urlsafe_b64decode(segment + padding)

Standard base64 pads its output with = characters and uses + and /, both of which mean something different inside a URL. Base64url swaps those two characters for - and _ and, in the JWT spec, drops the padding entirely. b64url_decode has to add the padding back before Python’s own base64 decoder will accept it, which is why it is not a one-liner.

Now add the encoder:

_ALGOS = {
    "HS256": hashlib.sha256,
    "HS384": hashlib.sha384,
    "HS512": hashlib.sha512,
}


def encode(payload: dict, secret: str, algorithm: str = "HS256") -> str:
    header = {"alg": algorithm, "typ": "JWT"}
    header_seg = b64url_encode(json.dumps(header, separators=(",", ":")).encode())
    payload_seg = b64url_encode(json.dumps(payload, separators=(",", ":")).encode())
    signing_input = f"{header_seg}.{payload_seg}".encode()
    digestmod = _ALGOS[algorithm]
    signature = hmac.new(secret.encode(), signing_input, digestmod).digest()
    sig_seg = b64url_encode(signature)
    return f"{header_seg}.{payload_seg}.{sig_seg}"

Walking through what that does: it JSON-encodes the header and payload separately, base64url-encodes each one, joins them with a dot to form the “signing input,” then runs that exact string through HMAC (a keyed hash: the same hash function everyone can compute, but the output also depends on a secret key only the server knows). The resulting signature is base64url-encoded and appended as the third segment. separators=(",", ":") just strips the extra whitespace Python’s JSON encoder adds by default, which keeps the token compact.

Try it in a Python shell in the same folder:

>>> import minijwt, time
>>> payload = {"sub": "user-42", "role": "editor", "iat": int(time.time()), "exp": int(time.time()) + 300}
>>> token = minijwt.encode(payload, "correct-horse-battery-staple")
>>> token
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyLTQyIiwicm9sZSI6ImVkaXRvciIsImlhdCI6MTc4NjA0NTY2NywiZXhwIjoxNzg2MDQ1OTY3fQ.ex-FB7nPZ6WuCVBq_KeiU4d1icdn-DX9fppyGRpwmU4'

Split that string on the dots and decode the first two segments with minijwt.b64url_decode, and you will get back the exact header and payload dictionaries you passed in, in plain text. That is the “signed, not encrypted” point made concrete: nothing about that payload is hidden.

Step 3: Verify a Token the Right Way

Encoding is the easy half. Verifying has to check three separate things: that the signature actually matches, that the token has not expired, and, as you will see in Step 5, that the algorithm named in the header is one you actually trust. Add this to minijwt.py:

class InvalidSignatureError(Exception):
    pass


class ExpiredSignatureError(Exception):
    pass


class InvalidAlgorithmError(Exception):
    pass


def decode(token: str, secret: str, algorithms=("HS256",)) -> dict:
    try:
        header_seg, payload_seg, sig_seg = token.split(".")
    except ValueError:
        raise InvalidSignatureError("malformed token: expected 3 segments")

    header = json.loads(b64url_decode(header_seg))
    alg = header.get("alg")

    if alg not in algorithms:
        raise InvalidAlgorithmError(
            f"token alg {alg!r} is not in the caller's allowlist {list(algorithms)!r}"
        )

    digestmod = _ALGOS[alg]
    signing_input = f"{header_seg}.{payload_seg}".encode()
    expected_sig = hmac.new(secret.encode(), signing_input, digestmod).digest()
    actual_sig = b64url_decode(sig_seg)

    if not hmac.compare_digest(expected_sig, actual_sig):
        raise InvalidSignatureError("signature does not match")

    payload = json.loads(b64url_decode(payload_seg))

    now = time.time()
    if "exp" in payload and now >= payload["exp"]:
        raise ExpiredSignatureError(f"token expired at {payload['exp']}, now {now}")
    if "nbf" in payload and now < payload["nbf"]:
        raise InvalidSignatureError(f"token not valid until {payload['nbf']}, now {now}")

    return payload

Two details matter more than they look like they do. First, algorithms is a parameter the caller supplies, a list of algorithms the caller has decided to trust, not something read from the token. The function checks the token's claimed algorithm against that caller-supplied list before doing anything else with it. Second, the signature comparison uses hmac.compare_digest instead of a plain ==. A normal string comparison exits as soon as it finds the first mismatched byte, which means it takes very slightly less time to reject a signature that is wrong in the first byte than one that is wrong in the last byte. In theory, an attacker who can measure that timing very precisely over many requests can use it to guess a correct signature one byte at a time. hmac.compare_digest always takes the same amount of time regardless of where the mismatch is, closing that side channel. It is a cheap habit worth having any time you compare a secret-derived value.

Step 4: Prove Tampering and Expiration Are Actually Caught

Talk is cheap; run it. In a new file, demo_steps.py, re-encode a token, then modify the payload segment in place without touching the signature, and confirm decode rejects it:

token = minijwt.encode({"sub": "user-42", "role": "editor", "exp": int(time.time()) + 300}, SECRET)
header_seg, payload_seg, sig_seg = token.split(".")

tampered_payload = json.loads(minijwt.b64url_decode(payload_seg))
tampered_payload["role"] = "admin"
tampered_seg = minijwt.b64url_encode(json.dumps(tampered_payload, separators=(",", ":")).encode())
tampered_token = f"{header_seg}.{tampered_seg}.{sig_seg}"

minijwt.decode(tampered_token, SECRET)

Running that raised, exactly as it should:

minijwt.InvalidSignatureError: signature does not match

That is the whole point of the signature: the payload changed, so recomputing HMAC over the new payload produces a different value than the signature that was issued for the original one. Expiration is equally mechanical. Encoding a token with exp set five seconds in the past and decoding it immediately raised:

minijwt.ExpiredSignatureError: token expired at 1786045662, now 1786045667.6109068

Step 5: The alg=none Attack, Because Trusting the Header Is Dangerous

Here is the part that turns this from an academic exercise into a genuine security lesson. Look again at the header: {"alg": "HS256", "typ": "JWT"}. That field is inside the token, which means anyone holding the token, including an attacker with no knowledge of your secret key, can rewrite it. If your verifier trusts that field to decide how to check the signature, you have handed the attacker the choice of verification method.

This is not a hypothetical. RFC 8725, the IETF's "JSON Web Token Best Current Practices" document, describes it plainly in Section 2.1: "The algorithm can be changed to 'none' by an attacker, and some libraries would trust this value and 'validate' the JWT without checking any signature." PortSwigger's Web Security Academy, one of the standard training resources for web application security, teaches this same class of bug under "Accepting tokens with no signature" as one of its core JWT attack labs.

To see it for yourself, write the verifier a real (if long-fixed) JWT library would never ship today, one that trusts the header's own claim about its algorithm:

def decode_naive_and_insecure(token: str, secret: str) -> dict:
    header_seg, payload_seg, sig_seg = token.split(".")
    header = json.loads(b64url_decode(header_seg))
    alg = header.get("alg")

    if alg == "none":
        return json.loads(b64url_decode(payload_seg))

    digestmod = _ALGOS[alg]
    signing_input = f"{header_seg}.{payload_seg}".encode()
    expected_sig = hmac.new(secret.encode(), signing_input, digestmod).digest()
    actual_sig = b64url_decode(sig_seg)
    if not hmac.compare_digest(expected_sig, actual_sig):
        raise InvalidSignatureError("signature does not match")
    return json.loads(b64url_decode(payload_seg))

Now forge a token without knowing SECRET at all. Set the header's algorithm to none, build whatever payload you want, and leave the signature segment empty:

forged_header = minijwt.b64url_encode(b'{"alg":"none","typ":"JWT"}')
forged_payload = minijwt.b64url_encode(b'{"sub":"attacker","role":"admin"}')
forged_token = f"{forged_header}.{forged_payload}."

minijwt.decode_naive_and_insecure(forged_token, SECRET)

Running that returned:

{'sub': 'attacker', 'role': 'admin'}

No exception. No secret key was ever needed. The naive verifier saw alg: none, believed it, and skipped signature checking entirely, handing the attacker an admin claim they invented themselves. Step 8 of this tutorial reproduces the same forgery against a live HTTP API, so you can watch it work end to end, not just inside a Python shell.

Step 6: Fix It With an Algorithm Allowlist

Feed the exact same forged token into the decode function you wrote in Step 3, the one that checks the header's algorithm against a caller-supplied allowlist before doing anything else:

minijwt.decode(forged_token, SECRET, algorithms=("HS256",))
minijwt.InvalidAlgorithmError: token alg 'none' is not in the caller's allowlist ['HS256']

Same forged token, different outcome, because the fix does not add a special case for the word "none." It removes the entire class of bug: the verifier never lets the token's own header decide how the token gets checked. The caller (your application code, not the token) decides up front which algorithms it will accept, and anything else is rejected before a single byte of the signature is compared. RFC 8725 Section 3.1, "Perform Algorithm Verification," recommends exactly this: validate the cryptographic algorithm explicitly, and reject anything outside an explicit allowlist rather than trusting whatever the token claims.

Step 7: Confirm a Production Library Gets This Right Too

You just fixed your own toy implementation. It is worth checking that the library you would actually use in production, PyJWT, already defends against this by default, rather than taking that on faith. Feed the same forged token to it:

import jwt as pyjwt

pyjwt.decode(forged_token, SECRET, algorithms=["HS256"])
jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed

PyJWT 2.13.0 rejected it the same way your fixed decode did. This is not an accident: PyJWT's own API documentation carries an explicit warning on the algorithms parameter: "Do not compute the algorithms parameter based on the alg from the token itself, or on any other data that an attacker may be able to influence, as that might expose you to various vulnerabilities," and it points directly at RFC 8725 Section 2.1, the same section quoted in Step 5. The library's design forces you to state your allowlist explicitly; there is no default that silently trusts the token.

PyJWT does still let you decode a token without verifying its signature at all, but only if you explicitly opt into that with options={"verify_signature": False}, which is a documented escape hatch for cases like reading claims from an already-verified token, not a default behavior. That distinction, insecure behavior has to be opted into explicitly rather than falling out of a careless default, is the same principle behind the allowlist fix above, and it is worth internalizing for any security-relevant code you write, JWT-related or not.

Step 8: Wire It Into a Real API

A JWT that only ever exists inside a Python shell does not teach you much about how these fail in practice. Build a minimal FastAPI service, api_demo.py:

import time
from fastapi import FastAPI, Header, HTTPException
import minijwt

SECRET = "correct-horse-battery-staple"
app = FastAPI()

FAKE_USERS = {
    "alice": {"password": "alice-pw", "role": "editor"},
    "bob": {"password": "bob-pw", "role": "viewer"},
}


@app.post("/login")
def login(username: str, password: str):
    user = FAKE_USERS.get(username)
    if not user or user["password"] != password:
        raise HTTPException(status_code=401, detail="bad credentials")
    payload = {"sub": username, "role": user["role"], "iat": int(time.time()), "exp": int(time.time()) + 60}
    token = minijwt.encode(payload, SECRET)
    return {"access_token": token, "token_type": "bearer"}


def _extract_bearer(authorization):
    if not authorization or not authorization.startswith("Bearer "):
        raise HTTPException(status_code=401, detail="missing bearer token")
    return authorization.removeprefix("Bearer ")


@app.get("/me")
def me(authorization: str | None = Header(default=None)):
    token = _extract_bearer(authorization)
    try:
        claims = minijwt.decode(token, SECRET, algorithms=("HS256",))
    except minijwt.ExpiredSignatureError:
        raise HTTPException(status_code=401, detail="token expired")
    except (minijwt.InvalidSignatureError, minijwt.InvalidAlgorithmError) as e:
        raise HTTPException(status_code=401, detail=f"invalid token: {e}")
    return {"sub": claims["sub"], "role": claims["role"]}


@app.get("/me-naive")
def me_naive(authorization: str | None = Header(default=None)):
    """Deliberately vulnerable endpoint for this tutorial. Do not copy this."""
    token = _extract_bearer(authorization)
    try:
        claims = minijwt.decode_naive_and_insecure(token, SECRET)
    except Exception as e:
        raise HTTPException(status_code=401, detail=f"invalid token: {e}")
    return {"sub": claims["sub"], "role": claims["role"]}

/me-naive exists purely so you can watch the alg=none forgery succeed over real HTTP; it is intentionally wired to the insecure verifier from Step 5. Never ship an endpoint like that. Start the server:

uvicorn api_demo:app --host 127.0.0.1 --port 8931

In a second terminal (with the same virtual environment active), log in for real and inspect the response:

>>> import requests
>>> r = requests.post("http://127.0.0.1:8931/login", params={"username": "alice", "password": "alice-pw"})
>>> r.status_code, r.json()
(200, {'access_token': 'eyJhbGciOiJIUzI1NiIs...', 'token_type': 'bearer'})

Take that token and call the protected endpoint:

>>> token = r.json()["access_token"]
>>> requests.get("http://127.0.0.1:8931/me", headers={"Authorization": f"Bearer {token}"}).json()
{'sub': 'alice', 'role': 'editor'}

Now run the same checks from Steps 4 through 6 as real HTTP calls instead of in-process function calls, plus one more: a request with no Authorization header at all. A missing header returned 401 {'detail': 'missing bearer token'}. A payload tampered to say "role": "admin" returned 401 {'detail': 'invalid token: signature does not match'}. The alg=none forgery from Step 5, sent to the safe /me endpoint, returned 401 {'detail': "invalid token: token alg 'none' is not in the caller's allowlist ['HS256']"}. Sent to /me-naive instead, the exact same forged token, still with zero knowledge of SECRET, returned 200 {'sub': 'mallory', 'role': 'admin'}. That is the entire lesson of this tutorial in one HTTP response: on a real, live server, one missing algorithm check is the difference between an attacker with no credentials being rejected and that same attacker walking away with an admin claim.

Finally, confirm expiration works under real request/response latency, not just in a synchronous function call. A token issued with a 2-second expiry answered normally immediately, then returned 401 {'detail': 'token expired'} three seconds later.

Common Mistakes and Gotchas

Treating the Payload as Confidential

Covered above, but worth repeating because it is the mistake that shows up most often in code review: anyone with the token can read every claim inside it. If you need to hide data, encrypt it separately or do not put it in the token at all.

Comparing Signatures With ==

Python's == on bytes short-circuits at the first differing byte. For anything derived from a secret, a full-length signature included, use hmac.compare_digest so the comparison takes constant time regardless of where a mismatch occurs.

Letting the Token Choose Its Own Verification Method

The root cause behind Step 5's whole exercise. Any field inside the token, not just alg, was written by whoever holds the token, which might be an attacker. Never let a value from inside an unverified token control how that same token gets verified.

The Algorithm Confusion Attack (RS256 to HS256)

This tutorial only built and exploited the alg=none case, but RFC 8725 documents a second, related attack in the same section: a server configured to verify tokens with RS256 (an asymmetric algorithm, where a private key signs and a public key verifies) can sometimes be tricked into verifying with HS256 (a symmetric algorithm, where the same key both signs and verifies) instead, using the server's own RS256 public key, which is by definition not secret, as the HMAC key. If the verifier does not pin the expected algorithm the way Step 6's allowlist does, an attacker who knows the public key (trivial, since it is public) can forge a validly-signed-looking HS256 token. This is a real, CVE-tracked vulnerability: RFC 8725 cites CVE-2015-9235, filed against the jsonwebtoken npm package (auth0/node-jsonwebtoken) before version 4.2.2, and PortSwigger's Web Security Academy teaches it as "JWT algorithm confusion." The fix is the same allowlist principle from Step 6: a server that only ever expects RS256 should reject any token claiming HS256, full stop.

How to Verify Everything Works End to End

Before moving on, confirm you can reproduce all of this yourself:

  • Run demo_steps.py (or re-run the equivalent commands in a Python shell) and confirm you see a normal token verify successfully, a tampered token get rejected with InvalidSignatureError, an expired token get rejected with ExpiredSignatureError, the alg=none token succeed against decode_naive_and_insecure, and the same token fail against decode with InvalidAlgorithmError.
  • Start api_demo.py with uvicorn and confirm /login issues a token, /me accepts a valid token and rejects a missing, tampered, or forged one, and /me-naive is the only endpoint the forged token succeeds against.
  • Confirm PyJWT, installed separately, rejects the same forged token by default, and only skips verification when you explicitly pass options={"verify_signature": False}.

If all three hold, you have a working, correctly-verified implementation and you have personally watched the vulnerability it fixes actually work.

Next Steps

Do not run minijwt in production; it exists to teach the mechanics, and it is missing things a real deployment needs, key rotation, JWKS endpoints for RS256/ES256 public key distribution, clock-skew leeway, and years of accumulated edge-case handling. For anything user-facing, use PyJWT (or your language's equivalent vetted library) and keep the allowlist habit from Step 6: always pass an explicit algorithms list, never derive it from the token. Two other things worth knowing before you ship JWT-based auth for real: JWTs cannot be revoked once issued, since there is no server-side session to delete, which is why production systems keep expirations short (minutes, not days) and pair them with a separate, revocable refresh token; and RFC 8725 in full is worth reading end to end once, since it covers several other pitfalls, weak symmetric keys, missing audience validation, and more, that this tutorial did not have room for.

Tags:

API SecurityAuthenticationFastAPIjson-web-tokensPython

Share

A U.S. Navy sailor uses binoculars to keep watch aboard a ship
Previous Post

Cloudflare’s AEO Dashboard Turns AI Recommendations Into a Trackable Metric

A U.S. Navy sailor in uniform reaches up to file a green medical record folder among long rows of shelved patient records in a hospital records room.
Next Post

Unlimited Technology Systems Data Breach Confirmed as 2026’s Largest at 3.8 Million

No Comment! Be the first one.

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Latest
08 Oct
How to Add Backpressure and Load Shedding to a Python Service Before Overload Takes It Down
08 Oct
GitHub’s Git Rebuild Turns Repository Durability and Read Scale Into Two Separate Problems
Trending
October 8, 2026
How to Add Backpressure and Load Shedding to a Python Service Before Overload Takes It Down
October 8, 2026
GitHub’s Git Rebuild Turns Repository Durability and Read Scale Into Two Separate Problems
October 8, 2026
A Compromised Admin Account Put the Shai-Hulud Worm Into AI Sandbox Maker Tensorlake’s npm SDK
October 8, 2026
How to Prevent Broken Object Level Authorization (IDOR) in a FastAPI App
October 8, 2026
Singapore’s AI Guidelines Turn Independent Review Into a Question of Who Sets the Risk Rating
October 8, 2026
Attackers Hijacked the .gh, .sl and .as Country Domains and Minted HTTPS Certificates for Google

Related Posts

A laptop wrapped in a chain and padlock, illustrating least-privilege controls for AI agents.
Learning Hub

How to Secure Tool-Using AI Agents Before They Touch Production

June 8, 2026
Colorful sticky notes arranged on an office wall, symbolizing governance checklists and planning.
Learning Hub

AI Governance for Agentic Apps: A Practical Checklist for Builders

June 8, 2026
A technician connects green fiber optic cables at a data center, representing a private production inference endpoint.
Learning Hub

How to Deploy a Fine-Tuned LLM Behind a Private Production Inference Endpoint

June 8, 2026
Narrow aisle behind black supercomputer racks in a data center
Learning Hub

Kubernetes SELinux Volume Labeling: What Cluster Operators Should Audit Before v1.37

June 8, 2026
SXZ.io SXZ.io
  • [email protected]

Categories

Articles
Learning Hub
News

All Rights Reserved by SXZ.io ©2026