β οΈ Note: This is a lightweight, startup-friendly template designed for small to mid-sized apps with a low userbase and authenticated users only. It intentionally keeps things simple β no Redis caching, no advanced queue systems, no heavy load-prevention infrastructure. If you're building an MVP, side project, or internal tool that needs to handle thousands of users, this is a clean and solid starting point. For larger scale, you'll want to layer on caching (Redis), a task queue (Celery/RQ), and a more advanced rate-limiter (e.g., API gateway orslowapiwith Redis backend).
A passwordless authentication backend built with FastAPI, SQLAlchemy (async), and PostgreSQL. Supports OTP login via email, Google OAuth, and includes rate limiting with SlowAPI β no passwords, no password reset, just simple and secure authentication.
- π Passwordless Auth β OTP via email + Google OAuth (no passwords needed)
- π± Support Both Web/Mobile β One API use anywhere
- π§ Email OTP β 6-digit codes sent via Resend
- π Google OAuth β One-click sign-in with Google
- πͺ Dual Client Support β httpOnly cookies for web, tokens in body for mobile
- π‘οΈ CSRF Protection β Middleware-based CSRF protection for cookie auth
- π Refresh Tokens β Long-lived refresh tokens with database-backed revocation
- π Async PostgreSQL β SQLAlchemy 2.0 async with asyncpg
- β‘ FastAPI β High-performance async Python API
- π Rate Limiting β SlowAPI for API abuse prevention
- ποΈ Alembic Migrations β Production-ready database migrations
- π CORS β Configurable cross-origin resource sharing
- π₯ Health Check β Database connectivity monitoring
| Layer | Technology |
|---|---|
| Framework | FastAPI |
| Database | PostgreSQL (async via asyncpg) |
| ORM | SQLAlchemy 2.0 (async) |
| Migrations | Alembic |
| Auth | JWT (PyJWT) + Google OAuth |
| Resend | |
| Rate Limiting | SlowAPI |
| Validation | Pydantic v2 |
| Config | pydantic-settings |
.
βββ app/
β βββ __init__.py
β βββ main.py # FastAPI app entry point, SlowAPI setup
β βββ api/
β β βββ v1/
β β βββ __init__.py # API router aggregation
β β βββ auth.py # Authentication endpoints (rate-limited)
β β βββ health.py # Health check endpoint
β βββ core/
β β βββ config.py # Pydantic settings (env vars)
β β βββ database.py # Async SQLAlchemy engine & session
β βββ deps/
β β βββ auth.py # Auth dependencies (get_current_user)
β βββ helpers/
β β βββ google_auth.py # Google token verification
β β βββ handle_cookies.py # Cookie utilities (set/clear auth cookies)
β βββ middleware/
β β βββ auth.py # CSRF protection middleware
β βββ models/
β β βββ app_users.py # User model (OTP + Google)
β β βββ otp_code.py # OTP codes model
β β βββ refresh_token.py # Refresh tokens model
β βββ schemas/
β β βββ auth_schema.py # Pydantic request/response schemas
β βββ templates/
β β βββ otp_email.html # HTML email template
β β βββ otp_email.txt # Plain text email template
β βββ utils/
β βββ auth_util.py # JWT creation, hashing, CSRF token
β βββ device_check.py # Mobile vs Web client detection
β βββ email_otp.py # OTP email sending via Resend
β βββ otp_generator.py # OTP code generation & hashing
βββ alembic/ # Alembic migration files
β βββ versions/
β βββ env.py
β βββ script.py.mako
βββ templates/
β βββ otp_email.html # HTML email template (legacy)
β βββ otp_email.txt # Plain text email template (legacy)
βββ .env.example # Environment variables template
βββ .gitignore
βββ alembic.ini # Alembic configuration
βββ requirements.txt
βββ README.md
- Python 3.14+
- PostgreSQL (running locally or remote)
- Google Cloud Console project (for OAuth)
- Resend account (for sending OTP emails)
git clone https://github.com/mvish77/fastapi-basic-template.git
cd fastapi-basic-templatepython -m venv venv
# Windows
venv\Scripts\activate
# macOS/Linux
source venv/bin/activatepip install -r requirements.txtcp .env.example .envEdit .env with your values (see Environment Variables below).
# Using psql
createdb your_database
# Or using any PostgreSQL client, create your databasealembic upgrade headuvicorn app.main:app --reload --host 0.0.0.0 --port 8000The API will be available at:
- API:
http://localhost:8000 - Swagger Docs:
http://localhost:8000/docs - ReDoc:
http://localhost:8000/redoc
| Method | Endpoint | Description | Rate Limit |
|---|---|---|---|
POST |
/api/v1/auth/send-otp |
Send 6-digit OTP to email | 5 / 5 min |
POST |
/api/v1/auth/verify-otp |
Verify OTP & login/signup | 5 / 5 min |
POST |
/api/v1/auth/google |
Google OAuth login/signup | 5 / 5 min |
POST |
/api/v1/auth/refresh |
Refresh access token | Default |
POST |
/api/v1/auth/logout |
Logout & revoke refresh token | Default |
GET |
/api/v1/auth/csrf-token |
Get CSRF token (web only) | Default |
GET |
/api/v1/auth/me |
Get current user info | Default |
| Method | Endpoint | Description |
|---|---|---|
GET |
/ |
Root endpoint (app info) |
GET |
/api/v1/health |
Health check (API + DB status) |
1. Client sends email β POST /api/v1/auth/send-otp
2. Server generates 6-digit OTP, sends via Resend email
3. User receives OTP in email (valid for 10 minutes)
4. Client submits email + OTP β POST /api/v1/auth/verify-otp
5. Server verifies OTP (SHA-256 hash comparison):
- New email β Auto-creates account (signup)
- Existing email β Logs in (login)
6. Returns JWT tokens:
- Web: httpOnly cookies (access_token + refresh_token)
- Mobile: Tokens in response body
1. Client initiates Google OAuth flow (frontend)
2. Google returns ID token to client
3. Client sends ID token β POST /api/v1/auth/google
4. Server verifies token with Google
5. Returns JWT tokens (same as OTP flow)
1. Access token expires (24 hours)
2. Client sends refresh token β POST /api/v1/auth/refresh
3. Server validates refresh token against database
4. Returns new access token
5. Refresh tokens are long-lived (30 days) and can be revoked on logout
This project uses Resend for sending OTP emails.
When RESEND_API_KEY is not set, OTP codes are logged to the console instead of being sent via email. This allows local development without an email service.
============================================================
π§ DEV MODE: OTP Email
============================================================
To: user@example.com
OTP Code: 482916
Valid for: 10 minutes
============================================================
Set these environment variables to enable email delivery:
RESEND_API_KEY=re_xxxxxxxxxxxxxxxxxxxxxxxxxx
EMAIL_FROM=My App <noreply@your_domain.com>
APP_NAME=My App
APP_URL=https://your_domain.comYou'll need to verify your sending domain in Resend before sending emails in production.
- Go to Google Cloud Console
- Create a project (or select existing)
- Navigate to APIs & Services β Credentials
- Create OAuth 2.0 Client ID (Web application type)
- Add authorized redirect URIs
- Copy the Client ID and Client Secret to your
.env:
GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-client-secretThis project uses SlowAPI for request rate limiting.
- Global: 100 requests/minute per IP
- Auth endpoints (
send-otp,verify-otp,google): 5 requests per 5 minutes per IP
Rate limits are applied per IP address using SlowAPI's @limiter.limit() decorator. When a limit is exceeded, the API returns a 429 Too Many Requests response.
Scaling note: The default in-memory rate limiter works fine for single-server deployments. For distributed setups, configure SlowAPI with a Redis backend.
This project uses Alembic for database migrations.
# Apply all migrations
alembic upgrade head
# Rollback one migration
alembic downgrade -1
# Generate a new migration after model changes
alembic revision --autogenerate -m "description of changes"
# View migration history
alembic historyCopy .env.example to .env and configure:
# βββ JWT Authentication βββββββββββββββββββββββββββββββββ
JWT_SECRET_KEY=your-256-bit-secret # Generate a secure random key
JWT_ALGORITHM=HS256
JWT_ACCESS_TOKEN_EXPIRE_MINUTES=1440 # 24 hours
JWT_REFRESH_TOKEN_EXPIRE_DAYS=30 # 30 days
# βββ CSRF Protection ββββββββββββββββββββββββββββββββββββ
CSRF_SECRET_KEY=your-csrf-secret # Generate a secure random key
CSRF_TOKEN_EXPIRE_MINUTES=60
# βββ Google OAuth βββββββββββββββββββββββββββββββββββββββ
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
# βββ Resend Email Service βββββββββββββββββββββββββββββββ
RESEND_API_KEY=re_xxxxxxxxxxxxxxxxxxxxxxxxxx
EMAIL_FROM=My App <noreply@your_domain.com>
APP_NAME=My App
APP_URL=https://your_domain.com
# βββ OTP Settings βββββββββββββββββββββββββββββββββββββββ
OTP_EXPIRY_MINUTES=10
# βββ Cookie Domain ββββββββββββββββββββββββββββββββββββββ
MY_DOMAIN=localhost # e.g., .your_domain.com for all subdomains
β οΈ Never commit.envto version control. Always use.env.exampleas a template.
| Feature | Description |
|---|---|
| Passwordless | No passwords to store, leak, or reset |
| OTP Expiry | Codes expire after 10 minutes |
| OTP Rate Limiting | 5 requests per 5 min per IP on auth endpoints |
| Global Rate Limiting | 100 requests/minute per IP (configurable) |
| OTP Hash Verification | OTPs stored and verified using SHA-256 hashing |
| Previous OTP Invalidation | New OTP deletes unused old ones |
| CSRF Middleware | Automatic CSRF protection for cookie-based auth |
| httpOnly Cookies | Tokens stored in httpOnly cookies for web (XSS-safe) |
| Token Revocation | Refresh tokens revoked on logout, stored hashed in DB |
| Secure Cookies | Secure + SameSite flags in production |
| Docs Disabled | Swagger/ReDoc disabled in production |
| Column | Type | Description |
|---|---|---|
user_id |
UUID (PK) | Auto-generated user ID |
email |
String(150) | Unique, indexed email |
display_name |
String(100) | User's display name |
avatar_url |
Text | Profile picture URL |
auth_provider |
Enum | email_otp or google |
google_id |
String(255) | Google OAuth ID (nullable) |
status |
Enum | active, suspended, deleted |
role |
Enum | user, admin, super_admin |
email_verified |
Boolean | Auto-verified via OTP/Google |
last_login_at |
Timestamp | Last login time |
created_at |
Timestamp | Account creation time |
updated_at |
Timestamp | Last update time |
| Column | Type | Description |
|---|---|---|
email |
String | Email address |
otp_code |
String | 6-digit OTP (plaintext for dev) |
otp_hash |
String | SHA-256 hash of OTP |
is_verified |
Boolean | Whether OTP was used |
attempts |
Integer | Failed verification attempts |
expires_at |
Timestamp | OTP expiration time |
| Column | Type | Description |
|---|---|---|
token_hash |
String | SHA-256 hash of refresh token |
user_id |
UUID (FK) | Associated user |
expires_at |
Timestamp | Token expiration |
device_info |
String | User-Agent string |
revoked_at |
Timestamp | When token was revoked |
The API automatically detects whether the client is a web browser or mobile app and responds accordingly:
| Web Browser | Mobile App | |
|---|---|---|
| Auth method | httpOnly cookies | Authorization: Bearer header |
| Token storage | Response cookies | Response body |
| Refresh flow | Cookie-based | Request body |
| CSRF | Required (middleware-protected) | Not required |
uvicorn app.main:app --reload- Swagger docs available at
/docs - OTP codes logged to console (no email required)
- Rate limits still apply
ENV=production uvicorn app.main:app --host 0.0.0.0 --port 8000- Swagger/ReDoc/OpenAPI are disabled
- Secure cookie flags are enabled
- Use Alembic for database migrations
- Generate secure
JWT_SECRET_KEYandCSRF_SECRET_KEY - Set up Resend account and verify sending domain
- Configure Google OAuth credentials
- Set
ENV=production - Configure
MY_DOMAINfor your production domain - Update
CORS_ALLOWED_ORIGINSfor your frontend URL - Run
alembic upgrade headon production database - Remove any debug/logging of OTP codes in production
This project is open source and available under the MIT License.
Built with β€οΈ using FastAPI