Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
Build a Flask login API by defining its JSON contract, storing password hashes instead of passwords, and using a session to remember a successfully authenticated user. Flask’s test client can then verify valid and invalid login attempts, protected access, and logout without starting a live server.
Choose the API contract before writing the routes
Flask’s official authentication tutorial demonstrates browser-oriented registration and login forms backed by SQLite and sessions; it is not a REST or JWT API. The example below adapts the tutorial’s authentication mechanics to JSON requests and responses. Decide which fields clients send, which responses they receive, and how routes indicate that authentication is required.
This example accepts JSON with username and password. It returns JSON for API outcomes rather than redirecting to a browser page. The status codes and response shapes below are one explicit contract, not behavior prescribed by Flask.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →| Request or outcome | Example response |
|---|---|
POST /auth/register with required fields |
201 and {"message":"registered"} |
| Registration with missing fields or an existing username | 400 for missing fields; 409 for a duplicate username |
POST /auth/login with valid credentials |
200 and {"message":"logged in"}; a session is set |
| Login with invalid credentials | 401 and {"error":"invalid credentials"} |
| Request to a protected route without a logged-in session | 401 and {"error":"authentication required"} |
POST /auth/logout |
200 and {"message":"logged out"}; the session is cleared |
For a production-facing API, consider using the same public login error for an unknown username and a wrong password. Distinct messages can reveal whether an account exists; the tradeoff is less precise feedback for legitimate users. This is a design choice, not a Flask default.
#1 Best Overall
Create the authentication blueprint and database operations
Keep authentication endpoints in a blueprint and register it with the application factory. The following compact example assumes a SQLite connection helper named get_db(), a user table with id, username, and password columns, and a factory that configures SECRET_KEY and initializes the database. The password column stores a hash.
from flask import Blueprint, jsonify, request, session
from werkzeug.security import check_password_hash, generate_password_hash
bp = Blueprint("auth", __name__, url_prefix="/auth")
@bp.post("/register")
def register():
data = request.get_json(silent=True) or {}
username = data.get("username")
password = data.get("password")
if not username or not password:
return jsonify(error="username and password are required"), 400
db = get_db()
existing = db.execute(
"SELECT id FROM user WHERE username = ?", (username,)
).fetchone()
if existing:
return jsonify(error="username already exists"), 409
db.execute(
"INSERT INTO user (username, password) VALUES (?, ?)",
(username, generate_password_hash(password)),
)
db.commit()
return jsonify(message="registered"), 201
@bp.post("/login")
def login():
data = request.get_json(silent=True) or {}
username = data.get("username")
password = data.get("password")
if not username or not password:
return jsonify(error="username and password are required"), 400
user = get_db().execute(
"SELECT * FROM user WHERE username = ?", (username,)
).fetchone()
if user is None or not check_password_hash(user["password"], password):
return jsonify(error="invalid credentials"), 401
session.clear()
session["user_id"] = user["id"]
return jsonify(message="logged in")
@bp.post("/logout")
def logout():
session.clear()
return jsonify(message="logged out")
Register the blueprint with the application, for example with app.register_blueprint(auth.bp), using the module name where the blueprint is defined. Adapt imports and database helpers to your project layout.
Rank #2
Hash passwords and bind SQL values
At registration, generate_password_hash() produces the value to store; do not save the submitted password itself. At login, check_password_hash() verifies the submitted value against the stored hash. SQL placeholders (? for SQLite) keep user-provided values separate from SQL syntax. Use the placeholder style required by your database driver.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Set session state only after verification
The login route clears prior session data and writes the user identifier only after the account lookup and password check succeed. That prevents failed login attempts from becoming authenticated and avoids carrying old session state into a new login. Routes that need a current user should load it from the stored identifier and handle a missing or stale account appropriately.
Protect routes and understand what a Flask session means
A protected API route can check the session’s user identifier and return a JSON response when it is absent. For example:
@bp.get("/account")
def account():
user_id = session.get("user_id")
if user_id is None:
return jsonify(error="authentication required"), 401
user = get_db().execute(
"SELECT id, username FROM user WHERE id = ?", (user_id,)
).fetchone()
if user is None:
session.clear()
return jsonify(error="authentication required"), 401
return jsonify(id=user["id"], username=user["username"])
Flask’s default session stores serialized values in a client-side cookie and signs it cryptographically. The browser can read cookie contents; the signature helps prevent modification without the secret key, but it does not make the contents private. Keep session values limited to appropriate identifiers and non-sensitive state. Do not put passwords or secrets in the session.
Set a strong, unpredictable production SECRET_KEY outside public source code. A publicly known development key can allow attackers to modify session cookies. Keep development configuration separate from production configuration.
Test the flow with Flask’s test client
The test client sends requests to the application without starting a live server. Configure a test app and create its client in fixtures; Flask’s testing documentation describes the client, application contexts, redirects, and session inspection. The fixture details depend on your application factory and database setup.
Best Value
import pytest
from yourapp import create_app
@pytest.fixture
def app():
app = create_app({
"TESTING": True,
"SECRET_KEY": "test-only-secret",
# Configure a separate test database here.
})
yield app
@pytest.fixture
def client(app):
return app.test_client()
Use a separate test database and seed or register a known account in each test. Do not copy a test secret key into production settings.
Check successful and unsuccessful login
def test_login_valid_credentials(client):
client.post("/auth/register", json={
"username": "sam", "password": "correct horse"
})
response = client.post("/auth/login", json={
"username": "sam", "password": "correct horse"
})
assert response.status_code == 200
assert response.get_json() == {"message": "logged in"}
@pytest.mark.parametrize("credentials", [
{"username": "unknown", "password": "anything"},
{"username": "sam", "password": "wrong"},
])
def test_login_invalid_credentials(client, credentials):
client.post("/auth/register", json={
"username": "sam", "password": "correct horse"
})
response = client.post("/auth/login", json=credentials)
assert response.status_code == 401
assert response.get_json() == {"error": "invalid credentials"}
Also test missing fields and duplicate registration against the status codes and response bodies you chose in the contract. A request with malformed or absent JSON should follow the same deliberate validation policy as other missing input.
Verify protected access, session state, and logout
def test_account_requires_login(client):
response = client.get("/auth/account")
assert response.status_code == 401
def test_login_allows_account_and_logout_removes_access(client):
client.post("/auth/register", json={
"username": "sam", "password": "correct horse"
})
client.post("/auth/login", json={
"username": "sam", "password": "correct horse"
})
with client.session_transaction() as session:
assert "user_id" in session
assert client.get("/auth/account").status_code == 200
assert client.post("/auth/logout").status_code == 200
assert client.get("/auth/account").status_code == 401
session_transaction() lets a test inspect or prepare session values. Alternatively, the test client can be used as a context manager when a test needs to inspect request context. Test the observable protected-route behavior as well as session contents so that assertions cover what clients actually experience.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsMake the tests match your real response behavior
The tutorial’s browser-oriented flow redirects after successful login and may redirect anonymous users to a login page. A JSON API commonly returns a status and body instead. If your API intentionally redirects, use follow_redirects=True when testing the destination; otherwise assert the API response directly. Do not assert a redirect merely because the browser tutorial uses one.
Flask’s tutorial uses pytest patterns for authentication tests and discusses coverage. Coverage helps identify code paths that tests never execute, but even 100% coverage does not prove that behavior is correct. Assertions should check the contract, including failure cases and the effect of logout.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

