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 JWT

  • get_current_user_id() - Extracts id attribute from your user

  • is_current_user_admin() - Checks admin role via has_role()

  • is_current_user_superadmin() - Checks superadmin role via has_role()

  • JWT authentication - Loads instances of your user class (when no custom getter provided)

How It Works

The user context system is elegantly simple:

  1. Register your user class with init_fms(user=)

  2. Optionally provide a custom getter (default: JWT-based)

  3. All other functions automatically derive from your registration

  4. Your user object just needs an id attribute and optional has_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_anonymous

  • Session-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_admin property/method: Returns True if user has admin privileges

  • is_superadmin property/method: Returns True if user has superadmin privileges

The system handles:

  • Dict-style access: {'id': uuid.uuid4(), 'is_admin': True}

  • Object attribute access: user.id, user.is_admin

  • Property access: user.is_admin (calls @property decorated 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 id attribute

  • Verify debug mode is enabled (app.config['DEBUG'] = True)

Permission checks failing unexpectedly:

  • Verify is_admin and is_superadmin are accessible as properties on your user object

  • Check that user ID is being returned correctly

  • Ensure request context exists when calling permission methods

“User has no attribute ‘is_admin’” error:

  • Add @property decorator to your role-checking methods

  • Ensure default False return 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.