⚠️ 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