Every Flask project I started followed the same painful pattern.
Day 1: excited about the idea.
Day 2: still setting up signup and login.
Day 3: debugging OTP email delivery.
Day 4: building an admin panel to manage users.
Day 5: finally writing the first line of the actual idea.
Sound familiar?
After the third time doing this, I stopped and built it properly once — a production-ready Flask authentication and admin foundation I could drop into any new project.
What I actually built
Not a tutorial-level "here's how Flask-Login works" setup. A real, production-ready codebase with actual security practices:
Authentication
- Signup and login with secure session handling
- Email OTP verification — 6-digit code, expires in 10 minutes, resend with cooldown
- Password complexity enforced both client-side and server-side (not just a frontend hint)
- Forgot/reset password with single-use, expiring tokens
- Passwords hashed with bcrypt — never stored plain
Security
- Rate limiting on login, OTP requests, and password reset (Flask-Limiter)
- CSRF protection on every form
- Security headers (X-Frame-Options, X-Content-Type-Options, Referrer-Policy)
- Cache-control headers that prevent browsers from showing stale authenticated pages on back/forward navigation
- Input validation and sanitization on every endpoint
- No secrets hardcoded anywhere — everything from environment variables
Admin panel
- Dashboard with total users, active users, new signups this week
- Searchable, paginated user table
- Enable/disable, promote to admin, or delete users
- Admins land directly on the admin panel after login — no unnecessary redirect through the user dashboard
- Cascade-safe user deletion (no orphaned OTP records crashing the delete)
Code quality
- Flask application factory pattern with Blueprints (auth, admin, main)
- SQLAlchemy ORM with Flask-Migrate for database migrations
- Environment-based config (development/production/testing)
- pytest test suite covering signup, OTP verification, login, wrong password, unverified account blocking
- Background email sending — SMTP happens on a thread so the page responds instantly instead of waiting
UI
- Tailwind CSS via CDN — zero build step required
- Custom fonts (Outfit + Inter + JetBrains Mono)
- Responsive split-panel auth layout
- Show/hide password toggle on every password field
- Flash messages with close button and 5-second auto-dismiss
- Custom error pages for 403, 404, 429, 500 — no raw stack traces ever shown
Things I learned building this
Background threads for email sending matter more than you think. My first version blocked the request while the SMTP connection happened. A misconfigured hostname would hang the entire request for 30+ seconds with no feedback to the user. Moving email dispatch to a daemon thread made every auth action feel instant.
The browser cache is a security issue. Without explicit Cache-Control: no-store headers, pressing back after logout can show a stale authenticated page directly from browser memory — the server never even gets asked. Most Flask tutorials don't cover this.
Cascade deletes need explicit configuration. SQLAlchemy's default behavior when you delete a User with related OTPCode rows is to try setting user_id = NULL on those rows — which fails immediately if user_id is NOT NULL (as it should be). You need cascade="all, delete-orphan" on the relationship, not just the foreign key.
CSP blocks your own inline scripts. I added a Content-Security-Policy header that blocked the inline Tailwind config script defining my custom color tokens. Every button existed and worked but rendered invisible — white text on white background. Spent longer than I'd like to admit on that one.
The stack
- Python 3.10+, Flask 3.0
- SQLAlchemy + Flask-Migrate
- Flask-Login, Flask-WTF, Flask-Mail, Flask-Limiter
- Tailwind CSS (CDN)
- SQLite for development, Postgres-ready for production
- pytest
Where to get it
If you're building a Flask project and don't want to spend days on auth infrastructure, it's available here:
👉 Flask SaaS Starter Kit on Gumroad
Comes with a README, a step-by-step SETUP guide written for people who aren't Flask experts yet, and a CUSTOMIZE guide for extending the user model and swapping email providers.
Happy to go deep on any of the implementation decisions — the rate limiting strategy, the OTP flow design, session security, the background email threading. Drop a comment.
What does your auth setup usually look like for new Flask projects?











