⚡ FastAPI · Security

REST API with JWT Authentication

⏱ 50 minutes⚡ FastAPI🔐 JWT🗄️ SQLAlchemy

JWT (JSON Web Token) is the standard for securing REST APIs. This tutorial builds a complete API with user registration, login, access tokens, refresh tokens, and role-based route protection.

📖 Term: JWT (JSON Web Token)

Definition: A standard (RFC 7519) for creating compact, self-contained tokens with three Base64-encoded parts separated by dots: header.payload.signature.

Purpose: Transmit authentication and authorization information without server-side session state. The client stores and returns the token with each request.

Why here: Each REST API request can be served by any server—JWTs eliminate the need to share session state.

📖 Term: Access Token vs Refresh Token

Definition: Access token = short-lived (30 min), used for requests. Refresh token = long-lived (7 days), stored securely, used only to request a new access token.

Purpose: If an access token is stolen, it's valid for only 30 min. The refresh token stays secret and doesn't circulate in every request (lower risk).

Why here: Standard pattern called "token rotation"—without it, a stolen token = permanent account access.

📖 Term: OAuth2

Definition: An authorization standard (RFC 6749) for delegating resource access. FastAPI uses OAuth2PasswordBearer to extract tokens from the Authorization header.

Purpose: Provide an interoperable framework for authentication—clients, servers, and proxies understand each other.

Why here: IDEs and tools (FastAPI Swagger/docs) recognize OAuth2 and auto-add login buttons.

Project Structure

Directory Layout
api-jwt/
├── main.py              # FastAPI entry point
├── database.py          # SQLAlchemy config
├── models.py            # ORM models
├── schemas.py           # Pydantic schemas (validation)
├── auth.py              # JWT logic
├── routes/
│   ├── auth.py          # /auth/register, /auth/login, /auth/refresh
│   └── users.py         # /users/me, /users/ (admin)
└── requirements.txt
📖 Term: Dependency (Dependency Injection)

Definition: A pattern where a function's dependencies (database, token validation, config) are injected rather than created inside.

Purpose: Make code testable and modular. FastAPI uses Depends() to inject automatically.

Why here: Depends(get_current_user) = FastAPI calls the function and passes the result automatically—framework magic.

📖 Term: Middleware

Definition: A component that intercepts each request before the handler and each response before returning to the client.

Purpose: Add cross-cutting behavior (logging, adding headers, timing) without modifying each route.

Why here: Authentication middleware would intercept everything and verify the token.

📖 Term: Hashing and Bcrypt

Definition: Hashing = irreversible transformation of data into a fixed-size fingerprint. Bcrypt = password hashing algorithm with integrated "salt".

Purpose: Store passwords securely—if the database is stolen, passwords cannot be recovered (unlike MD5 or SHA which are too fast).

Why here: MD5/SHA are obsolete: you can test billions of combinations per second. Bcrypt adds intentional delay (cost factor) to slow brute-force attacks.

📖 Term: Salt

Definition: A random string added to the password before hashing, unique per user.

Purpose: Prevent rainbow tables—two users with the same password won't have the same hash.

Why here: Bcrypt handles salting automatically, it's hidden but essential for security.

📖 Term: Pydantic Model

Definition: A class for validating and parsing data (HTTP requests, JSON responses) with Python types.

Purpose: Automatic validation (is the email valid? is it a number?), type conversion, OpenAPI documentation generation.

Why here: If you declare email: EmailStr, Pydantic rejects requests with invalid emails before your code sees them.

📖 Term: SQLAlchemy and ORM

Definition: ORM (Object-Relational Mapping) = abstraction between Python objects and SQL tables. SQLAlchemy = most popular for Python.

Purpose: Write User.query.filter(...) instead of raw SQL—code becomes portable (switch PostgreSQL to MySQL = no changes).

Why here: ORMs prevent SQL injection (queries are parameterized) and make code more readable.

1. Database and Models

2. JWT Logic

Two tokens serve specific purposes. The access token is short-lived (30 min) = reduced attack surface. If stolen, damage is limited to 30 min. The refresh token is stored securely (not in browser localStorage—use httpOnly cookies) and only circulates to request new access tokens. This is the standard OAuth2 pattern for modern SPAs.

3. Authentication Routes

4. Protected Routes

5. Testing the API

Terminal — complete test
# 1. Register
curl -X POST http://localhost:8000/auth/register \
  -H "Content-Type: application/json" \
  -d '{"username":"alderi","email":"alderi@example.com","password":"MyPassword123!"}'

# 2. Login → retrieve tokens
TOKEN=$(curl -s -X POST http://localhost:8000/auth/login \
  -d "username=alderi@example.com&password=MyPassword123!")

# 3. Access protected route
curl http://localhost:8000/users/me \
  -H "Authorization: Bearer $TOKEN"

# 4. Test admin route (rejected)
curl http://localhost:8000/users/ \
  -H "Authorization: Bearer $TOKEN"
The Authorization: Bearer {token} header is OAuth2 standard. OAuth2PasswordBearer(tokenUrl=...) tells FastAPI: "extract the token from the Authorization header, the part after 'Bearer'". Each test shows a scenario: account creation, login, basic user access, and admin access rejection.
Open http://localhost:8000/docs — FastAPI automatically generates a Swagger interface with an "Authorize" button to test your JWT tokens.