The topic of handling of authentication and authorization in multi-tenant applications is really broad, there are many choices to be made and design decisions that might or might not make sense depending on your needs. I’m going to provide some notes that will hopefully be useful to you, but I wouldn’t consider this complete by any means.
One of the first design decisions in a multi-tenant app is how you will handle authentication:
- Will each tenant have its own user base of email/password authentication (e.g. a DB connection for each tenant) or will there be just one source of users (a single connection)?
- will you provide your tenant with SSO capabilities (so that they can authenticate with their own identity provider)?
- Will you provide social login capabilities?
In your case, you mention that a user can be associated with more than one tenant, so let’s assume that there will be one source of users (maybe one DB connection and some social logins) shared across all tenants.
In terms of the authentication transaction, the tenant is not relevant when using a shared user base. Using a vanity URL (such as https://tenant1.app.com
) migh provide the application a hint of the user intent (“I want to see my tenant1
resources”), but the identity of the user is the same regardless of the tenant chosen. So, even if you have tenant1.yourapp.com
and tenant2.yourapp.com
, you will probably have one common authentication endpoint (e.g. yourapp.com/login
) that subdomains will use to identify who the user is when needed (that endpoint will probably do so by redirecting to Auth0’s /authorize
endpoint, and this might all happen in a popup window to ease state management). Once you know how the user is, and when the user is back at the subdomain, you would know if the user is authorized or not to the tenant. But that’s something that is better handled at the application level, not in a rule (e.g. “You tried to access tenant1
, but you are only authorized to access tenant2
and tenant3
. Which one would you like to use?”).
If you had different user stores for each tenant, the situation would be different, as you would require a hint to know which directory of users to use when authenticating. In this case, the login_hint
parameter in OAuth2comes in handy.
Then there’s the matter of authorization . You mentioned that each user has a different set of permissions for each assigned tenant. Most likely this will be a set of roles, but that’s completely up to you. Imagine that in your backend, somewhere, you have this information:
{
"user": "user1",
"tenants": [
"tenant1" : {
"roles": ["full admin"],
},
"tenant2": {
"roles": ["user", "moderator"]
}
]
}
That is information about what the user can do . You can do all authorization decisions in the backend , with just a bearer token that has the user id as a claim (plus the usual expiration, audience, issuer and signature, of course).
Imagine an access_token that simply has this information about the user:
{
"sub": "user1"
}
And your SPA would make a request to your backend API:
GET https://yourbackend.com/tenant1/api/posts/450
Authorization: Bearer xxxxxx
In this case, as the token only identifies the user, your backend will need to check user1
's permission, to see if she’s allowed to access tenant1
and, in particular, if she can read post 450 (or whatever). If you expect permissions to change on-the-fly, then that’s the model you are after.
You could put permission information in the access_token about what the user can do , but that be simply an optimization. You can write a rule that checks the user permissions somewhere, and use custom claims to convey this information in the access_token and/or id_token . Something like this (I’m simplifying here for easy reading, but you would need to use namespaced claims):
{
"sub": "user1",
"roles": "tenant1:full_admin tenant2:user tenant2:moderator"
}
At some point, putting all the permission information in the token becomes unmanageable, especially when a user has many tenants assigned and/or much granularity in the permissions (you end up with a big token). Plus, you will have to live with the fact that access_tokens have a certain validity time, and decide if you are willing to live with that compromise (what if permissions change while the token is still valid?).