A Flask authentication system with bcrypt password hashing, brute-force rate limiting, and email account activation, backed by MySQL.
Built during a software development and cybersecurity internship at Efood. The goal was a complete account lifecycle — register, activate by email, log in, hold a session, view a profile — with the security controls a real authentication flow needs rather than a minimal demo.
Python · Flask · MySQL · bcrypt · Flask-Limiter · Flask-Mail · Jinja2 · HTML/CSS
- Registration with email activation — accounts are created inactive and enabled only once the emailed token is confirmed
- Session handling — Flask sessions with authentication checks guarding protected routes
- Profile management — view account details for the logged-in user
- Templating — Jinja2 templates sharing a common layout
- Password storage — bcrypt with a per-user salt via
gensalt(), verified withcheckpw(). No plaintext passwords and no unsalted hashes are stored. - Brute-force protection — Flask-Limiter caps the login route at 2 attempts per minute, with a 10 per minute global default
- Activation tokens — generated with
secrets.token_hex(), a cryptographically secure source, rather thanrandom - SQL injection — every query is parameterised, so user input is bound as data and never concatenated into a SQL string
- Input validation — email format and username character set are enforced server-side, so validation cannot be bypassed by crafting requests directly
- Credentials — all secrets live in
config.py, which is gitignored and never committed
main.py Routes, authentication logic, mail sending
config.example.py Settings template — copy to config.py
schema.sql Database schema
templates/ Jinja2 templates (index, register, home, profile, layout)
static/ Stylesheet
pip install -r requirements.txt
cp config.example.py config.py # then fill in your own values
mysql -u root -p < schema.sql
python main.pyThen open http://localhost:5000/efoodproject.
Gmail requires an app password rather than your account password for MAIL_PASSWORD.
Rate limits are tracked in memory, which is fine for a single process but would need a shared store such as Redis behind multiple workers.