Skip to content

Role-based Authorization

💡 TL;DR - Row Level Security with Grant Permissions on User Roles

Declarative security enables you to Grant row filters to user roles, providing row level security: users see only the rows to which they are authorized. Grants can access user properties, such as their organization. A common usage is to enforce multi-tenant access.

Define users and roles with a SQL database using an Admin app, or, supply a provider to attach to existing corporate security (AD, LDAP, etc).

 

Scope

Security consists of many aspects (http headers, cookie settings, etc.); here, we focus on the following key concepts:

  • Authentication: a login function that confirms a user has access, usually by posting credentials and obtaining a JWT token identifying the users' roles.

  • Authorization: controlling access to row/columns based on assigned roles.

  • Role: in security, users are assigned one or many roles. Roles are authorized for access to data, down to the row level.

 

Process Overview

The overall flow is shown below, where:

  • Green - represents developer responsibilities
  • Blue - System processing

Developers Configure Security

Developers are responsible for providing (or using system defaults).

Authentication-Provider

This class, given a user/password, returns the list of authorized roles (on None). It is invoked by the system when client apps log in.

Developers must:

* Provide this class

* Identify the class in `conf/config.py`

 

Authentication Data

Developers must determine the data required to authenticate users. This can be a SQL Database, LDAP, AD, etc. It is separate from user databases so it can be shared between systems. The Authentication-Provider uses it to authenticate a user/password, and return their roles.

 

declare_security

Add code to the pre-created (empty) Python module that defines table/role filters. The system merges these into each retrieval. These declarations are processed on system startup as described below.

 

System Processing

System processing is summarized below.

 

Startup: declare_security

When you start the server, the system (api_logic_server_run.py) imports declare_security. This:

  1. Imports from security.system.security_manager import Grant, Security, which sets up SQLAlchemy listeners for all database access calls

  2. Creates Grant objects, internally maintained for subsequent use on API calls (SQLAlchemy read events).

 

Login: Call Auth-Provider

When users log in, the app POSTs their id/password to the system, which invokes the Authentication-Provider to autthenticate and return a set of roles. These are tokenized and returned to the client, and passed in the header of subsequent requests.

 

API: Security Manager

This provides:

  • The Grant function, to save the filters for each table/role

  • Filtering, by registering for and processing the SQLAlchemy receive_do_orm_execute event to enforce filters.

 

Server: User State

The server provides the functions for login (using the Authentication-Provider). This returns the JWT which users supply in the header of subsequent requests.

As the server processes requests, it validates JWT presence, and provides current_user_from_JWT() to return this data for the Security Manager.

 

Use Cases

Data Security

Security enables you to hide certain rows from designated roles, such as a list of HR actions.

 

Multi-Tenant

Some systems require the data to be split between multiple customers. One approach here is to 'stamp' each row with a client_id, associate client_id with each customers, and then add the client_id to each search. The sample illustrates how this can be achieved with authorization:

Grant(  on_entity = models.Category,
        to_role = Roles.tenant,
        filter = models.Category.Client_id == Security.current_user().client_id)  # User table attributes

 

Appendix: Resources

The Security Manager and sqlite Authentication-Provider are built into created projects from the system's prototype project -- see the security directory.