How to Build and Deploy a Secure FastAPI with JWT & SQLAlchemy
FastAPI has earned a reputation for speed and developer friendliness, but a rapid API is only useful if it keeps data safe. By pairing FastAPI with JWT‑based authentication and SQLAlchemy’s robust ORM, you can spin up a production‑ready service that resists common attacks while staying lightweight. This guide walks through the essential steps—from project scaffolding to cloud deployment—so you end up with a secure, maintainable API you can trust.
Why security matters for FastAPI
Even though FastAPI itself is built on Starlette, which handles a lot of low‑level HTTP work, it doesn’t magically secure your endpoints. Without proper authentication, anyone could query your database, modify records, or expose sensitive information. Adding JWT (JSON Web Tokens) gives you stateless, signed tokens that can be verified without a server‑side session store, while SQLAlchemy shields you from SQL injection attacks through parameterized queries and model validation.
Setting up the project skeleton
Start with a clean virtual environment and install the core dependencies:
- fastapi – the web framework
- uvicorn – ASGI server for local testing
- python‑jwt – token creation and verification
- sqlalchemy – ORM layer
- alembic – database migrations
Organize the codebase into logical modules: app/main.py for the entry point, app/models.py for SQLAlchemy models, app/schemas.py for Pydantic validation, and app/auth.py for JWT utilities. This separation keeps concerns clear and makes testing easier.
Integrating SQLAlchemy for data persistence
Define a Base class with declarative_base() and create a simple User model that includes an email, hashed_password, and is_active flag. Use bcrypt or passlib to hash passwords before storing them, never keep plain text. Then configure a SessionLocal factory that yields database sessions per request. FastAPI’s Depends system can inject this session into path operations, ensuring each request works with a fresh connection.
Running alembic init sets up migration scripts. Whenever you modify a model, run alembic revision --autogenerate -m "describe change" followed by alembic upgrade head. This approach keeps the schema in sync without manual SQL.
Adding JWT authentication
The first step is to decide on a secret key and an expiration window—commonly 30 minutes for access tokens and a longer period for refresh tokens. In app/auth.py, write a create_access_token function that encodes the user ID and expiry timestamp using jwt.encode. A matching verify_token routine should catch ExpiredSignatureError and InvalidTokenError and raise an HTTP 401.
Next, protect routes with a dependency like get_current_user. This function extracts the Authorization: Bearer <token> header, validates the token, queries the user from the database, and returns the model instance. Endpoints that only need read access can use Depends(get_current_user), while admin‑only routes add an extra check on the user's role field.
Deploying the API safely
When moving to production, swap the development server (uvicorn --reload) for a process manager such as gunicorn with the uvicorn.workers.UvicornWorker class. This provides multiple workers and graceful restarts. Store secret keys, database URLs, and other credentials in environment variables—never hard‑code them. Tools like Docker make this straightforward: a Dockerfile copies the code, installs dependencies, and runs gunicorn -k uvicorn.workers.UvicornWorker app.main:app.
Behind the container, place a reverse proxy (NGINX or Traefik) that enforces HTTPS. Obtain a TLS certificate via Let’s Encrypt and configure the proxy to forward only /api/ paths to the FastAPI service. Enabling HTTP security headers—Strict-Transport-Security, Content-Security-Policy, and X-Content-Type-Options—adds another layer of defense.
Common pitfalls and tips
- Never expose raw SQL errors. Catch
SQLAlchemyErrorand return a generic 500 response. - Refresh token rotation. Issue a new refresh token each time the old one is used and invalidate the previous token in a server‑side store to limit token replay attacks.
- Rate limiting. Use a middleware like
slowapito throttle requests per IP, protecting against brute‑force login attempts. - Testing authentication. Write integration tests that generate a JWT, call a protected endpoint, and assert the expected status code. This catches misconfigurations early.
FAQ
What makes JWT suitable for FastAPI?
JWTs are stateless, meaning the server doesn’t need to keep a session database. FastAPI’s dependency injection can decode and verify a token on each request with minimal overhead, keeping response times low while still providing strong cryptographic guarantees.
Can I use PostgreSQL with SQLAlchemy in this setup?
Absolutely. Just install psycopg2-binary (or asyncpg for async support) and set the SQLALCHEMY_DATABASE_URI to postgresql://user:pass@host/dbname. The ORM code stays the same; only the connection string changes.
Do I need a separate refresh‑token endpoint?
It’s best practice to separate access‑token issuance from refresh‑token renewal. The refresh endpoint should require a valid refresh token, issue a new access token, and optionally rotate the refresh token. This limits the window an attacker has if a token is compromised.
How do I handle CORS for a front‑end client?
FastAPI includes a CORSMiddleware. Configure it with the client’s origin (e.g., https://myapp.com) and allow only the necessary HTTP methods. Avoid using allow_origins=["*"] in production, as it defeats the purpose of cross‑origin protection.