Skip to content

Lightweight Role-Based Access Control (RBAC) library for Streamlit applications.

License

Notifications You must be signed in to change notification settings

izuno4t/streamlit-rbac

Repository files navigation

streamlit-rbac

CI PyPI Python License

Lightweight Role-Based Access Control (RBAC) library for Streamlit applications.

Inspired by Spring Security's authorization API, streamlit-rbac provides a thin, declarative layer for role-based access control — without pulling in authentication, user management, or any heavyweight framework.


✨ Features

  • Pure function corehas_role(), has_any_role(), has_all_roles() are stateless and side-effect-free, making them trivial to unit test.
  • Declarative decorator@require_roles() guards functions with a single line.
  • Streamlit integrationauthorize_page() stops unauthorized page rendering via st.stop().
  • Bring your own auth — inject any role_loader function to resolve roles from IdP tokens, session state, databases, or anything else.
  • Zero required dependencies — core functions use only the Python standard library. Streamlit is an optional dependency.

🏗️ Architecture

+---------------------------------------------+
|          Streamlit Integration               |  authorize_page()
|          (optional dependency)               |
+---------------------------------------------+
|          Decorator Layer                     |  @require_roles()
+---------------------------------------------+
|          Core Functions                      |  has_role()
|          (pure, zero dependencies)           |  has_any_role()
|                                              |  has_all_roles()
+---------------------------------------------+

Each layer depends only on the one below it. Core functions work anywhere — no Streamlit required.


📦 Installation

pip install streamlit-rbac

# With Streamlit integration
pip install streamlit-rbac[streamlit]

Requires Python 3.11+


🚀 Quick Start

Pure functions (great for testing)

from streamlit_rbac import has_role, has_any_role, has_all_roles

# Single role check
has_role("Admin", user_roles=["Admin", "User"])  # True
has_role("Admin", user_roles=["User"])            # False

# Any of these roles (OR)
has_any_role("Admin", "Manager", user_roles=["Manager"])  # True

# All of these roles (AND)
has_all_roles("Admin", "Auditor", user_roles=["Admin", "Auditor"])  # True
has_all_roles("Admin", "Auditor", user_roles=["Admin"])              # False

Role loader (deferred evaluation)

Instead of passing roles directly, supply a callable that returns them:

from streamlit_rbac import has_role

def get_user_roles() -> list[str]:
    # Fetch from IdP, database, session, etc.
    return ["Admin", "User"]

has_role("Admin", role_loader=get_user_roles)  # True

user_roles and role_loader are mutually exclusive — pass one or the other, never both.

Decorator

from streamlit_rbac import require_roles

@require_roles("Admin", role_loader=get_user_roles)
def delete_user(user_id: str) -> None:
    ...

Raises PermissionError if the user lacks the required role. An optional on_denied callback runs before the error:

@require_roles("Admin", role_loader=get_user_roles, on_denied=log_violation)
def delete_user(user_id: str) -> None:
    ...

Streamlit page guard

import streamlit as st
from streamlit_rbac import authorize_page

def get_user_roles() -> list[str]:
    claims = st.session_state.get("token_claims", {})
    return claims.get("roles", [])

# Place at the top of each page script
authorize_page("Admin", role_loader=get_user_roles)

st.title("Admin Page")  # Only rendered if authorized

If the user lacks the required role, authorize_page displays an error message and calls st.stop() — nothing below it executes.


🎛️ Component-Level Control

Mix page guards with inline checks for fine-grained control:

from streamlit_rbac import authorize_page, has_role

authorize_page("User", "Admin", role_loader=get_user_roles)
st.title("Dashboard")

# Only SuperAdmins see this section
if has_role("SuperAdmin", role_loader=get_user_roles):
    with st.expander("System Settings"):
        st.write("Dangerous operations...")

📖 API Reference

Core Functions

Function Description
has_role(required, *, user_roles, role_loader) Single role check
has_any_role(*required, *, user_roles, role_loader) Any of the given roles (OR)
has_all_roles(*required, *, user_roles, role_loader) All of the given roles (AND)

All return bool. All raise ValueError if both or neither of user_roles / role_loader are provided.

Edge cases: has_any_role() with no required roles returns False. has_all_roles() with no required roles returns True (vacuous truth).

Decorator API

Function Description
@require_roles(*roles, *, user_roles, role_loader, on_denied) Guard a function (OR logic). Raises PermissionError on denial.

Streamlit Integration

Function Description
authorize_page(*roles, *, role_loader, login_url, denied_message) Page-level guard. Calls st.stop() on denial.

Types

Type Definition
RoleLoader Callable[[], Iterable[str]]
OnDeniedHandler Callable[[], None]

💡 Design Principles

Principle Detail
Authorization only No authentication, no user management, no database schemas. This library answers one question: does this user have the required role?
Developer stays in control Role resolution is entirely your responsibility via role_loader. The library never dictates where roles come from.
Pure core, optional integration Core functions have zero dependencies. Streamlit is only imported when you use authorize_page().

📄 License

MIT

About

Lightweight Role-Based Access Control (RBAC) library for Streamlit applications.

Resources

License

Contributing

Stars

Watchers

Forks

Packages

No packages published