Skip to main content

Introduction to JWT Authentication

JSON Web Token (JWT) is a stateless authentication mechanism used to securely exchange user information between a client and a server. After a successful login, the server generates a signed token and sends it to the client. The client includes this token in the Authorization header of subsequent requests, allowing the server to authenticate the user without storing any session information. Unlike Session-Based Authentication, the server does not maintain authentication state. Instead, all the information required to authenticate the user is contained within the JWT. For a deeper understanding of JWT, including how tokens are encoded and decoded, visit the official JWT website: :contentReference[oaicite:0]

What is a JWT?

A JWT is a compact, URL-safe string consisting of three parts separated by dots (.).
Each part has a specific purpose.

Structure of a JWT

1. Header

The header contains metadata about the token. Typical information includes:
  • Token type (JWT)
  • Signing algorithm (for example, HS256)
Example:

2. Payload

The payload contains information (called claims) about the authenticated user. Typical claims include:
  • User ID
  • Username
  • Email
  • Role
  • Permissions
  • Issued time (iat)
  • Expiration time (exp)
Example:
Note: The payload is Base64URL encoded, not encrypted. Anyone possessing the token can decode and read its contents. Therefore, never store sensitive information such as passwords or secret keys inside a JWT.

3. Signature

The signature is generated using:
  • Header
  • Payload
  • Secret key
  • Signing algorithm
The signature allows the server to verify that:
  • The token has not been modified.
  • The token was generated by a trusted server.
Without the server’s secret key, a client cannot generate a valid signature.

Complete JWT Authentication & Authorization Flow

Authorization Header

The client sends the JWT using the Authorization header.
Example:

Why is JWT Stateless?

Unlike Session-Based Authentication, the server does not store authentication sessions.
The server only needs its secret key to verify incoming tokens.

Advantages of JWT Authentication

  • Users authenticate only once by logging in.
  • Username and password are not sent with every request.
  • No server-side session storage is required.
  • Stateless authentication improves scalability.
  • Works well with REST APIs, mobile applications, and microservices.
  • Tokens are digitally signed, allowing the server to detect tampering.
  • JWT can carry useful user information (claims), such as:
    • User ID
    • Username
    • Email
    • Roles
    • Permissions
  • Roles and permissions can be used directly for authorization checks.
  • Works seamlessly with Single Page Applications (SPA) and third-party clients.
  • Reduces database lookups when appropriate because user claims travel with the token.

Limitations

  • Tokens cannot be easily revoked before they expire.
  • The payload is encoded, not encrypted.
  • Sensitive information should never be stored in the payload.
  • A JWT is larger than a session ID and is sent with every request.
  • Short-lived access tokens are recommended for better security.
  • If user roles or permissions change, applications often retrieve the latest user information from the database instead of relying solely on the claims inside the token.

Typical Use Cases

  • REST APIs
  • Mobile applications
  • Single Page Applications (SPA)
  • Microservices
  • Cloud-native applications

Comparison

What We Will Implement

In this section, we will implement:
  • User registration.
  • Password hashing.
  • User login.
  • JWT generation.
  • JWT validation.
  • Authentication dependency.
  • Public and protected endpoints.
  • Role-based authorization in the next section.

Task 1 - Create the Project

Goal

Create a new FastAPI project for implementing JWT Authentication using SQLite.

Create the Project

Installed Packages

Create the Project Structure

Create the required files. Windows
macOS / Linux

Update database.py

Update app.py

Run the Application

Verify

Open the following URLs:

Expected Outcome

At the end of this task, you should have:
  • A new FastAPI project.
  • All required dependencies installed.
  • A configured SQLite database.
  • A running FastAPI application.
  • The project ready for implementing JWT authentication.

Task 2 - Create the User Model

Goal

Create the User model and request models required for implementing JWT authentication.

Update models.py

Run the Application

Verify

After restarting the application, a new SQLite database named jwt_auth.db should be created with a user table.

Note

Unlike Session-Based Authentication, JWT Authentication does not require a Session table. The server does not store authentication state. Instead, all authentication information is carried inside the JWT, making the application stateless.

Expected Outcome

At the end of this task, you should have:
  • A User model.
  • Request models for registration and login.
  • A user table created automatically in the SQLite database.

Task 3 - Implement Password Hashing

Goal

Implement password hashing and password verification using bcrypt.

Update auth.py

Run the File

Expected Output

Why Hash Passwords?

Passwords should never be stored as plain text. Instead, only their hashed values are stored in the database. During authentication, the entered password is verified against the stored hash.

Expected Outcome

At the end of this task, you should have:
  • A reusable password hashing function.
  • A reusable password verification function.
  • A secure mechanism for storing user passwords.

Task 4 - Implement User Registration

Goal

Create an API to register new users by storing their username and hashed password in the database.

Update app.py

Import the required modules.
Implement the registration endpoint.

Run the Application

Test the API

Open the Swagger UI.
Call:
Request Body

Verify

Open the user table in the SQLite database. The password should be stored as a hashed value similar to:
instead of

Expected Outcome

At the end of this task, you should have:
  • A working user registration API.
  • Duplicate username validation.
  • Passwords stored securely using hashing.

Task 5 - Implement Login and Generate JWT

Goal

Authenticate users using their username and password. Upon successful authentication, generate a JWT and return it to the client.

Update auth.py

Import the required modules.
Add the following constants.
Implement the JWT generation function.

Update app.py

Import the required modules.
Implement the login endpoint.

How It Works

  1. Receive the username and password.
  2. Retrieve the user from the database.
  3. Verify the password.
  4. Create the JWT payload.
  5. Add an expiration time.
  6. Sign the JWT using the secret key.
  7. Return the JWT to the client.

Run the Application

Test the API

Open the Swagger UI.
Call:
Request Body

Expected Response

Copy the generated JWT. You can paste it into the debugger available at: :contentReference[oaicite:0] Observe the decoded:
  • Header
  • Payload
  • Signature

Expected Outcome

At the end of this task, you should have:
  • A working login API.
  • Username and password verification.
  • JWT generation.
  • Signed access tokens with an expiration time.

Task 6 - Validate JWT and Authenticate Users

Goal

Validate the JWT received in the Authorization header and return the authenticated user.

Update auth.py

Import the required modules.
Implement the authentication dependency.

Nested Dependencies

The authenticate_user() function is itself a dependency and depends on two other dependencies.
  • HTTPBearer() extracts the Bearer token from the Authorization header.
  • get_session() provides a database session.

How It Works

  1. Read the Bearer token from the Authorization header.
  2. Verify the JWT signature.
  3. Verify the token has not expired.
  4. Extract the user ID from the token.
  5. Retrieve the user from the database.
  6. Return the authenticated user.
  7. Return 401 Unauthorized if the token is invalid, expired, or the user does not exist.

Authorization Header

The client must send the JWT as a Bearer token.

Expected Outcome

At the end of this task, you should have:
  • A reusable JWT authentication dependency.
  • JWT signature validation.
  • Token expiration validation.
  • Retrieval of the authenticated user.
  • Proper 401 Unauthorized responses for invalid or expired tokens.

Task 7 - Create Public and Protected Endpoints

Goal

Create public and protected endpoints using JWT authentication.

Update app.py

Import the authentication dependency.
Add the following endpoints.

How It Works

The /public endpoint is accessible to everyone. The /protected endpoint depends on the authenticate_user() dependency. Before executing the endpoint, FastAPI:
  1. Reads the JWT from the Authorization header.
  2. Validates the JWT signature.
  3. Checks whether the token has expired.
  4. Retrieves the authenticated user.
  5. Executes the endpoint only if authentication succeeds.

Run the Application

Test the APIs

Open the Swagger UI.

Step 1 - Register

Register a new user.

Step 2 - Login

Login to receive a JWT.
Copy the returned access_token.

Step 3 - Authorize

Click the Authorize button in Swagger UI. Enter the token in the following format:
Example:
Click Authorize.

Step 4 - Access the Protected Endpoint

Call:
If the token is valid, the response will be:
If the token is missing, invalid, or expired, the response will be: