At Girmantech, we partner with numerous clients across diverse industries, and one challenge we consistently encounter is the need for robust and flexible systems like Role-Based Access Control (RBAC). Whether it's managing complex user permissions, safeguarding sensitive data, or enhancing operational efficiency, a scalable, granular RBAC system is critical.
What is RBAC?
RBAC (Role-Based Access Control) is a method of managing user permissions based on their roles within an organization. It assigns specific privileges to roles, and users are granted access to resources based on the roles they belong to.
Basic RBAC?
Basic RBAC assigns predefined roles to users, granting them fixed permissions. This approach is suitable for simple systems but lacks flexibility for organizations with dynamic roles and permissions.
Example of Basic RBAC
Administrator:
Can create, edit, and delete users, roles, and products.
Has full control over the management plane.
Manager:
Can create and edit users and products.
Can assign and remove roles from users.
Cannot delete users or roles.
Example of Granular RBAC for Product Management
Scenario:
A company has a product management platform with different levels of product managers: Product Owners, Product Managers, and Product Analysts. Each role requires varying levels of access to product data and management capabilities.
Granular RBAC Implementation:
Product Owner:
Can create, edit, and delete products.
Can manage product life cycle, set goals, and prioritise features.
Can assign and remove product managers and analysts.
Product Manager:
Can create, edit, and view products.
- Can manage product features, track progress, and collaborate with teams.
Product Analyst:
- Can view product data, analyse metrics, and provide insights.
The owner of the tenant can create as many roles as they want based on their organisational structure, assign permission corresponding to that role and map user to each role !
Aspect | Simple RBAC | Granular RBAC |
Permission Control | Broad permissions, applied uniformly across resources. | Fine-grained permissions, applied to specific actions and resources. |
Flexibility | Limited, suitable for straightforward use cases. | Highly flexible, suitable for complex environments. |
Implementation Complexity | Easy to implement and maintain. | Complex to implement and maintain. |
Use Cases | Best for smaller applications with straightforward requirements. | Ideal for large, complex systems with diverse access needs. |
Role Definition | Roles are broadly defined with fixed permissions. | Roles are detailed, with specific permissions tied to actions and resources. |
Example | A content management system with "Admin," "Editor," and "Viewer" roles. | An enterprise system with varying access to financial data and customer records. |
Scenarios in which Granular RBAC shines.
Simple RBAC assigns broad roles (e.g., Manager, Employee), while Granular RBAC grants specific permissions based on tasks.
Organizations with complex structures and workflows benefit greatly from granular RBAC. It allows for precise control over access, ensuring employees only have the permissions they need to do their jobs effectively.
1. Fine-Tuned Access: Allows precise permissions, ensuring users only access what they need.
2. Approval Processes: Controls access at each workflow stage for secure progress.
3.Separation of Duties: Ensures no single user controls an entire process, reducing fraud risk.
4. Scalable Management: Adapts easily as roles and workflows evolve within the enterprise.
5. Compliance: Facilitates detailed access control and logging for regulatory compliance.
6. Insider Threats: Limits access to essential functions, minimizing misuse risks.
7. Collaboration: Enables controlled information sharing while protecting sensitive data.
8. Temporary Access: Provides time-bound permissions that expire automatically for added security.
How to implement Granular RBAC in an RDBMS system and exposing API on top of that !
Table Users
Column Name | Constraints | Data Type | Remark |
id | PRIMARY KEY, UNIQUE | BIGSERIAL | Auto-incrementing unique identifier for users |
name | NOT NULL | VARCHAR(255) | User's full name |
UNIQUE | VARCHAR(255) | User's email address | |
verified | BOOLEAN | User's hashed password | |
created_at | NOT NULL | TIMESTAMP | Timestamp indicating when the user was created |
updated_at | NOT NULL | BIGINT | |
created_by | FK ( users.id ), NOT NULL | BIGINT | |
updated_by | FK ( users.id ), NOT NULL | BIGINT |
Table - Tenants
Column Name | Constraints | Data Type | Remark |
id | PRIMARY KEY | BIGSERIAL | Auto-incrementing unique identifier for tenants |
name | NOT NULL | VARCHAR(255) | Tenant name |
owner | FOREIGN KEY REFERENCES USER(user_id), NOT NULL | INTEGER | User ID of the tenant owner |
created_at | NOT NULL | BIGINT | Timestamp indicating when the tenant was created |
updated_at | BIGSERIAL | BIGINT | |
created_by | FK ( users.id ), NOT NULL | BIGINT | |
updated_by | FK ( users.id ), NOT NULL | BIGINT |
Table - Roles
Column Name | Constraints | Data Type | Remark |
id | PRIMARY KEY, UNIQUE | BIGSERIAL | Auto-incrementing unique identifier for roles |
name | NOT NULL | VARCHAR(255) | Role name |
tenant_id | PRIMARY KEY, UNIQUE | BIGSERIAL | Tenant ID associated with the role |
description | TEXT | Description of the role | |
created_at | NOT NULL | BIGINT | Timestamp indicating when the role was created |
updated_at | NOT NULL | BIGINT | |
created_by | FK ( users.id ), NOT NULL | BIGINT | |
updated_by | FK ( users.id ), NOT NULL | BIGINT |
Table - user_role_mapping
Column Name | Constraints | Data Type | Remark |
id | PRIMARY KEY | BIGSERIAL | Auto-incrementing unique identifier for user role mapping |
user_id | FOREIGN KEY REFERENCES USER(user_id) | INTEGER | User ID associated with the role |
role_id | FOREIGN KEY REFERENCES ROLE(role_id) | INTEGER | Role ID associated with the user |
created_at | NOT NULL | BIGINT | Timestamp indicating when the mapping was created |
updated_at | TIMESTAMP | BIGINT | |
created_by | FK ( users.id ), NOT NULL | BIGINT | |
updated_by | FK ( users.id ), NOT NULL | BIGINT |
Table - features
Column Name | Constraints | Data Type | Remark |
id | PRIMARY KEY, UNIQUE, DEFAULT nextval('feature_seq') | BIGSERIAL | Auto-incrementing unique identifier for features |
api_identifier | NOT NULL, UNIQUE | VARCHAR(255) | Feature name |
api_method | NOT NULL | VARCHAR(255) | POST, PUT, PATCH, GET, DELETE |
api_url | NOT NULL | VARCHAR(255) | |
description | TEXT | Description of the feature | |
created_by | FOREIGN KEY REFERENCES USER(user_id) | INTEGER | User ID of the creator |
updated_by | FOREIGN KEY REFERENCES USER(user_id) | INTEGER | User ID of the last updater |
created_at | NOT NULL | BIGINT | Timestamp indicating when the feature was created |
updated_at | NOT NULL | BIGINT |
Table - Role Feature Mapping
Column Name | Constraints | Data Type | Remark |
id | PRIMARY KEY | BIGSERIAL | Auto-incrementing unique identifier for user role mapping |
role_id | FOREIGN KEY REFERENCES ROLE(role_id) | INTEGER | Role ID associated with the feature. UNIQ ( role_id, feature_id ) |
feature_id | FOREIGN KEY REFERENCES FEATURE(feature_id) | INTEGER | Feature ID associated with the role. UNIQ ( role_id, feature_id ) |
created_at | NOT NULL | TIMESTAMP | Timestamp indicating when the mapping was created |
updated_at | TIMESTAMP | Timestamp indicating when the mapping was last updated | |
created_by | FK ( users.id ), NOT NULL | BIGINT | |
updated_by | FK ( users.id ), NOT NULL | BIGINT |
API Specification for RBAC and Feature Management
Token-based authentication: Use JWT tokens to authenticate API requests.
Endpoint: /login
Request:
POST
Body:JSON{ "email": "user@example.com", "password": "password"
Response:
200 OK
Body: JSON { "token": "your_jwt_token", “refresh_token” : “your_refresh_token” }
401 UnAuthorized if authentication fails.
User Management
Authentication. The logged In user must be authenticated and should be the owner of the tenant !
GET: List all users: /users
POST: Create a new user : /users
GET: Retrieve a specific user: /users/{user_id}
PUT: Update a user: /users/{users_id}
DELETE: Delete a user: /users/{users_id}
Role Management
Authentication. The logged In user must be authenticated and should be the owner of the tenant !
GET: List all roles: /tenant/{tenant_id}/roles
POST: Create a new role: /tenant/{tenant_id}/roles
GET: Retrieve a specific role: /tenant/{tenant_id}/roles/{role_id}
PUT: Update a role: /tenant/{tenant_id}/roles/{role_id}
DELETE: Delete a role: /tenant/{tenant_id}/roles/{role_id}
Feature Management
Endpoint: /features
GET: List all features: Requires authentication and no special roles. This API is not tenant specific.
INSERT, UPDATE and DELETE of features to be maintained by the developers of application.
Role-Feature Mapping
Authentication. The logged In user must be authenticated and should be the owner of the tenant !
GET: List features associated with a role: /tenant/{tenant_id}/roles/{role_id}/features
POST: Associate a feature with a role: /tenant/{tenant_id}/roles/{role_id}/features
DELETE: Disassociate a feature from a role: /tenant/{tenant_id}/roles/{role_id}/features/{mapping_id}
User-Role Mapping
Authentication. The logged In user must be authenticated and should be the owner of the tenant !
POST: Associate a role with a user: /tenant/{tenant_id}/user_role_mapping. user_id and role_id is provided in the request body !
PUT: Change the role of a user: /tenant/{tenant_id}/user_role_mapping: user_id and role_id is provided in the request body !
DELETE: not supported as a user must be associated to 1 role.
GET: Not supported as we want to return the role of the user in the GET USER rest call.
Middleware
- Implement middleware to check for authentication tokens, extract user information, and enforce access control based on user roles and feature permissions.
Authentication:
Verify the user's identity using JWT tokens and extract the user's ID from the authenticated token.
Return 401 if token validation fails or if token has expired
Role Retrieval:
Fetch the user's associated role from the USER_ROLE_MAPPING table based on the user ID.
Retrieve the corresponding features from the ROLE_FEATURE_MAPPING table.
Before allowing access to a feature, check if the user's permissions include that feature.
- You can check it with the help of “api_url” + “api_method” combination or you can annotate api_identifier on top of each controller method and test it out.
If the user has permission, proceed with the request.
If the user lacks permission, return an appropriate error response (e.g., 403 Forbidden).