Flask: Custom Session Backends
Custom session backends in Flask allow developers to replace the default cookie-based session storage with alternative systems like databases, Redis, or file-based storage for enhanced scalability, security, and flexibility. Building on Flask Authentication, Flask User Registration, Flask User Login and Logout, Flask Password Hashing with Flask-Bcrypt, Flask Role-Based Access Control, Flask Protecting Routes with Flask-Login, Flask Managing User Sessions, Flask Using Flask Sessions, Flask Session Security, Flask Querying the Database, and Flask Relationships in Models, custom backends integrate with Flask-Login and NumPy Array Operations for efficient data handling. This tutorial explores Flask custom session backends, covering implementation, security considerations, and practical use cases in web development.
01. Why Use Custom Session Backends?
Flask’s default session storage uses signed cookies, which are convenient but limited in size (typically 4KB) and unsuitable for sensitive or large data. Custom session backends (e.g., Redis, SQLAlchemy, or MongoDB) offer: - Scalability: Handle large session data or distributed systems. - Security: Store sensitive data server-side, reducing client-side exposure. - Flexibility: Support complex session management needs. These backends are ideal for applications like e-commerce platforms, social media sites, or enterprise systems requiring robust session handling.
Example: Basic Custom Session Backend with SQLAlchemy
from flask import Flask, session, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime, timedelta
import json
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///sessions.db'
app.config['SECRET_KEY'] = 'secure-key-123'
db = SQLAlchemy(app)
class Session(db.Model):
id = db.Column(db.String(255), primary_key=True)
data = db.Column(db.Text, nullable=False)
expiry = db.Column(db.DateTime, nullable=False)
class CustomSessionInterface:
def __init__(self):
self.session_class = dict
def open_session(self, app, request):
sid = request.cookies.get(app.session_cookie_name)
if sid:
session_record = Session.query.filter_by(id=sid).first()
if session_record and session_record.expiry > datetime.utcnow():
return self.session_class(json.loads(session_record.data))
return self.session_class()
def save_session(self, app, session, response):
domain = self.get_cookie_domain(app)
if not session:
response.delete_cookie(app.session_cookie_name, domain=domain)
return
sid = secrets.token_hex(16)
expiry = datetime.utcnow() + timedelta(minutes=30)
session_data = json.dumps(dict(session))
session_record = Session(id=sid, data=session_data, expiry=expiry)
db.session.merge(session_record)
db.session.commit()
response.set_cookie(app.session_cookie_name, sid, max_age=1800, secure=True, httponly=True, samesite='Lax')
app.session_interface = CustomSessionInterface()
@app.route('/set_session', methods=['POST'])
def set_session():
session['username'] = request.form['username']
return redirect(url_for('get_session'))
@app.route('/get_session')
def get_session():
username = session.get('username', 'Guest')
return f"Welcome, {username}"
with app.app_context():
db.create_all()
if __name__ == '__main__':
app.run(debug=True, ssl_context='adhoc')
Output:
Welcome, Alice (after POST to /set_session with username='Alice')
Welcome, Guest (if no session or after expiry)
Explanation:
CustomSessionInterface
- Overrides Flask’s default session handling.open_session
- Retrieves session data from the database if valid.save_session
- Stores session data in the database with a unique session ID.- Secure cookie settings ensure safe session ID transmission.
02. Key Custom Session Backend Techniques
Custom session backends provide flexibility to tailor session storage to application needs. Below is a summary of key techniques and their applications:
Technique | Description | Use Case |
---|---|---|
Database Backend | Store sessions in SQLAlchemy/MongoDB | Persistent, scalable sessions |
Redis Backend | Use Redis for fast, in-memory storage | High-performance sessions |
Session Security | Secure session ID transmission | Prevent hijacking |
Session Expiry | Implement timeout mechanisms | Limit session duration |
Integration with Flask-Login | Combine with authentication | Secure user sessions |
2.1 Database Backend with SQLAlchemy
Example: SQLAlchemy Session Backend
from flask import Flask, session, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime, timedelta
import json, secrets
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db_sessions.db'
app.config['SECRET_KEY'] = 'secure-key-456'
db = SQLAlchemy(app)
class Session(db.Model):
id = db.Column(db.String(255), primary_key=True)
data = db.Column(db.Text, nullable=False)
expiry = db.Column(db.DateTime, nullable=False)
class CustomSessionInterface:
def open_session(self, app, request):
sid = request.cookies.get(app.session_cookie_name)
if sid:
session_record = Session.query.filter_by(id=sid).first()
if session_record and session_record.expiry > datetime.utcnow():
return dict(json.loads(session_record.data))
return {}
def save_session(self, app, session, response):
domain = self.get_cookie_domain(app)
if not session:
Session.query.filter_by(id=request.cookies.get(app.session_cookie_name)).delete()
db.session.commit()
response.delete_cookie(app.session_cookie_name, domain=domain)
return
sid = request.cookies.get(app.session_cookie_name, secrets.token_hex(16))
expiry = datetime.utcnow() + timedelta(minutes=30)
session_data = json.dumps(dict(session))
session_record = Session(id=sid, data=session_data, expiry=expiry)
db.session.merge(session_record)
db.session.commit()
response.set_cookie(app.session_cookie_name, sid, max_age=1800, secure=True, httponly=True, samesite='Lax')
app.session_interface = CustomSessionInterface()
@app.route('/set_data', methods=['POST'])
def set_data():
session['data'] = request.form['data']
return redirect(url_for('get_data'))
@app.route('/get_data')
def get_data():
data = session.get('data', 'None')
return f"Data: {data}"
with app.app_context():
db.create_all()
if __name__ == '__main__':
app.run(debug=True, ssl_context='adhoc')
Output:
Data: test (after POST to /set_data with data='test')
Data: None (if no session or expired)
Explanation:
- Stores session data in a SQLite database, reducing client-side storage.
- Implements expiry to clean up old sessions.
2.2 Redis Backend
Example: Redis Session Backend
from flask import Flask, session, request, redirect, url_for
from redis import Redis
import json, secrets
from datetime import timedelta
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secure-key-789'
redis_client = Redis(host='localhost', port=6379, db=0)
class RedisSessionInterface:
def __init__(self, redis, prefix='session:'):
self.redis = redis
self.prefix = prefix
def open_session(self, app, request):
sid = request.cookies.get(app.session_cookie_name)
if sid:
data = self.redis.get(self.prefix + sid)
if data:
return dict(json.loads(data))
return {}
def save_session(self, app, session, response):
domain = self.get_cookie_domain(app)
if not session:
sid = request.cookies.get(app.session_cookie_name)
if sid:
self.redis.delete(self.prefix + sid)
response.delete_cookie(app.session_cookie_name, domain=domain)
return
sid = request.cookies.get(app.session_cookie_name, secrets.token_hex(16))
session_data = json.dumps(dict(session))
self.redis.setex(self.prefix + sid, timedelta(minutes=30), session_data)
response.set_cookie(app.session_cookie_name, sid, max_age=1800, secure=True, httponly=True, samesite='Lax')
app.session_interface = RedisSessionInterface(redis_client)
@app.route('/set_session', methods=['POST'])
def set_session():
session['username'] = request.form['username']
return redirect(url_for('get_session'))
@app.route('/get_session')
def get_session():
username = session.get('username', 'Guest')
return f"Welcome, {username}"
if __name__ == '__main__':
app.run(debug=True, ssl_context='adhoc')
Output:
Welcome, Bob (after POST to /set_session with username='Bob')
Welcome, Guest (if no session or expired)
Explanation:
- Uses Redis for fast, in-memory session storage.
setex
- Sets session data with a 30-minute expiry.
2.3 Session Security
Example: Secure Custom Backend
from flask import Flask, session, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime, timedelta
import json, secrets
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///secure_sessions.db'
app.config['SECRET_KEY'] = secrets.token_hex(32)
app.config['SESSION_COOKIE_SECURE'] = True
app.config['SESSION_COOKIE_HTTPONLY'] = True
app.config['SESSION_COOKIE_SAMESITE'] = 'Strict'
db = SQLAlchemy(app)
class Session(db.Model):
id = db.Column(db.String(255), primary_key=True)
data = db.Column(db.Text, nullable=False)
expiry = db.Column(db.DateTime, nullable=False)
class SecureSessionInterface:
def open_session(self, app, request):
sid = request.cookies.get(app.session_cookie_name)
if sid:
session_record = Session.query.filter_by(id=sid).first()
if session_record and session_record.expiry > datetime.utcnow():
return dict(json.loads(session_record.data))
return {}
def save_session(self, app, session, response):
domain = self.get_cookie_domain(app)
if not session:
Session.query.filter_by(id=request.cookies.get(app.session_cookie_name)).delete()
db.session.commit()
response.delete_cookie(app.session_cookie_name, domain=domain)
return
sid = request.cookies.get(app.session_cookie_name, secrets.token_hex(16))
expiry = datetime.utcnow() + timedelta(minutes=30)
session_data = json.dumps(dict(session))
session_record = Session(id=sid, data=session_data, expiry=expiry)
db.session.merge(session_record)
db.session.commit()
response.set_cookie(app.session_cookie_name, sid, max_age=1800, secure=True, httponly=True, samesite='Strict')
app.session_interface = SecureSessionInterface()
@app.route('/set_data', methods=['POST'])
def set_data():
session['data'] = request.form['data']
return redirect(url_for('get_data'))
@app.route('/get_data')
def get_data():
data = session.get('data', 'None')
return f"Data: {data}"
with app.app_context():
db.create_all()
if __name__ == '__main__':
app.run(debug=True, ssl_context='adhoc')
Output:
Data: secure (after POST to /set_data with data='secure')
Data: None (if no session or expired)
Explanation:
- Applies secure cookie settings and a strong
SECRET_KEY
. - Uses
SameSite='Strict'
for enhanced CSRF protection.
2.4 Session Expiry
Example: Session Expiry with Redis
from flask import Flask, session, request, redirect, url_for
from redis import Redis
import json, secrets
from datetime import timedelta
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secure-key-101'
redis_client = Redis(host='localhost', port=6379, db=0)
class RedisSessionInterface:
def __init__(self, redis, prefix='session:'):
self.redis = redis
self.prefix = prefix
def open_session(self, app, request):
sid = request.cookies.get(app.session_cookie_name)
if sid:
data = self.redis.get(self.prefix + sid)
if data:
return dict(json.loads(data))
return {}
def save_session(self, app, session, response):
domain = self.get_cookie_domain(app)
if not session:
sid = request.cookies.get(app.session_cookie_name)
if sid:
self.redis.delete(self.prefix + sid)
response.delete_cookie(app.session_cookie_name, domain=domain)
return
sid = request.cookies.get(app.session_cookie_name, secrets.token_hex(16))
session_data = json.dumps(dict(session))
self.redis.setex(self.prefix + sid, timedelta(minutes=15), session_data)
response.set_cookie(app.session_cookie_name, sid, max_age=900, secure=True, httponly=True, samesite='Lax')
app.session_interface = RedisSessionInterface(redis_client)
@app.route('/set_session', methods=['POST'])
def set_session():
session['username'] = request.form['username']
return redirect(url_for('get_session'))
@app.route('/get_session')
def get_session():
username = session.get('username', 'Guest')
return f"Welcome, {username}"
if __name__ == '__main__':
app.run(debug=True, ssl_context='adhoc')
Output:
Welcome, Charlie (after POST to /set_session with username='Charlie')
Welcome, Guest (after 15 minutes)
Explanation:
- Sets a 15-minute expiry for Redis sessions using
setex
. - Automatically cleans up expired sessions.
2.5 Integration with Flask-Login
Example: Flask-Login with SQLAlchemy Session Backend
from flask import Flask, session, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user
from flask_bcrypt import Bcrypt
from datetime import datetime, timedelta
import json, secrets
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///login_sessions.db'
app.config['SECRET_KEY'] = secrets.token_hex(32)
app.config['SESSION_COOKIE_SECURE'] = True
app.config['SESSION_COOKIE_HTTPONLY'] = True
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
db = SQLAlchemy(app)
bcrypt = Bcrypt(app)
login_manager = LoginManager(app)
login_manager.login_view = 'login'
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password_hash = db.Column(db.String(120), nullable=False)
class Session(db.Model):
id = db.Column(db.String(255), primary_key=True)
data = db.Column(db.Text, nullable=False)
expiry = db.Column(db.DateTime, nullable=False)
class CustomSessionInterface:
def open_session(self, app, request):
sid = request.cookies.get(app.session_cookie_name)
if sid:
session_record = Session.query.filter_by(id=sid).first()
if session_record and session_record.expiry > datetime.utcnow():
return dict(json.loads(session_record.data))
return {}
def save_session(self, app, session, response):
domain = self.get_cookie_domain(app)
if not session:
Session.query.filter_by(id=request.cookies.get(app.session_cookie_name)).delete()
db.session.commit()
response.delete_cookie(app.session_cookie_name, domain=domain)
return
sid = request.cookies.get(app.session_cookie_name, secrets.token_hex(16))
expiry = datetime.utcnow() + timedelta(minutes=30)
session_data = json.dumps(dict(session))
session_record = Session(id=sid, data=session_data, expiry=expiry)
db.session.merge(session_record)
db.session.commit()
response.set_cookie(app.session_cookie_name, sid, max_age=1800, secure=True, httponly=True, samesite='Lax')
app.session_interface = CustomSessionInterface()
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
user = User.query.filter_by(username=username).first()
if user and bcrypt.check_password_hash(user.password_hash, password):
session.regenerate()
login_user(user)
session['theme'] = request.form.get('theme', 'light')
return redirect(url_for('dashboard'))
return "Invalid credentials"
@app.route('/logout')
@login_required
def logout():
session.clear()
logout_user()
return redirect(url_for('home'))
@app.route('/dashboard')
@login_required
def dashboard():
theme = session.get('theme', 'light')
return f"Welcome, {current_user.username}, to your dashboard (Theme: {theme})"
@app.route('/home')
def home():
return "Welcome to the homepage"
with app.app_context():
db.create_all()
hashed_password = bcrypt.generate_password_hash('secure123').decode('utf-8')
db.session.add(User(username='Eve', password_hash=hashed_password))
db.session.commit()
if __name__ == '__main__':
app.run(debug=True, ssl_context='adhoc')
Output:
Welcome, Eve, to your dashboard (Theme: dark) (after login with theme='dark')
Welcome to the homepage (after logout)
Explanation:
- Combines custom SQLAlchemy session backend with Flask-Login for secure authentication.
- Regenerates session ID on login to prevent fixation.
2.6 Insecure Custom Backend
Example: Insecure Session Backend
from flask import Flask, session, request, redirect, url_for
import json
app = Flask(__name__)
app.config['SECRET_KEY'] = 'weak' # Insecure key
class InsecureSessionInterface:
def open_session(self, app, request):
sid = request.cookies.get(app.session_cookie_name)
if sid:
with open(f'sessions/{sid}.json', 'r') as f:
return json.load(f)
return {}
def save_session(self, app, session, response):
sid = request.cookies.get(app.session_cookie_name, '123') # Predictable ID
with open(f'sessions/{sid}.json', 'w') as f:
json.dump(dict(session), f)
response.set_cookie(app.session_cookie_name, sid) # No secure flags
app.session_interface = InsecureSessionInterface()
@app.route('/set_session', methods=['POST'])
def set_session():
session['data'] = request.form['data']
return redirect(url_for('get_session'))
@app.route('/get_session')
def get_session():
data = session.get('data', 'None')
return f"Data: {data}"
if __name__ == '__main__':
app.run(debug=True)
Output:
Data: test (after POST to /set_session with data='test')
Data: None (if no session)
Explanation:
- Weak
SECRET_KEY
, predictable session IDs, and lack of secure cookie flags make the backend vulnerable. - Solution: Use secure keys, random session IDs, and secure cookie settings.
03. Effective Usage
3.1 Recommended Practices
- Use secure backends like Redis or SQLAlchemy with strong security configurations.
Example: Comprehensive Custom Session Backend
from flask import Flask, session, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required
from flask_bcrypt import Bcrypt
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired
from datetime import datetime, timedelta
import json, secrets
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///complete_sessions.db'
app.config['SECRET_KEY'] = secrets.token_hex(32)
app.config['SESSION_COOKIE_SECURE'] = True
app.config['SESSION_COOKIE_HTTPONLY'] = True
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(minutes=30)
db = SQLAlchemy(app)
bcrypt = Bcrypt(app)
login_manager = LoginManager(app)
login_manager.login_view = 'login'
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password_hash = db.Column(db.String(120), nullable=False)
class Session(db.Model):
id = db.Column(db.String(255), primary_key=True)
data = db.Column(db.Text, nullable=False)
expiry = db.Column(db.DateTime, nullable=False)
class SecureSessionInterface:
def open_session(self, app, request):
sid = request.cookies.get(app.session_cookie_name)
if sid:
session_record = Session.query.filter_by(id=sid).first()
if session_record and session_record.expiry > datetime.utcnow():
return dict(json.loads(session_record.data))
return {}
def save_session(self, app, session, response):
domain = self.get_cookie_domain(app)
if not session:
Session.query.filter_by(id=request.cookies.get(app.session_cookie_name)).delete()
db.session.commit()
response.delete_cookie(app.session_cookie_name, domain=domain)
return
sid = request.cookies.get(app.session_cookie_name, secrets.token_hex(16))
expiry = datetime.utcnow() + timedelta(minutes=30)
session_data = json.dumps(dict(session))
session_record = Session(id=sid, data=session_data, expiry=expiry)
db.session.merge(session_record)
db.session.commit()
response.set_cookie(app.session_cookie_name, sid, max_age=1800, secure=True, httponly=True, samesite='Lax')
app.session_interface = SecureSessionInterface()
class LoginForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired()])
submit = SubmitField('Login')
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user and bcrypt.check_password_hash(user.password_hash, form.password.data):
session.regenerate()
login_user(user, remember=True)
session['theme'] = request.form.get('theme', 'light')
return redirect(url_for('dashboard'))
return "Invalid credentials"
return render_template_string('''
<form method="POST">
{{ form.hidden_tag() }}
{{ form.username.label }} {{ form.username() }}<br>
{{ form.password.label }} {{ form.password() }}<br>
{{ form.submit() }}
</form>
''', form=form)
@app.route('/logout')
@login_required
def logout():
session.clear()
logout_user()
return redirect(url_for('home'))
@app.route('/dashboard')
@login_required
def dashboard():
theme = session.get('theme', 'light')
return f"Welcome, {current_user.username}, to your secure dashboard (Theme: {theme})"
@app.route('/home')
def home():
return "Welcome to the homepage"
with app.app_context():
db.create_all()
hashed_password = bcrypt.generate_password_hash('secure123').decode('utf-8')
db.session.add(User(username='Frank', password_hash=hashed_password))
db.session.commit()
if __name__ == '__main__':
app.run(debug=True, ssl_context='adhoc')
Output:
Welcome, Frank, to your secure dashboard (Theme: dark) (after login with theme='dark')
Welcome to the homepage (after logout)
- Uses a secure SQLAlchemy backend with CSRF protection via Flask-WTF.
- Implements session regeneration and secure cookie settings.
- Integrates with Flask-Login and Flask Session Security.
3.2 Practices to Avoid
- Avoid insecure session IDs, weak keys, or non-secure backends.
Example: Insecure File-Based Backend
from flask import Flask, session, request, redirect, url_for
import json
app = Flask(__name__)
app.config['SECRET_KEY'] = 'weak'
class FileSessionInterface:
def open_session(self, app, request):
sid = request.cookies.get(app.session_cookie_name)
if sid:
try:
with open(f'sessions/{sid}.json', 'r') as f:
return json.load(f)
except:
pass
return {}
def save_session(self, app, session, response):
sid = request.cookies.get(app.session_cookie_name, '123')
with open(f'sessions/{sid}.json', 'w') as f:
json.dump(dict(session), f)
response.set_cookie(app.session_cookie_name, sid)
app.session_interface = FileSessionInterface()
@app.route('/set_session', methods=['POST'])
def set_session():
session['data'] = request.form['data']
return redirect(url_for('get_session'))
@app.route('/get_session')
def get_session():
data = session.get('data', 'None')
return f"Data: {data}"
if __name__ == '__main__':
app.run(debug=True)
Output:
Data: test (after POST to /set_session with data='test')
Data: None (if no session)
- Predictable session IDs and lack of security make the backend vulnerable.
- Solution: Use secure backends with random IDs and secure cookies.
04. Common Use Cases in Web Development
4.1 E-commerce Session Management
Use a Redis backend for scalable e-commerce session handling.
Example: E-commerce Redis Sessions
from flask import Flask, session, request, redirect, url_for
from redis import Redis
import json, secrets
from datetime import timedelta
app = Flask(__name__)
app.config['SECRET_KEY'] = secrets.token_hex(32)
app.config['SESSION_COOKIE_SECURE'] = True
app.config['SESSION_COOKIE_HTTPONLY'] = True
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
redis_client = Redis(host='localhost', port=6379, db=0)
class RedisSessionInterface:
def __init__(self, redis, prefix='session:'):
self.redis = redis
self.prefix = prefix
def open_session(self, app, request):
sid = request.cookies.get(app.session_cookie_name)
if sid:
data = self.redis.get(self.prefix + sid)
if data:
return dict(json.loads(data))
return {}
def save_session(self, app, session, response):
domain = self.get_cookie_domain(app)
if not session:
sid = request.cookies.get(app.session_cookie_name)
if sid:
self.redis.delete(self.prefix + sid)
response.delete_cookie(app.session_cookie_name, domain=domain)
return
sid = request.cookies.get(app.session_cookie_name, secrets.token_hex(16))
session_data = json.dumps(dict(session))
self.redis.setex(self.prefix + sid, timedelta(minutes=30), session_data)
response.set_cookie(app.session_cookie_name, sid, max_age=1800, secure=True, httponly=True, samesite='Lax')
app.session_interface = RedisSessionInterface(redis_client)
@app.route('/add_to_cart', methods=['POST'])
def add_to_cart():
if 'cart' not in session:
session['cart'] = []
session['cart'].append(request.form['item'])
session.modified = True
return redirect(url_for('view_cart'))
@app.route('/view_cart')
def view_cart():
cart = session.get('cart', [])
return f"Cart: {cart}"
if __name__ == '__main__':
app.run(debug=True, ssl_context='adhoc')
Output:
Cart: ['book', 'pen'] (after adding items)
Cart: [] (after session expiry)
Explanation:
- Uses Redis for fast, scalable cart storage.
- Ensures security with secure cookies and session expiry.
4.2 Secure Admin Dashboard Sessions
Use a SQLAlchemy backend for secure admin session management.
Example: Admin Dashboard Sessions
from flask import Flask, session, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user, login_required
from flask_bcrypt import Bcrypt
from datetime import datetime, timedelta
import json, secrets
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///admin_sessions.db'
app.config['SECRET_KEY'] = secrets.token_hex(32)
app.config['SESSION_COOKIE_SECURE'] = True
app.config['SESSION_COOKIE_HTTPONLY'] = True
app.config['SESSION_COOKIE_SAMESITE'] = 'Strict'
db = SQLAlchemy(app)
bcrypt = Bcrypt(app)
login_manager = LoginManager(app)
login_manager.login_view = 'login'
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password_hash = db.Column(db.String(120), nullable=False)
role = db.Column(db.String(20), nullable=False)
class Session(db.Model):
id = db.Column(db.String(255), primary_key=True)
data = db.Column(db.Text, nullable=False)
expiry = db.Column(db.DateTime, nullable=False)
class SecureSessionInterface:
def open_session(self, app, request):
sid = request.cookies.get(app.session_cookie_name)
if sid:
session_record = Session.query.filter_by(id=sid).first()
if session_record and session_record.expiry > datetime.utcnow():
return dict(json.loads(session_record.data))
return {}
def save_session(self, app, session, response):
domain = self.get_cookie_domain(app)
if not session:
Session.query.filter_by(id=request.cookies.get(app.session_cookie_name)).delete()
db.session.commit()
response.delete_cookie(app.session_cookie_name, domain=domain)
return
sid = request.cookies.get(app.session_cookie_name, secrets.token_hex(16))
expiry = datetime.utcnow() + timedelta(minutes=15)
session_data = json.dumps(dict(session))
session_record = Session(id=sid, data=session_data, expiry=expiry)
db.session.merge(session_record)
db.session.commit()
response.set_cookie(app.session_cookie_name, sid, max_age=900, secure=True, httponly=True, samesite='Strict')
app.session_interface = SecureSessionInterface()
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
user = User.query.filter_by(username=username).first()
if user and bcrypt.check_password_hash(user.password_hash, password) and user.role == 'admin':
session.regenerate()
login_user(user)
return redirect(url_for('admin_dashboard'))
return "Invalid credentials"
@app.route('/admin')
@login_required
def admin_dashboard():
return f"Secure admin dashboard for {current_user.username}"
with app.app_context():
db.create_all()
hashed_password = bcrypt.generate_password_hash('admin123').decode('utf-8')
db.session.add(User(username='Grace', password_hash=hashed_password, role='admin'))
db.session.commit()
if __name__ == '__main__':
app.run(debug=True, ssl_context='adhoc')
Output:
Secure admin dashboard for Grace (after valid admin login)
Explanation:
- Uses SQLAlchemy for persistent, secure admin sessions.
- Enforces strict security with
SameSite='Strict'
and short expiry.
Conclusion
Custom session backends in Flask, integrated with Flask-Login, Flask Session Security, and NumPy Array Operations, provide scalable and secure session management. Key takeaways:
- Use Redis or SQLAlchemy for scalable, server-side session storage.
- Implement secure session IDs, cookie settings, and expiry mechanisms.
- Integrate with Flask-Login and Flask-WTF for authentication and CSRF protection.
- Apply in e-commerce, admin dashboards, or large-scale apps for robust session handling.
With these techniques, you can build Flask applications that manage sessions securely and efficiently, meeting the demands of modern web development!
Comments
Post a Comment