100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Python

User Authentication in Flask

Understand the core building blocks of authenticating users in Flask: password hashing, login forms, and session-backed identity.

APIs & AuthIntermediate10 min readJul 10, 2026
Analogies

The Anatomy of Authentication

Authentication answers 'who is this user?' and in a Flask application it typically involves three pieces: a User model with a securely hashed password (never plaintext), a login route that verifies submitted credentials against the stored hash, and a mechanism — usually Flask's signed session cookie — that remembers the authenticated user across subsequent requests. Flask itself has no built-in authentication system; it provides the primitives (sessions, request handling) that extensions like Flask-Login or hand-rolled code build upon.

🏏

Cricket analogy: Authentication is like a stadium's accreditation check: a steward verifies your credential badge (hashed password check) once at the gate, then issues a wristband (session cookie) that gets you back in through any entrance for the rest of the day without re-verifying every time.

Hashing Passwords Correctly

Passwords must never be stored in plaintext or with fast general-purpose hashes like MD5 or SHA-256 alone, because those are too fast to brute-force at scale. Werkzeug (a Flask dependency) provides generate_password_hash(), which by default uses a strong, salted algorithm (scrypt in modern Werkzeug versions), and check_password_hash() to verify a plaintext attempt against the stored hash without ever needing to reverse it. A salt embedded in the hash ensures that two users with the same password produce different stored hashes, defeating precomputed rainbow-table attacks.

🏏

Cricket analogy: Salting a password hash is like each ground having its own unique pitch conditions, so a bowling strategy (attack) that works on a flat Chepauk pitch won't transfer to a bouncy WACA pitch — a precomputed attack against one user's hash doesn't help against another's differently salted hash.

python
from flask import Flask, request, session, redirect, url_for, jsonify
from werkzeug.security import generate_password_hash, check_password_hash

app = Flask(__name__)
app.secret_key = 'use-a-long-random-value-from-os.urandom(32)'

users = {
    'ada': generate_password_hash('correct-horse-battery-staple')
}

@app.route('/login', methods=['POST'])
def login():
    data = request.get_json(silent=True) or {}
    username = data.get('username')
    password = data.get('password')

    stored_hash = users.get(username)
    if stored_hash is None or not check_password_hash(stored_hash, password):
        return jsonify({'error': 'Invalid credentials'}), 401

    session['user_id'] = username
    session.permanent = True
    return jsonify({'message': f'Welcome, {username}'}), 200

@app.route('/logout', methods=['POST'])
def logout():
    session.pop('user_id', None)
    return jsonify({'message': 'Logged out'}), 200

@app.route('/profile')
def profile():
    if 'user_id' not in session:
        return jsonify({'error': 'Not authenticated'}), 401
    return jsonify({'user': session['user_id']}), 200

Verifying Credentials and Establishing Identity

On login, the view looks up the user record by username, calls check_password_hash(stored_hash, submitted_password) to compare securely (using a constant-time comparison internally to resist timing attacks), and, only on success, writes an identifier into Flask's session — never the password itself. Every subsequent request that includes the browser's signed session cookie can then check session['user_id'] to determine whether the caller is authenticated, without hitting the database on every request just to check a login flag.

🏏

Cricket analogy: Constant-time comparison in check_password_hash is like a third umpire taking exactly the same review time whether the decision is obviously out or a tight call, so no one can infer the answer just from how long the review takes — timing attacks are defeated the same way.

Never store a password reset token or session identifier that reveals information about the password itself. Store only opaque, unguessable identifiers (like a user ID or a securely random token), and always regenerate the session (e.g. by clearing and rebuilding it) after a successful login to prevent session fixation attacks.

Common Pitfalls

A frequent mistake is comparing passwords with Python's == operator on raw hashes or, worse, storing passwords in reversible encryption instead of a one-way hash — both undermine the entire point of hashing. Another common issue is failing to set app.secret_key to a long, random, environment-provided value, which leaves the session cookie's signature forgeable; a hardcoded or short secret_key checked into source control is a critical vulnerability that lets an attacker forge arbitrary session data, including admin login state.

🏏

Cricket analogy: Comparing hashes with == instead of a timing-safe function is like judging a run-out purely by eyeballing it from the boundary rather than using the specialized snickometer review process — it looks similar but skips the safeguard that actually protects against a wrong call.

Never hardcode app.secret_key as a literal string in source code committed to version control. Load it from an environment variable (e.g. os environ.get('FLASK_SECRET_KEY')) generated with something like secrets.token_hex(32), and rotate it if it's ever exposed.

  • Flask has no built-in authentication system; it provides sessions and request handling that authentication logic builds on top of.
  • Always hash passwords with a strong algorithm like Werkzeug's generate_password_hash() (scrypt by default) — never store plaintext.
  • Use check_password_hash() for verification; it performs a constant-time comparison to resist timing attacks.
  • On successful login, store only a non-sensitive identifier (like a user ID) in the session, never the password.
  • Set app.secret_key to a long, random value loaded from environment configuration, never hardcoded in source.
  • Regenerate or rebuild the session after login to prevent session fixation attacks.
  • Check session state on protected routes to determine whether a request is authenticated.

Practice what you learned

Was this page helpful?

Topics covered

#Python#FlaskStudyNotes#WebDevelopment#UserAuthenticationInFlask#User#Authentication#Flask#Anatomy#Security#StudyNotes#SkillVeris