Hello!
I have looked at some of the closed answers and I feel like they don’t quite cover this use case, so I feel it might be helpful to re-open this.
I explained the problem to the team with this example. I’ll start with the goals:
- we are a SaaS platform which provides services for many different merchants - let’s call it
my_saas_svc
- each merchant has a unique key - we’ll call this
merchantId
- it is possible for a single identity to be a member of multiple merchant accounts. BUT we would like the identity to have different roles for different merchant accounts
- the current proposal was to do a claims check for the merchant ID
- we also have a potential use-case for customers who are also merchants
But if the identity can be associated with multiple merchantIds, this claim would need to be an array. Which leads to a few subtle security problems.
Consider this scenario, where I’m a “bad actor” trying to exploit the system
-
XYZ corp
sells $10 Million AUD a year usingmy_saas_svc
- I get myself hired by
XYZ corp
as a consultant. They assign my identity theMerchant ReadOnly
role for merchant Idmer-123
using anXYZ corp
email address - I use my
XYZ corp
email/identity to sign myself up as a new merchant. I get theMerchant Administrator
role, with merchant IDmer-456
- I now have roles
[ "Merchant ReadOnly", "Merchant Adminstrator" ]
and a merchantIds claim of[ "mer-123", "mer-456" ]
If we base our entitlements purely on the roles that are present and make no link between the role and the merchantId that role was actually supposed to be applied for, it’s a problem because it now looks like I have both roles for both merchantIds. I’m now a Merchant Administrator
of XYZ Corp
. In other words, if my security principle pseudo-code is:
Roles:contains("Merchant Administrator") && Claims:merchantIds:contains("mer-123")
…I’ve managed to become a “Merchant Administrator” of “XYZ Corp” as far as my entitlement checks are concerned, when I was only supposed to be a “Merchant Administrator” of the organisation I created.
What is therefore needed is a SCOPED role-assignment. This is not to be confused with “oauth” scope which has a different meaning - we’ll call our scoping “breadth” to avoid confusion. Our role-assignment would need to have a “breadth” which is limited to a specific merchant, e.g.
- role assignment 1:
{ "role": "Merchant ReadOnly", "breadth": "my_saas_svc/merchant/mer-123" }
- role assignment 2:
{ "role": "Merchant Administrator", "breadth": "my_saas_svc/merchant/mer-456" }
Then my pseudo-code would become:
Roles:contains(role: "Merchant Administrator", breadth: my_saas_svc/merchant/mer-123)
Access would be denied for my identity, since the “breadth” of the assignment for that role only covers my_saas_svc/merchant/mer-456
. An example of a system that supports this kind of scoped/breadthed RBAC is Azure RBAC Steps to assign an Azure role - Azure RBAC | Microsoft Learn
I’m currently looking into “Organizations” as recommended by a colleague:
@konrad.sopala I read your comments on some of the tickets referenced here. I believe this is what those other posters were trying to explain. I also have this design issue.
EDIT: it sort of looks like organizations cover this scenario, as long as two organizations can use my_saas_svc
as the IDP for both BUT have different roles between the two organizations which are put in the token on login:
It’s not 100% clear from the documentation if that’s the case, but it kind of sounds like it. I think there’s a limit on the organizations that would mean we eventually have to bump up to enterprise though. There’s some entity limits per organization per tenant as well: