Multi-tenancy authentication done right
Tags: authentication, authorization, security, tooling
Published: 2023-04-17 14:28:56

## The Problem

Implementing complex identity structures securely is an extremely challenging task and more often than not becomes a textbook example of sunk cost fallacy.

Let’s take a B2B SaaS application as an example. Imagine you are developing an issue tracker application like Jira, let's call it Trackalot. The development journey normally goes something like this:

1. You start with a simple tag, let’s call it `organization`, in your users table to group users belonging to different customers, and you embed logic in your app to look up the `organization` field for conditional rendering and similar. <br>
   This is looking great – a small database migration and a couple of PRs later and you are done (or so you think)!

2. Your company is doing great and you start onboarding your first enterprise customer who has a few hundred users. Your product manager comes in and explains that the deal is not going to close unless the customer can specify their own multi-factor authentication (MFA) policy. So now you rush to create an `organizations` table, refactor all your code to account for the new table, migrate the database again and then write some custom logic to handle the different MFA flows that each customer may have.<br>
   It’s a fair amount of work but you got this, you crunch a little and deliver!

3. At this point, you might or might have not gotten the customer from (2) depending on how many sleepless nights you spent writing code. But great news: you now have an organization table and you can extend it easily! Now your product manager comes to you and explains that one of the customers needs custom templates for the magic links sign-in emails. Now you are at a crossroad, do you:

   - Create a tenant per customer in your Identity and Account Management (IAM) product/solution. A crippling doubt settles in: How are you going to login users belonging to different tenants? You park the thought for now, thinking “hmm, maybe subdomains”.
   - Create a field in the `organizations` table which links to the custom email template. The problem is that your IAM product doesn’t really support multiple templates per user, so how are you going to send these emails? Firing up your own service to use AWS Simple Email Service (SES)?
   - Scrap the IAM product you are using and build all of your identity logic yourself<br>

   Eventually you settle on one of these, most likely the first one, and call it victory.

4. Now your product manager (who’s slowly becoming your work nemesis) comes back to you and says that, actually, what your app really needs is role-based access control (RBAC). So you find yourself wondering:
   - Where do you store these roles?
   - How do you enforce them?
   - Are they per organization or global?
   - Should they be configurable or are they fixed?
5. Your company launches a new feature where users can belong to multiple organizations or workspaces, just like Figma or GitHub. An impending sense of doom settles in:
   - How are you going to share these users?
   - Do you have to mask the user ID so that different organizations can’t correlate users?
   - What about different RBAC roles per org?

<br>

This doesn’t even account for: the various vulnerabilities you accidentally introduced while trying to ship as quickly as possible (trust us, they are pretty tough to [spot](https://salt.security/blog/traveling-with-oauth-account-takeover-on-booking-com)!); the not insignificant user entity reconciliation burden you have now placed on the data science team because you have multiple user tables; and the inability to comply with GDPR and data localization laws that large customers really care about and that will cost you a 7-figure contract.

![Just changing lightbulbs](https://www.slashid.dev/blog/ditch-orgs/im_just_trying_to_change_this_lightbulb.gif)

While this is an overly dramatic narrative, it matches pretty consistently what most companies go through: implementing effective and scalable IAM takes several engineers, several lost deals, false starts and months of development effort. Not to mention long term maintenance as your product features (and your backend architecture) grow exponentially.

Our hope with suborgs is to remove all this undifferentiated complexity and security risk so you can focus on actually implementing the core logic of your app (i.e., the thing you were hired to develop).

Let’s see how this would look if step 0 had been registering with SlashID.

## The Solution with SlashID

Now let’s see how this would have gone if you’d started off using SlashID and our suborganizations features.

1. You need to separate the users of your different customers → This is available out of the box with SlashID suborgs.
   As a SlashID customer, your company has a SlashID organization, which can have as many child suborgs as you need. Each suborg is independent and fully-featured with authentication and user management, but you still retain control. With just one API call you can spin up a new suborg for each new customer you onboard, each with their own user base.
2. Your customers have different MFA requirements -> Done!
   Each suborg has the full suite of SlashID authentication features, including customisable and step-up MFA. You are free to change your individual suborgs’ configuration to suit your customers’ specific needs.
   By using SlashID you didn’t even need to ship a new feature to close a new deal - it was already there, ready to go.
3. Your customers want customized templates for authentication emails -> Already done!
   As above, each suborg has independent configuration, including customizable email and SMS templates, so each one of your customers can have their own branded comms for their end users. Victory is assured.
4. Role-based access control no longer fills you with fear. Each suborg can have its own set of [person groups](https://developer.slashid.dev/docs/guides/suborgs). SlashID user JWTs have a claim with a user’s groups, and our React SDK has components for conditional rendering and access by group. Or you can use the token claims on your backend to make authorization decisions.
5. Users shared across multiple organizations? No sense of doom here.
   By default, each suborg has its own independent user base, with unique IDs per user. However, if you want to add seamless but secure switching between apps, you can do that too: suborgs can be configured to share some information so you get consistent user identity across them.

There you have it - minutes of development work instead of months; well-rested engineers; and you get to stay friends with your product manager.

Jokes aside, we created suborgs after countless conversations with our customers, to respond to the challenges dev teams face when trying to model complex organization structures with their IAM product.

## A real world example in 10 minutes or less

Going back to the Trackalot example, how would you implement it with suborgs?

> You can also find more examples in our [documentation](https://developer.slashid.dev/docs/guides/suborgs).

### Step 1: Create a suborg for each customer

Creating a suborg is a simple API call:

```shell
curl -X POST --location 'https://api.slashid.com/organizations/suborganizations' \
--header 'SlashID-OrgID: <ORGANIZATION_ID>' \
--header 'SlashID-API-Key: <API_KEY>' \
--header 'Content-Type: application/json' \
--data '{
    "sub_org_name": "Parks & Rec Dept",
    "groups_org_id": "85637a0a-a574-326a-bac3-d1f46d62dbd9"
}'
```

In this example we are specifying that the suborg should have the same group pool (`groups_org_id`) as the parent org - this way
they can share the same RBAC roles.

This is the response, containing the ID and API key of the new suborg:

```shell
{
   "result": {
       "api_key": "wY7gDtUDjxGMdynd6BKaaLojHFE=", // API key of the new suborganization
       "id": "97724371-a0a1-4b93-bf51-6ba2feb1acdf", // ID of the new suborganization
       "org_name": "Parks & Rec Dept", // as in the request
       "tenant_name": “Trackalot" // same as for the parent organization
   }
}
```

Let’s create another one:

```shell
curl -X POST --location 'https://api.slashid.com/organizations/suborganizations' \
--header 'SlashID-OrgID: <ORGANIZATION_ID>' \
--header 'SlashID-API-Key: <API_KEY>' \
--header 'Content-Type: application/json' \
--data '{
    "sub_org_name": "Dunder Mifflin Paper Company",
    "groups_org_id": "85637a0a-a574-326a-bac3-d1f46d62dbd9"

}'


{
    "result": {
        "api_key": "FkImXyWgZhuqTGTh70fXzuI1PMo=",
        "id": "d89e29fc-2693-d3b7-764c-debc9561eea2",
        "Org_name": "Dunder Mifflin Paper Company",
        "tenant_name": "Trackalot" // same as for the parent organization
    }
}
```

We have built the following org structure:

![Suborgs](https://www.slashid.dev/blog/ditch-orgs/orgs_blue.jpeg)

Now that the Parks & Rec Dept and Dunder Mifflin Paper Company are ready, we are one step closer to helping them avoid those 94 meetings a day with Trackalot.

### Step 2: Customize authentication policies

You can use the [Organization APIs](https://developer.slashid.dev/docs/category/api/core/organizations) to customize the behavior of suborgs. So for example, you can change the email template used for magic links by the Parks & Rec Dept suborg with the following call:

```shell
curl -X POST --location 'https://api.slashid.com/organizations/suborganizations' \
--header 'SlashID-OrgID: 97724371-a0a1-4b93-bf51-6ba2feb1acdf' \
--header 'SlashID-API-Key: wY7gDtUDjxGMdynd6BKaaLojHFE=' \
--header 'Content-Type: application/json' \
--data '{
    "email_authn_challenge": <EMAIL_TEMPLATE>
}'
```

This won’t impact the template for Dunder Mifflin Paper Company or any other suborg.

Note how problems (2) and (3) from the scenario above are solved with a single API call vs endless coding, scoping and back and forth with your product manager.

### Step 3: Create roles for RBAC and assign them to users

Now you have some customers, you can create groups to more easily manage users, starting with `employee` and `admin`, using the [SlashID groups API](https://developer.slashid.dev/docs/api/access/create-a-group/#create-a-group):

```shell
curl -X POST --location 'https://api.slashid.com/groups' \
--header 'SlashID-OrgID: 85637a0a-a574-326a-bac3-d1f46d62dbd9' \ // "Trackalot" org ID
--header 'SlashID-API-Key: /cgMbOUYjFGMUv4hIvKoMxhVIJ0=' \ // "Trackalot" API key
--header 'Content-Type: application/json' \
--data '{
    "name": "employee"
}'

curl -X POST --location 'https://api.slashid.com/groups' \
--header 'SlashID-OrgID: 85637a0a-a574-326a-bac3-d1f46d62dbd9' \ // "Trackalot" org ID
--header 'SlashID-API-Key: /cgMbOUYjFGMUv4hIvKoMxhVIJ0=' \ // "Trackalot" API key
--header 'Content-Type: application/json' \
--data '{
    "name": "admin"
}'
```

We used the organization ID for Trackalot to create the groups, and we see that listing the groups for the suborgs returns the expected result:

```shell
curl -X GET --location 'https://api.slashid.com/groups' \
--header 'SlashID-OrgID: d89e29fc-2693-d3b7-764c-debc9561eea2' \ // Dunder Mifflin Paper Company org ID
--header 'SlashID-API-Key: FkImXyWgZhuqTGTh70fXzuI1PMo=' // Dunder Mifflin Paper Company API key

{
    "result": [
        "employee",
        "admin"
    ]
}
```

The same API call with the Parks & Rec organization ID returns the same results, as expected - they share a group pool, so the groups are shared.

```shell
curl -X GET --location 'https://api.slashid.com/groups' \
--header 'SlashID-OrgID: 97724371-a0a1-4b93-bf51-6ba2feb1acdf' \ // Parks & Rec Dept org ID
--header 'SlashID-API-Key: wY7gDtUDjxGMdynd6BKaaLojHFE=' // Parks & Rec Dept API key

{
    "result": [
        "employee",
        "admin"
    ]
}
```

It is outside the scope of this blogpost but with SlashID suborg you have the freedom to not share users or groups pools. See our documentation for a few examples of how to do that.

### Step 4: Sharing users

Lastly, if Trackalot takes off and you want to allow end users to be part of multiple organizations, you can also do that easily through our person pools.

By default, suborgs have their own dedicated pool of users, but they can also share users through person pools. This means that if the same person works for both Parks & Recs and Dunder Mifflin, they will have a consistent unique identifier in all suborgs sharing that person pool as long as they use the same handles (email addresses or phone numbers) to register.

You can read more about person pools in our [guide](https://developer.slashid.dev/docs/guides/suborgs).

But it doesn't end there, thanks to our buckets you are also able to share [attributes](https://developer.slashid.dev/docs/concepts/attribute_buckets) across users and organizations.

## Conclusion

In this blogpost we’ve shown how you can use SlashID suborgs to implement complex user structures flexibly and securely, without wasting months of dev time on it and without risking a breach.

Look out for future guides and blog posts, where we will deep dive into more case studies to explore DataVault, attribute-based access control (ABAC), and role-based access control (RBAC).

Have questions or want to find out more? Check out our [documentation](https://developer.slashid.dev/) or [reach out to us](mailto:support@slashid.dev).

Ready to get started with SlashID? [Register here!](https://www.slashid.dev/request-access/)

## Read more

- [More suborg use cases](https://developer.slashid.dev/docs/guides/suborgs)
- [Conditional RBAC with our React SDK](https://www.slashid.dev/blog/groups-react/)