Part of My Python Learning Streak. Day 15 extends the Day 14 User-Course API with a full JWT authentication system: registration, login, and protected routes.
- Password hashing with bcrypt via
passlib— plain passwords are never stored - JWT-based login using
python-jose— stateless auth, no server-side sessions - Token expiry — access tokens expire after 60 minutes
- Protected routes —
get_current_userdependency guards endpoints that require a logged-in user - One-to-many relationship — each user can own multiple courses
- FastAPI
- SQLAlchemy (SQLite)
- python-jose (JWT encode/decode)
- passlib (bcrypt password hashing)
pip install fastapi uvicorn sqlalchemy python-jose[cryptography] passlib[bcrypt] python-multipartpython-multipart is required for OAuth2PasswordRequestForm to parse the login form data — easy to miss since it's not always listed alongside the other auth libraries.
Run the server:
uvicorn main:app --reloadThen open http://127.0.0.1:8000/docs for the interactive Swagger UI.
| Method | Path | Auth required | Description |
|---|---|---|---|
| POST | /register |
No | Register a new user (name, email, password) |
| POST | /login |
No | Log in with email + password, returns a JWT |
| GET | /me |
Yes | Return the logged-in user's profile |
| POST | /courses |
Yes | Create a course owned by the logged-in user |
| GET | /users/{user_id} |
Yes | Get a user's profile and their courses |
| GET | /users |
No | List all users |
| DELETE | /users/{user_id} |
Yes (self only) | Delete your own account |
- Register —
POST /registerwithname,email,password. The password is hashed with bcrypt before it touches the database. - Login —
POST /loginwithusername(your email) andpasswordas form data. On success, you get back anaccess_tokenandtoken_type: bearer. - Authenticated requests — send the token in the
Authorizationheader asBearer <token>. FastAPI'sOAuth2PasswordBearerextracts it automatically. - Token validation —
get_current_userdecodes the token, reads the user ID from thesubclaim, and fetches the user from the database. Missing, malformed, or expired tokens return401 Unauthorized.
POST /registerto create an account.POST /loginand copy theaccess_tokenfrom the response.- Click Authorize at the top of the page and paste the token.
- Try
GET /me— you should see your own profile. - Click Authorize again and log out (clear the token) — protected routes should now return
401.
.
├── auth.py # JWT creation/validation, password hashing, get_current_user dependency
├── database.py # SQLAlchemy engine, session, and get_db dependency
├── main.py # FastAPI routes
├── models.py # User and Course ORM models
└── schemas.py # Pydantic request/response schemas
SECRET_KEYinauth.pyis hardcoded for learning purposes. In a real deployment, load it from an environment variable instead.- SQLite is used for simplicity (
user_course.db). Delete this file if you change the model schema, so SQLAlchemy can recreate the tables.
Ati (@studyhaxer) — Day 15 of a 60-day Python learning streak.