API Authentication with JWT

A JWT (JSON Web Token) is a signed, self-contained token that carries a user's identity, letting a stateless API verify who is calling without storing any server-side session.

Learn API Authentication with JWT in our free Flask course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick…

Part of the free Flask course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

In this lesson you'll see a token's three parts, issue one on login, send it as a Bearer token, verify its signature on protected routes, and reject expired tokens.

A JWT is three base64url chunks joined by dots: header.payload.signature . The header names the algorithm; the payload holds your claims (user id, role, expiry); the signature is an HMAC of the first two parts using a secret key. Anyone can read the payload, but only someone holding the secret can produce a valid signature — so tampering is detectable.

The runnable example builds and verifies a token with Python's standard library ( hmac , base64 , json ), so it runs anywhere. This is exactly what a JWT library does under the hood.

Output: three parts? True , the decoded payload, then rejected: Invalid signature for the tampered token — the secret key is what makes forgery fail.

The flow: the client posts credentials to /login ; on success the server returns a token. The client then sends that token in the Authorization: Bearer … header on every protected request, and the server verifies it before answering.

The runnable example wires that into Flask routes and exercises it with the test client — first rejected with no token, then accepted with a valid one.

Output: 401 for the request with no token, then {" "} once the Bearer token is attached.

In production you do not hand-roll the signing. Flask-JWT-Extended creates and verifies tokens, reads the Bearer header for you, handles expiry, and provides a @jwt_required() decorator plus get_jwt_identity() . Here is the same flow in that library, shown read-only.

Complete the verifier. Replace each ___ to split the token, recompute the signature, and compare safely.

The client did not send the token. It must include Authorization: Bearer <token> on every protected request.

The secret key changed (or differs between servers). All previously issued tokens fail verification. Keep one stable secret, loaded from an environment variable.

The payload is only base64-encoded, not encrypted — anyone can read it. Never put passwords or sensitive data in a JWT.

Add an expiry claim and enforce it on verification.

Lesson complete — your API is locked down with tokens!

You understand a JWT's three parts, how the HMAC signature stops tampering, how to issue a token on login, send it as a Bearer header, verify it on protected routes, and expire it with an exp claim.

🚀 Up next: Serialization with Marshmallow — turn your models into clean JSON and validate incoming data.

Practice quiz

What are the three parts of a JWT, in order?

  • header.payload.signature
  • payload.header.signature
  • signature.header.payload
  • header.signature.payload

Answer: header.payload.signature. A JWT is header.payload.signature, three base64url chunks joined by dots.

Which JWT part holds the claims like user id and expiry?

  • The header
  • The payload
  • The signature
  • The secret

Answer: The payload. The payload carries the claims; the header names the algorithm.

What does the signature protect against?

  • Slow networks
  • Large payloads
  • Tampering with the token
  • Expired sessions

Answer: Tampering with the token. The HMAC signature over header+payload makes tampering detectable.

Who can produce a valid signature for a token?

  • Anyone who can read the payload
  • Any browser
  • The user only
  • Only someone holding the secret key

Answer: Only someone holding the secret key. Only the holder of the secret key can compute a valid HMAC signature.

How is a token sent on a protected request?

  • In the Authorization: Bearer header
  • As a URL query parameter named jwt
  • In a cookie called csrf
  • In the request body as form data

Answer: In the Authorization: Bearer header. Clients send 'Authorization: Bearer <token>' on protected requests.

Can anyone read a JWT's payload?

  • No, it is encrypted
  • Yes, it is only base64url-encoded, not encrypted
  • Only the server can
  • Only with the secret key

Answer: Yes, it is only base64url-encoded, not encrypted. The payload is base64url-encoded, so it is readable; the signature, not secrecy, provides integrity.

Which claim is used to expire a token?

  • iat
  • sub
  • exp
  • aud

Answer: exp. The exp claim holds the expiry time; tokens past it are rejected.

In Flask-JWT-Extended, which decorator protects a route?

  • @login_required
  • @app.protect
  • @token_only
  • @jwt_required()

Answer: @jwt_required(). @jwt_required() rejects missing, invalid, or expired tokens automatically.

What does create_access_token(identity=...) store the identity in?

  • The token's 'sub' claim
  • A server-side session
  • A cookie
  • The database

Answer: The token's 'sub' claim. create_access_token stores the identity in the token's sub claim.

Why are JWTs well suited to a stateless API?

  • They require a database lookup per request
  • They store sessions on the client's disk
  • The token itself carries verified identity, so no server-side session is needed
  • They encrypt every response

Answer: The token itself carries verified identity, so no server-side session is needed. A signed, self-contained token lets the server verify identity without storing a session.