Custom User Context Integration
Advanced integration examples for using external authentication systems with flask-more-smorest’s permission system.
Tip
Looking to extend the User model? See User Model Extension Guide for extension patterns. This guide covers integrating with external auth providers.
When to Use Custom User Context
Use custom user context when you need to:
Integrate with existing apps - Your application already has User/UserRole models
Use external auth - Integrate with OAuth, SAML, LDAP, or other providers
Support multi-tenant - Different user models per tenant/organization
Custom authentication - Use any authentication mechanism (session, token, header, etc.)
Note
Without configuration, flask-more-smorest uses its built-in JWT-based User model.
The custom user context system allows you to replace this with your own implementation.
Quick Start
Register your custom user class (and optionally a custom getter):
from flask_more_smorest.perms import init_fms
def my_get_user():
from flask import session
user_id = session.get('user_id')
return MyUser.query.get(user_id) if user_id else None
# Register - everything else derives from this!
init_fms(user=MyUser, get_current_user=my_get_user)
That’s it! No classes, no base class, no multiple methods to override.
The system automatically provides:
get_current_user()- Calls your registered function or uses JWTget_current_user_id()- Extractsidattribute from your useris_current_user_admin()- Checks admin role viahas_role()is_current_user_superadmin()- Checks superadmin role viahas_role()JWT authentication - Loads instances of your user class (when no custom getter provided)
How It Works
The user context system is elegantly simple:
Register your user class with
init_fms(user=)Optionally provide a custom getter (default: JWT-based)
All other functions automatically derive from your registration
Your user object just needs an
idattribute and optionalhas_role()method
from flask_more_smorest.perms import (
init_fms,
get_current_user,
get_current_user_id,
is_current_user_admin,
is_current_user_superadmin,
)
def my_get_user():
# Your custom logic to retrieve user
user_id = request.headers.get('X-User-ID')
return MyUser.query.get(user_id) if user_id else None
init_fms(user=MyUser, get_current_user=my_get_user)
# Now you can use:
user = get_current_user() # Calls my_get_user()
user_id = get_current_user_id() # Extracts user.id
admin = is_current_user_admin() # Checks user.has_role('admin')
superadmin = is_current_user_superadmin() # Checks user.has_role('superadmin')
Flask-Login Integration
Integrate with Flask-Login’s session-based authentication:
from flask_login import current_user
from flask_more_smorest.perms import init_fms
def get_flask_login_user():
return current_user if not current_user.is_anonymous else None
init_fms(user=MyUser, get_current_user=get_flask_login_user)
# Now all permission checks use Flask-Login's current_user
from flask_more_smorest.perms import is_current_user_admin
@app.route('/api/admin/dashboard')
def admin_dashboard():
if is_current_user_admin():
return "Admin dashboard"
return "Access denied", 403
Benefits:
Seamless Flask-Login integration
Anonymous user handling via
current_user.is_anonymousSession-based authentication with all Flask-Login features
Custom JWT Implementation
If you have a custom JWT implementation:
import jwt
from flask import request
from flask_more_smorest.perms import init_fms
def decode_token():
token = request.headers.get('Authorization', '').replace('Bearer ', '')
try:
return jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
except jwt.InvalidTokenError:
return None
def get_jwt_user():
payload = decode_token()
if payload:
return MyUser.query.get(payload.get('user_id'))
return None
init_fms(user=MyUser, get_current_user=get_jwt_user)
OAuth Integration (Google, GitHub, etc.)
from authlib.integrations.flask_client import OAuth
from flask import session
from flask_more_smorest.perms import init_fms
oauth = OAuth()
google = oauth.register(
'google',
client_id='your-client-id',
client_secret='your-client-secret',
server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
client_kwargs={'scope': 'openid email profile'}
)
def get_oauth_user():
user_id = session.get('user_id')
return MyUser.query.get(user_id) if user_id else None
init_fms(user=MyUser, get_current_user=get_oauth_user)
# OAuth callback handler
@app.route('/auth/google/callback')
def google_callback():
token = google.authorize_access_token()
user_info = google.parse_id_token(token)
# Link/create user in your DB
user = MyUser.get_or_create_from_oauth(user_info)
session['user_id'] = user.id
return redirect('/dashboard')
SAML Integration
from flask_saml2 import SP as SAMLSP
from flask import session
from flask_more_smorest.perms import init_fms
saml = SAMLSP(...)
def get_saml_user():
user_id = session.get('user_id')
return MyUser.query.get(user_id) if user_id else None
init_fms(user=MyUser, get_current_user=get_saml_user)
# SAML assertion consumer service
@app.route('/saml/acs')
def saml_acs():
authn_response = saml.parse_authn_request_response(request.form)
user = MyUser.get_or_create_from_saml(authn_response)
session['user_id'] = user.id
return redirect('/dashboard')
LDAP Integration
from flask import session
from flask_more_smorest.perms import init_fms
import ldap3
def get_ldap_user():
username = session.get('username')
if not username:
return None
# Look up user in LDAP
server = ldap3.Server(app.config['LDAP_SERVER'])
conn = ldap3.Connection(server, 'uid=' + username + ',ou=users,dc=example,dc=com', password=...)
if not conn.bind():
return None
# Sync to local DB if needed
user = MyUser.get_or_create_from_ldap(username)
return user
init_fms(user=MyUser, get_current_user=get_ldap_user)
Multi-Tenant Applications
Different user models per tenant:
from flask import g
from flask_more_smorest.perms import init_fms
def get_tenant_user_model(tenant_id: str):
"""Get the User model class for a specific tenant."""
module = import_module(f'tenants.{tenant_id}.models')
return module.User
def get_tenant_user():
if not hasattr(g, 'tenant_id'):
return None
UserClass = get_tenant_user_model(g.tenant_id)
user_id = session.get('user_id')
return UserClass.query.get(user_id) if user_id else None
init_fms(user=get_tenant_user_model(g.tenant_id), get_current_user=get_tenant_user)
# Tenant middleware
@app.before_request
def set_tenant():
g.tenant_id = request.headers.get('X-Tenant-ID', 'default')
Type Safety with AbstractUser
Inherit from AbstractUser to ensure compatibility with all features:
from flask_more_smorest.perms import AbstractUser, init_fms
class MyUser(AbstractUser):
# Your custom fields
pass
# Registering your user class allows the system to use it for type checking and validation
init_fms(user=MyUser)
# Runtime checking
user = get_current_user()
if isinstance(user, AbstractUser):
print(f"User {user.email} is a valid AbstractUser subclass")
else:
print("Warning: User model should inherit from AbstractUser")
Custom Role Checking
If your custom User model has a different role system:
def get_custom_role_user():
# Your custom user retrieval
user_id = session.get('user_id')
return MyUser.query.get(user_id) if user_id else None
init_fms(user=MyUser, get_current_user=get_custom_role_user)
# Your User model just needs role properties:
class MyUser:
id: uuid.UUID
roles: list[Role]
@property
def is_admin(self) -> bool:
# Custom role checking - e.g., multiple admin levels
return any(role.level >= 2 for role in self.roles)
@property
def is_superadmin(self) -> bool:
# Custom superadmin check
return any(role.level >= 3 for role in self.roles)
User Object Requirements
Your user object must have:
Required:
id: Any type (UUID, int, string are all supported)
Optional (for admin checks):
is_adminproperty/method: ReturnsTrueif user has admin privilegesis_superadminproperty/method: ReturnsTrueif user has superadmin privileges
The system handles:
Dict-style access:
{'id': uuid.uuid4(), 'is_admin': True}Object attribute access:
user.id,user.is_adminProperty access:
user.is_admin(calls@propertydecorated method)Method access:
user.is_admin()(calls callable)
Testing with Custom Context
Clear registration in tests:
from flask_more_smorest.perms import init_fms, clear_registration
def test_with_custom_user():
# Register mock user
class MockUser:
id = 'test-id'
def has_role(self, role: str) -> bool:
return role == 'admin'
def list_roles(self) -> list[str]:
return ['admin']
init_fms(user=MockUser)
# Test your code
user = get_current_user()
assert user.id == 'test-id'
# Clear for next test
clear_registration()
Troubleshooting
User not showing up in error debug context:
Ensure your registered function returns a user with an
idattributeVerify debug mode is enabled (
app.config['DEBUG'] = True)
Permission checks failing unexpectedly:
Verify
is_adminandis_superadminare accessible as properties on your user objectCheck that user ID is being returned correctly
Ensure request context exists when calling permission methods
“User has no attribute ‘is_admin’” error:
Add
@propertydecorator to your role-checking methodsEnsure default
Falsereturn when role not found:@property def is_admin(self) -> bool: return getattr(self, '_is_admin', False)
Clearing registration for tests:
from flask_more_smorest.perms import clear_registration
def setup():
clear_registration()
Getting the default JWT behavior back:
from flask_more_smorest.perms import clear_registration
# Revert to built-in JWT authentication
clear_registration()
See also
- User Model Extension Guide
Comprehensive guide for extending User models.
- Permissions System
Learn about the permission system and how it works with user context.