Simple security for Flask apps
Flask-Security provides comprehensive authorization functionality through decorators that protect views and functions based on authentication status and user roles. It integrates with Flask-Principal for role-based access control and supports multiple authentication methods.
Decorators that require users to be authenticated before accessing protected resources.
def login_required(fn):
"""
Decorator that requires user authentication.
Redirects to login page if user is not authenticated.
Args:
fn: Function to protect
Returns:
Decorated function
"""
def auth_required(*auth_methods):
"""
Multi-method authentication decorator.
Accepts multiple authentication methods.
Args:
*auth_methods (str): Authentication methods ('token', 'basic', 'session')
Returns:
Decorator function
"""
def auth_token_required(fn):
"""
Decorator for token-based authentication.
Checks for authentication token in query params or headers.
Args:
fn: Function to protect
Returns:
Decorated function
"""
def http_auth_required(realm):
"""
Decorator for HTTP Basic authentication.
Args:
realm (str): HTTP Basic auth realm
Returns:
Decorator function
"""Decorators that enforce role-based permissions on protected resources.
def roles_required(*roles):
"""
Decorator requiring user to have ALL specified roles.
Returns 403 Forbidden if user lacks any required role.
Args:
*roles (str): Role names that user must have
Returns:
Decorator function
"""
def roles_accepted(*roles):
"""
Decorator requiring user to have AT LEAST ONE of the specified roles.
Returns 403 Forbidden if user has none of the roles.
Args:
*roles (str): Role names, user must have at least one
Returns:
Decorator function
"""Decorator that restricts access to anonymous (non-authenticated) users only.
def anonymous_user_required(fn):
"""
Decorator that requires user to be anonymous (not logged in).
Redirects authenticated users away from the protected resource.
Args:
fn: Function to protect
Returns:
Decorated function
"""from flask_security import login_required, current_user
@app.route('/profile')
@login_required
def profile():
return f"Welcome to your profile, {current_user.email}!"
@app.route('/dashboard')
@login_required
def dashboard():
return render_template('dashboard.html', user=current_user)from flask_security import roles_required, roles_accepted
@app.route('/admin')
@roles_required('admin')
def admin_panel():
return "Admin Panel - Only admins can see this"
@app.route('/admin/users')
@roles_required('admin', 'user-manager')
def manage_users():
# User must have BOTH admin AND user-manager roles
return "User Management"
@app.route('/moderator')
@roles_accepted('admin', 'moderator')
def moderator_panel():
# User must have EITHER admin OR moderator role
return "Moderator Panel"
@app.route('/content/edit/<int:post_id>')
@roles_accepted('admin', 'editor', 'author')
def edit_content(post_id):
return f"Editing post {post_id}"from flask_security import auth_token_required
@app.route('/api/data')
@auth_token_required
def api_data():
return {
'data': 'Protected API data',
'user': current_user.email
}
@app.route('/api/admin')
@auth_token_required
@roles_required('admin')
def api_admin():
return {'admin_data': 'Secret admin information'}from flask_security import http_auth_required
@app.route('/basic-auth')
@http_auth_required('Protected Area')
def basic_auth_endpoint():
return f"Authenticated via HTTP Basic: {current_user.email}"
# Custom realm
@app.route('/api/secure')
@http_auth_required('API Access')
def secure_api():
return {'message': 'Authenticated via HTTP Basic Auth'}from flask_security import auth_required
@app.route('/api/flexible')
@auth_required('token', 'basic', 'session')
def flexible_auth():
# Accepts token, HTTP basic, or session authentication
return {
'message': 'Authenticated via any method',
'user': current_user.email
}
@app.route('/api/token-or-session')
@auth_required('token', 'session')
def token_or_session():
# Accepts either token or session authentication
return {'data': 'Protected data'}from flask_security import anonymous_user_required
@app.route('/register')
@anonymous_user_required
def register():
# Only show registration to non-logged-in users
return render_template('register.html')
@app.route('/login')
@anonymous_user_required
def login():
# Redirect logged-in users away from login page
return render_template('login.html')from flask_security import login_required, roles_required
@app.route('/admin/settings')
@login_required
@roles_required('admin')
def admin_settings():
return "Admin Settings"
# Method-specific protection
@app.route('/api/admin/users', methods=['GET', 'POST'])
@auth_token_required
@roles_required('admin')
def admin_users_api():
if request.method == 'GET':
return {'users': get_all_users()}
elif request.method == 'POST':
return {'result': create_user(request.json)}from functools import wraps
from flask import abort
from flask_security import current_user, login_required
def owner_required(get_resource_owner):
"""Custom decorator to check resource ownership"""
def decorator(fn):
@wraps(fn)
@login_required
def decorated_view(*args, **kwargs):
resource_owner = get_resource_owner(*args, **kwargs)
if current_user != resource_owner and not current_user.has_role('admin'):
abort(403)
return fn(*args, **kwargs)
return decorated_view
return decorator
# Usage
@app.route('/posts/<int:post_id>/edit')
@owner_required(lambda post_id: Post.query.get(post_id).author)
def edit_post(post_id):
return f"Editing post {post_id}"
def permission_required(permission):
"""Custom decorator for fine-grained permissions"""
def decorator(fn):
@wraps(fn)
@login_required
def decorated_view(*args, **kwargs):
if not current_user.has_permission(permission):
abort(403)
return fn(*args, **kwargs)
return decorated_view
return decorator
@app.route('/admin/delete-user/<int:user_id>')
@permission_required('delete_users')
def delete_user(user_id):
return f"Deleting user {user_id}"from flask.views import MethodView
from flask_security import roles_required, login_required
class AdminAPI(MethodView):
decorators = [login_required, roles_required('admin')]
def get(self):
return {'message': 'Admin GET endpoint'}
def post(self):
return {'message': 'Admin POST endpoint'}
app.add_url_rule('/admin-api', view_func=AdminAPI.as_view('admin_api'))from flask_security import current_user
@app.route('/content/<int:content_id>')
@login_required
def view_content(content_id):
content = Content.query.get_or_404(content_id)
# Custom authorization logic
if content.is_private:
if not (current_user.has_role('admin') or content.owner == current_user):
abort(403, "Access denied to private content")
return render_template('content.html', content=content)
@app.route('/api/posts')
@auth_token_required
def get_posts():
# Filter results based on user role
if current_user.has_role('admin'):
posts = Post.query.all()
else:
posts = Post.query.filter_by(published=True).all()
return {'posts': [post.to_dict() for post in posts]}tessl i tessl/pypi-flask-security@3.0.0