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.
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.
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.
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.
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
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.
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.
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.
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.
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.
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. 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"