Flask-Principal
Flask-Principal is an extension for Flask that provides identity management for users, enabling flexible authorization based on 'Needs' and 'Permissions'. The latest stable version is 0.4.0, released in 2012, and the library is largely unmaintained. It uses a signal-based approach to load user identities and their associated permissions.
Common errors
-
RuntimeError: Working outside of application context.
cause The `identity_changed.send()` method or other Principal operations were called outside of an active Flask application context (e.g., in a background thread or a script without a context pushed).fixEnsure all Flask-Principal operations, especially signal emissions, are performed within an active request context or by explicitly pushing an application context: `with app.app_context(): identity_changed.send(...)`. -
AssertionError: This Principal extension is not registered to the current application.
cause Flask-Principal was either not initialized with your Flask application (`principals = Principal(app)` was omitted or called incorrectly), or you are attempting to use Principal features (like `current_identity`) outside of a request where the application context might not be properly set up or associated with the Principal extension.fixVerify that `principals = Principal(app)` is correctly called during your application setup. If using an app factory, ensure `Principal()` is initialized with `app` *within* the factory or with `Principal().init_app(app)`. -
Access denied / Permission not granted / @requires_role decorator not working.
cause The `current_identity` does not possess the required `Need` for the permission being checked. This often happens if the `identity_loaded` signal handler doesn't correctly add `Needs` to the identity's `provides` set, or if the `Need` definition in the permission does not match what's provided.fixInspect your `identity_loaded` signal handler to ensure it correctly populates `identity.provides` with the appropriate `Need` objects for the current user. Double-check that the `Need` defined in your `Permission` (e.g., `Permission(RoleNeed('admin'))`) exactly matches the `Need` added to the identity.
Warnings
- deprecated Flask-Principal is largely unmaintained, with its last release (0.4.0) dating back to 2012. It may not be compatible with newer Flask versions or Python features, and there are no active security updates. Modern Flask applications are advised to use more actively maintained authentication and authorization libraries like Flask-Login (for authentication) combined with custom decorators for role-based access control, or comprehensive solutions for JWT/session management.
- gotcha Flask-Principal was originally developed for Python 2. While basic functionality might work on Python 3, subtle compatibility issues or unhandled edge cases may arise due to differences in string handling, standard library changes, or other Python 2/3 specific behaviors. The library's `requires_python` metadata is `None`, indicating no explicit Python 3 support was declared.
- gotcha Flask-Principal handles *authorization* (what a user can do), not *authentication* (who the user is). You must integrate it with a separate authentication mechanism (e.g., Flask-Login, a custom session manager, or OAuth) to establish the user's identity before Principal can assign and check permissions.
- gotcha The signal-based nature of `identity_changed` and `identity_loaded` means they must be sent within an active Flask application context (`app.app_context()` or during a request). Calling `identity_changed.send()` outside of an app context will raise a `RuntimeError`.
Install
-
pip install Flask-Principal
Imports
- Principal
from flaskext.principal import Principal
from flask_principal import Principal
- Identity
from flask_principal import Identity
- AnonymousIdentity
from flask_principal import AnonymousIdentity
- Permission
from flask_principal import Permission
- Need
from flask_principal import Need
- RoleNeed
from flask_principal import RoleNeed
- UserNeed
from flask_principal import UserNeed
- current_identity
from flask_principal import current_identity
- identity_changed
from flask_principal import identity_changed
- identity_loaded
from flask_principal import identity_loaded
Quickstart
import os
from flask import Flask, session, g, redirect, url_for
from flask_principal import Principal, Identity, AnonymousIdentity, \
identity_changed, identity_loaded, \
current_identity, \
Need, UserNeed, RoleNeed, Permission
# Initialize Flask app
app = Flask(__name__)
app.secret_key = os.environ.get('FLASK_SECRET_KEY', 'default_secret_key')
# Initialize Flask-Principal
principals = Principal(app)
# Define a permission for 'admin' role
admin_permission = Permission(RoleNeed('admin'))
@app.before_request
def load_identity_from_session():
# Load identity from session or create an anonymous one
if 'identity_id' in session:
g.identity = Identity(session['identity_id'])
# Signal that the identity has loaded
identity_changed.send(app, identity=g.identity)
else:
g.identity = AnonymousIdentity()
@identity_loaded.connect_via(app)
def on_identity_loaded(sender, identity):
# This signal is fired when an identity is loaded (or changed)
# Use this to add Needs to the identity based on your application logic
if identity.id:
# Example: user_id 1 is an admin, others are regular users
if identity.id == 1:
identity.provides.add(RoleNeed('admin'))
identity.provides.add(UserNeed(identity.id)) # All logged-in users get their UserNeed
# For anonymous users, no specific Needs are added here by default
@app.route('/login/<int:user_id>')
def login(user_id):
# Simulate a login: set identity in session and emit signal
session['identity_id'] = user_id
identity_changed.send(app, identity=Identity(user_id))
return f'Logged in as User {user_id}. <a href="{url_for("profile")}">Go to profile</a> <a href="{url_for("admin_dashboard")}">Go to admin dashboard</a>'
@app.route('/logout')
def logout():
# Simulate a logout: clear session and emit anonymous identity signal
if 'identity_id' in session:
del session['identity_id']
identity_changed.send(app, identity=AnonymousIdentity())
return 'Logged out. <a href="{url_for("profile")}">Go to profile</a>'
@app.route('/')
@app.route('/profile')
def profile():
if current_identity.is_authenticated():
return f'Hello, User {current_identity.id}! You are authenticated.' \
f' Role: {"Admin" if admin_permission.can() else "User"}.'
return 'Hello, Anonymous User! You are not logged in.'
@app.route('/admin')
@admin_permission.require() # This decorator enforces the 'admin' permission
def admin_dashboard():
return f'Welcome to the Admin Dashboard, User {current_identity.id}!'
if __name__ == '__main__':
# For local testing, ensure a secret key is set
if 'FLASK_SECRET_KEY' not in os.environ:
print("WARNING: FLASK_SECRET_KEY environment variable not set. Using a default for demonstration.")
app.run(debug=True)