Skip to main content
Now let’s implement real security with JWT tokens and proper password hashing. This is production-ready authentication!

What We’ll Build

JWT Tokens

Cryptographically signed tokens that can’t be forged

Password Hashing

Secure password storage with industry-standard algorithms

Token Expiration

Tokens that automatically expire after a set time

Secure Validation

Proper token validation and user authentication

Installing Dependencies

We’ll use two additional packages:
  • PyJWT: For creating and validating JWT tokens
  • pwdlib: Modern password hashing library (supports Argon2, bcrypt, scrypt)
Argon2 is the current winner of the Password Hashing Competition and is recommended for new applications.

Configuration and Setup

First, let’s set up our security configuration:
Never commit your SECRET_KEY to version control! In production:
  • Store it in environment variables
  • Use a secrets management service
  • Generate a unique key for each environment
Generate a secure key with: openssl rand -hex 32

Password Hashing

Let’s create functions to hash and verify passwords:
The DUMMY_HASH is used to prevent timing attacks when a user doesn’t exist. We hash a dummy password so the timing is the same whether the user exists or not.

Database Models

In production, replace fake_users_db with a real database using SQLAlchemy, MongoDB, or your preferred database.

JWT Token Creation

Now let’s create JWT tokens:

JWT Token Structure

A JWT token contains three parts (separated by dots):
Example decoded JWT:
  • sub (subject): The username
  • exp (expiration): Unix timestamp when the token expires
  • The signature ensures the token hasn’t been tampered with
JWT tokens are encoded, not encrypted. Anyone can decode them and read the payload. Never put sensitive data (like passwords) in JWT tokens!

User Authentication

Create functions to get and authenticate users:
Timing Attack Protection: We always hash a password (even if the user doesn’t exist) to prevent attackers from determining valid usernames by measuring response times.

Get Current User (with JWT)

Now let’s implement proper token validation:
1

Extract the token

oauth2_scheme extracts the JWT from the Authorization header
2

Decode and verify

jwt.decode() verifies the signature and expiration, then decodes the payload
3

Extract username

Get the username from the sub (subject) claim
4

Get user from database

Look up the user in the database
5

Return user

Return the validated user object

Login Endpoint

Finally, create the login endpoint that issues JWT tokens:

Protected Endpoints

Now you can protect any endpoint:

Complete Working Example

Testing the Authentication

1

Get a token

Response:
2

Use the token

3

Wait for expiration

After 30 minutes, the token will expire and you’ll get a 401 error

Security Best Practices

Secret Key

  • Generate with openssl rand -hex 32
  • Store in environment variables
  • Use different keys per environment

Token Expiration

  • Short expiration (15-30 minutes)
  • Implement refresh tokens for longer sessions
  • Force re-authentication for sensitive actions

Password Hashing

  • Use Argon2id or bcrypt
  • Never store plain text passwords
  • Use timing-safe comparisons

HTTPS Only

  • Always use HTTPS in production
  • Tokens sent over HTTP can be intercepted
  • Use secure cookies when applicable

Generating Password Hashes

To create a new user, hash their password:
The test user’s password is “secret”. The hash changes each time due to the random salt, but any hash can verify the same password.

Next Steps

Now let’s add fine-grained permissions with OAuth2 scopes:

OAuth2 Scopes

Learn how to implement OAuth2 scopes for role-based access control