{"id":545,"date":"2026-06-11T08:45:44","date_gmt":"2026-06-11T08:45:44","guid":{"rendered":"https:\/\/blog-origin.donely.ai\/blog\/role-based-access-control-setup-for-multi-tenant-saas\/"},"modified":"2026-06-11T08:45:44","modified_gmt":"2026-06-11T08:45:44","slug":"role-based-access-control-setup-for-multi-tenant-saas","status":"publish","type":"post","link":"https:\/\/blog-origin.donely.ai\/blog\/role-based-access-control-setup-for-multi-tenant-saas\/","title":{"rendered":"How to Set Up Role\u2011Based Access Control for Multi\u2011Tenant SaaS"},"content":{"rendered":"<p>Getting RBAC right is a make\u2011or\u2011break issue for any SaaS that serves many customers from the same code base. If a user from one tenant can peek at another tenant\u2019s data, the whole business is at risk. In this guide we walk through every piece you need to lock down access, from defining tenants to auditing every role change.<\/p>\n<p>By the end you\u2019ll have a clear roadmap to build a secure, scalable RBAC system that works for your multi\u2011tenant product.<\/p>\n<nav class=\"table-of-contents\" style=\"background: #fafafa;border: 1px solid #ebebeb;border-radius: 10px;padding: 1em 1.25em;margin: 1.5em 0\">\n<h3>Table of Contents<\/h3>\n<ul>\n<li><a href=\"#step-1-define-tenant-and-role-hierarchy\">Step 1: Define Tenant and Role Hierarchy<\/a><\/li>\n<li><a href=\"#step-2-design-permission-model-actions-and-resources\">Step 2: Design Permission Model (Actions and Resources)<\/a><\/li>\n<li><a href=\"#step-3-implement-authentication-and-tenant-context\">Step 3: Implement Authentication and Tenant Context<\/a><\/li>\n<li><a href=\"#step-4-enforce-authorization-middleware\">Step 4: Enforce Authorization Middleware<\/a><\/li>\n<li><a href=\"#step-5-manage-and-audit-rbac-with-admin-panel\">Step 5: Manage and Audit RBAC with Admin Panel<\/a><\/li>\n<li><a href=\"#faq\">FAQ<\/a><\/li>\n<li><a href=\"#conclusion\">Conclusion<\/a><\/li>\n<\/ul>\n<\/nav>\n<h2 id=\"step-1-define-tenant-and-role-hierarchy\">Step 1: Define Tenant and Role Hierarchy<\/h2>\n<p>First, map out who your tenants are and how roles sit inside each tenant. Think of a tenant as a company that signs up for your platform. Within that company you might have admins, managers, and regular users. Each of those roles gets a different set of permissions.<\/p>\n<p>Write down the hierarchy on a whiteboard or in a simple spreadsheet. List every role you expect to see, then note which roles inherit from which. For example, an<strong>Admin<\/strong>can do everything a<strong>Manager<\/strong>can, plus extra tasks like adding new users.<\/p>\n<p>Why keep the hierarchy flat? A flat tree is easier to audit and reduces the chance of permission creep. When you add a new role later, you only need to slot it into the existing tree.<\/p>\n<p>Here\u2019s a quick template you can copy:<\/p>\n<ul>\n<li>Tenant<\/li>\n<li>\u2514\u2500 Admin<\/li>\n<li>\u2514\u2500 Manager<\/li>\n<li>\u2514\u2500 Employee<\/li>\n<\/ul>\n<p>Make sure every role maps to a real job function in your customers\u2019 org charts. If a role doesn\u2019t match a real job, you\u2019ll end up granting more rights than needed.<\/p>\n<div class=\"pro-tip\" style=\"background: linear-gradient(135deg, #fffbeb, #fef3c7);border-left: 4px solid #f59e0b;padding: 1em 1.5em;margin: 1.5em 0;border-radius: 0 8px 8px 0\"><strong>Pro Tip:<\/strong> Use a naming convention like<code>tenantId:roleName<\/code>(e.g.,<code>123:admin<\/code>) so you can always tell which tenant a role belongs to.<\/div>\n<p>Once the hierarchy is set, you can store it in a database table called<code>roles<\/code>with columns for<code>role_id<\/code>,<code>parent_role_id<\/code>, and<code>description<\/code>. That makes it easy to query a role\u2019s inherited permissions later.<\/p>\n<p><a href=\"https:\/\/donely.ai\" rel=\"noopener\" target=\"_blank\">Donely<\/a>\u2019s own dashboard lets you spin up separate tenant instances and assign per\u2011hub roles, which is a good reference for how a real product does this at scale.<\/p>\n<p><img decoding=\"async\" alt=\"A realistic diagram of a multi\u2011tenant hierarchy showing tenant boxes with stacked role icons, clean lines, professional \" loading=\"lazy\" src=\"https:\/\/rebelgrowth.s3.us-east-1.amazonaws.com\/blog-images\/batch_66618_0_af24e38b4985.png\" \/><\/p>\n<p>When you\u2019re done, you should be able to answer three questions instantly: Who belongs to which tenant? What role does each user have? Which roles inherit from which others?<\/p>\n<h2 id=\"step-2-design-permission-model-actions-and-resources\">Step 2: Design Permission Model (Actions and Resources)<\/h2>\n<p>Next, decide what actions each role can perform on which resources. An action is a verb like<em>read<\/em>,<em>write<\/em>, or<em>delete<\/em>. A resource is the thing being acted on, such as a<code>project<\/code>or<code>invoice<\/code>record.<\/p>\n<p>Azure\u2019s official RBAC guide explains that a role definition is a collection of permissions, and each permission lists allowed actions and the scope they apply to. The model is additive, so a user gets the union of all permissions from every role they hold. <a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/role-based-access-control\/overview\">Microsoft Azure RBAC overview<\/a><\/p>\n<p>AWS\u2019s multi\u2011tenant guidance adds the idea of a per\u2011tenant policy store, which lets you keep each tenant\u2019s rules separate and prevents cross\u2011tenant leaks.AWS multi\u2011tenant policy example<\/p>\n<p>Start by listing every resource type your SaaS exposes via APIs. Then, for each role, write down the exact actions it needs. Avoid broad verbs like \u201cmanage everything\u201d. Instead, be specific:<code>read:invoice<\/code>,<code>update:customer<\/code>,<code>delete:session<\/code>.<\/p>\n<p>Store these rules in a<code>permissions<\/code>table with columns for<code>role_id<\/code>,<code>action<\/code>,<code>resource_type<\/code>, and<code>scope<\/code>. The<code>scope<\/code>column holds the tenant ID so the same permission can apply to many tenants without duplication.<\/p>\n<p>When you query a user\u2019s effective permissions, pull all rows where<code>role_id<\/code>matches any of the user\u2019s assigned roles and<code>scope<\/code>matches the current tenant. Then combine the actions into a set.<\/p>\n<div class=\"key-takeaway\" style=\"background: linear-gradient(135deg, #eff6ff, #dbeafe);border-left: 4px solid #2563eb;padding: 1em 1.5em;margin: 1.5em 0;border-radius: 0 8px 8px 0\"><strong>Key Takeaway:<\/strong> A fine\u2011grained permission matrix lets you enforce least\u2011privilege at the API level.<\/div>\n<p>Remember to review the matrix with a real customer early on. Ask them to walk through a typical workflow and point out any missing or extra actions.<\/p>\n<h2 id=\"step-3-implement-authentication-and-tenant-context\">Step 3: Implement Authentication and Tenant Context<\/h2>\n<p>Authentication proves who a user is. Once you know the user, you need to know which tenant they belong to. The common pattern is to embed the tenant ID in the user\u2019s JWT token after they log in.<\/p>\n<p>When a user signs in, your auth server looks up the user\u2019s tenant and role, then issues a token that contains claims like<code>sub<\/code>(user ID),<code>tid<\/code>(tenant ID), and<code>roles<\/code>(a list of role IDs). Your API layer reads those claims on every request.<\/p>\n<p>Here\u2019s a tiny code sketch (in any language) that extracts the tenant ID:<\/p>\n<pre><code>const token = getAuthHeader();\nconst payload = decodeJwt(token);\nconst tenantId = payload.tid;\nconst userRoles = payload.roles;<\/code><\/pre>\n<p>After you have the tenant ID, you can pass it to the permission check routine you built in Step\u202f2. That way the same permission row works for every tenant because the<code>scope<\/code>filter matches the token\u2019s<code>tid<\/code>.<\/p>\n<p><iframe allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen=\"\" frameborder=\"0\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/b6VhN_HHDiQ\" width=\"560\"><\/iframe><\/p>\n<p>Don\u2019t forget to rotate signing keys regularly and store them in a secrets manager. If a key leaks, an attacker could forge tokens for any tenant.<\/p>\n<p>We also recommend adding a short\u2011lived token (10\u201115 minutes) and a refresh token flow. That limits the window an attacker has if they steal a token.<\/p>\n<h2 id=\"step-4-enforce-authorization-middleware\">Step 4: Enforce Authorization Middleware<\/h2>\n<p>Now that you can read a user\u2019s roles and tenant, you need a piece of code that sits in front of every API endpoint and decides if the request is allowed. This is the authorization middleware.<\/p>\n<p>AWS\u2019s guide recommends a Policy Decision Point (PDP) that evaluates policies written in a declarative language. You can build a simple PDP yourself or use a managed service like Amazon Verified Permissions. The key is to keep the decision logic out of individual endpoint handlers so you don\u2019t accidentally diverge.<\/p>\n<p>In practice, the middleware does three things:<\/p>\n<p>Here\u2019s a pseudo\u2011code example:<\/p>\n<pre><code>function authorize(req, res, next) { const { tid, roles } = decodeJwt(req.headers.authorization); const { action, resource } = req.body; const allowed = checkPermissions(roles, action, resource, tid); if (allowed) { next(); } else { res.status(403).send('Forbidden'); }\n}<\/code><\/pre>\n<p>Because the middleware runs for every request, you get a single place to add logging, rate\u2011limiting, or extra checks like IP whitelisting.<\/p>\n<p>Wikipedia gives a concise definition of RBAC that you can cite for readers who need a quick reference. <a href=\"https:\/\/en.wikipedia.org\/wiki\/Role-based_access_control\">Wikipedia , Role\u2011based access control<\/a><\/p>\n<p>When you add the middleware, test it with a script that tries every combination of role, tenant, and action. Any accidental allowance will show up in the test log.<\/p>\n<h2 id=\"step-5-manage-and-audit-rbac-with-admin-panel\">Step 5: Manage and Audit RBAC with Admin Panel<\/h2>\n<p>Even the best code needs a UI for admins to assign roles, add new tenants, and review audit logs. Build a simple admin console that shows a list of tenants, each with a button to manage its users and roles.<\/p>\n<p>In the UI, let an admin pick a user, then check or uncheck boxes for each role. When they save, call an API that updates the<code>user_roles<\/code>table. Keep the UI read\u2011only for non\u2011admin users.<\/p>\n<p>Audit logs are essential for compliance. Every time a role assignment changes, write an immutable entry that includes who made the change, when, and what the old and new values were. Donely\u2019s audit\u2011log feature does exactly this, and you can follow a similar pattern.<\/p>\n<p>For extra safety, add a \u201csoft delete\u201d flag on users so you can deactivate accounts without losing history.<\/p>\n<p><img decoding=\"async\" alt=\"A realistic admin dashboard screenshot showing tenant list, role assignment modal, and audit log table, professional UI \" loading=\"lazy\" src=\"https:\/\/rebelgrowth.s3.us-east-1.amazonaws.com\/blog-images\/batch_66618_1_6e5a960c7641.png\" \/><\/p>\n<p>When an admin views the audit log, let them filter by tenant, user, or date range. That makes investigations quick.<\/p>\n<p>Finally, schedule a regular review (monthly or quarterly) where you compare the current permission matrix against actual usage data. Remove any roles that haven\u2019t been used in the last 90 days to keep the surface area small.<\/p>\n<h2 id=\"faq\">FAQ<\/h2>\n<h3>What is the difference between RBAC and ABAC?<\/h3>\n<p>RBAC assigns permissions to roles, then users inherit those roles. ABAC (attribute\u2011based access control) evaluates policies based on user attributes, resource attributes, and environmental factors. RBAC is simpler to audit, while ABAC can express more complex conditions. For most SaaS apps, start with RBAC and add ABAC only if you need dynamic rules like time\u2011of\u2011day restrictions.<\/p>\n<h3>How do I ensure tenant isolation at the database level?<\/h3>\n<p>Use a tenant identifier column on every table that stores customer data. Add a database\u2011level row\u2011level security policy that forces every query to include<code>WHERE tenant_id = :currentTenant<\/code>. Many cloud databases, such as PostgreSQL, support built\u2011in row\u2011level security that you can enable once per tenant.<\/p>\n<h3>Can I reuse the same role definitions across tenants?<\/h3>\n<p>Yes. Define a global set of role templates (Admin, Manager, User) and then instantiate them per tenant. Store the template ID so you can update the definition in one place and have the change roll out to all tenants that use that template.<\/p>\n<h3>What should I log for compliance?<\/h3>\n<p>Log every role assignment change, every successful and failed authentication event, and every authorization decision that denies access. Include timestamp, actor ID, tenant ID, and the resource that was accessed. Keep logs immutable for the period required by regulations such as GDPR or SOC\u202f2.<\/p>\n<h3>How do I handle temporary improved privileges?<\/h3>\n<p>Create a \u201cprivileged\u201d role that can be assigned for a short window. When a user needs extra rights, grant the role with an expiration timestamp. Your middleware should check the current time against the expiration and automatically revoke the role when the window closes.<\/p>\n<h3>Is it safe to store JWT secrets in code?<\/h3>\n<p>No. Store signing keys in a secrets manager or hardware security module. Rotate them regularly and limit access to the service that issues tokens. If a key is compromised, you can revoke it without affecting other tenants.<\/p>\n<h3>What tools can help me manage secrets for RBAC?<\/h3>\n<p>Secret\u2011management platforms let you store API keys, database passwords, and JWT signing keys securely. They also provide audit trails for who accessed each secret. A good example is the list of <a href=\"https:\/\/envmanager.com\/blog\/secrets-management-tools\">Best Secrets Management Tools for 2026<\/a>, which covers options that integrate with cloud providers.<\/p>\n<h2 id=\"conclusion\">Conclusion<\/h2>\n<p>Setting up role\u2011based access control for a multi\u2011tenant SaaS app is a series of deliberate steps: define a clear tenant\u2011role hierarchy, build a fine\u2011grained permission matrix, tie authentication to tenant context, enforce decisions with centralized middleware, and give admins a safe UI plus immutable audit logs. Follow the pattern above and you\u2019ll reduce the risk of data leaks, simplify compliance, and give your customers confidence that only the right people see the right data.<\/p>\n<p>If you want to see a real\u2011world example of how a platform handles multi\u2011tenant isolation, on <a href=\"https:\/\/donely.ai\/blog\/multi-tenant-saas-platform-for-multiple-client-instances\">Best Multi\u2011Tenant SaaS Platforms for Client Instances (2026)<\/a>. It shows the same principles in action and explains how Donely\u2019s built\u2011in audit logs complement RBAC.<\/p>\n<\/p>\n<ol>\n<li>Extract the tenant ID and role list from the JWT.<\/li>\n<li>Look up the permission matrix for the requested<code>action<\/code>and<code>resource<\/code>.<\/li>\n<li>Allow the request if the action is in the user\u2019s permission set; otherwise return a 403.<\/li>\n<\/ol>\n","protected":false},"excerpt":{"rendered":"<p>Getting RBAC right is a make\u2011or\u2011break issue for any SaaS that serves many customers from the same code base. If a user from one tenant can peek at another tenant\u2019s data, the whole business is at risk. In this guide we walk through every piece you need to lock down access, from defining tenants to [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":546,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-545","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ai-agents"],"_links":{"self":[{"href":"https:\/\/blog-origin.donely.ai\/blog\/wp-json\/wp\/v2\/posts\/545","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog-origin.donely.ai\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog-origin.donely.ai\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog-origin.donely.ai\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog-origin.donely.ai\/blog\/wp-json\/wp\/v2\/comments?post=545"}],"version-history":[{"count":0,"href":"https:\/\/blog-origin.donely.ai\/blog\/wp-json\/wp\/v2\/posts\/545\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog-origin.donely.ai\/blog\/wp-json\/wp\/v2\/media\/546"}],"wp:attachment":[{"href":"https:\/\/blog-origin.donely.ai\/blog\/wp-json\/wp\/v2\/media?parent=545"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog-origin.donely.ai\/blog\/wp-json\/wp\/v2\/categories?post=545"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog-origin.donely.ai\/blog\/wp-json\/wp\/v2\/tags?post=545"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}