Handling roles across multiple customers/hostnames

We have multiple customers, each of whom could have multiple hostnames or app access points. Each user will have one set of roles for each domain, but could easily have different roles on different domains.

This is an extension of the question asked in this post

Ours is analogous to a school where a user A is both a teacher and a parent and can log in to the app at staff.myschool.edu and access all the teacher functionality then later log in to parent.anotherschool.edu to check their child’s grades. Same login, but different user experience depending on where you access the app.

I’m planning on using the app_metadata property of the user to store roles for each domain and then processing it inside an Auth0 rule. The choice of root key for the app_metadata array seems to be critical:

Link to Auth0 User API

  • The properties of the new object will replace the old ones.
  • The metadata fields are an exception to this rule (user_metadata and app_metadata). These properties are merged instead of being replaced but be careful, the merge only occurs on the first level.

I’m hoping to create a rule that will detect which domain has requested access, read that subtree’s values from the app_metadata, then only send back the roles relevant to that domain in the app_metadata.

Here’s what the rule would effectively do. If this is the object stored by Auth0:

{
  "username": "Michelle Smith",
  "app_metadata": {
    "portal.domaina.com": "Administrator", "Scheduler"],
    "my.domainb.com": "Customer"]
  }
} 

When accessed from portal.domaina.com this will be returned:

{
  "username": "Michelle Smith",
  "app_metadata": {
    "roles": "Administrator", "Scheduler"]
  }
}

When accessed from my.domainb.com it will look like this:

{
  "username": "Michelle Smith",
  "app_metadata": {
    "roles": "Customer"]
  }
} 

I have read on this forum that the limit on the size of the user object is 16 MB. That lets you store a massive number of roles per user before you need to consider other strategies.

Managing which roles a user has on a given domain will require calling the Auth0 management api, and because of the section quoted above I know we can just send an update to a single domain without impacting the others that might be in that user’s profile.

I am hoping that someone else with more experience in this can weigh in here and let us know what the best practices are for accomplishing this.

In general your approach is suitable although the security aspects of it would always be dependent of the full implementation. For example, would a client application be able to get tricked into simulating that the request is from another application/domain and as such receive roles not meant to itself.

I would advise you also, if you haven’t done so already, to take a look at the authorization extension documentation as I see some overlap between what it offers and what you’re trying to achieve. In particular, when you create a role in the extension it already asks you to which application this role is associated to so you may get a quicker implementation of you requirements by leveraging the extension.

Thanks, the authorization extension looks very interesting. However, it doesn’t seem to address the multi-tenant requirement. It would just shift the burden on our code from querying the data store to pull in the user’s roles for the given domain to querying the data store to retrieve the Auth0 application settings. I’ve read the docs a few times and can’t see a way to inject a filter into the defined roles based on the incoming hostname.

As for tricking the client, we’re also going to be enforcing these roles on the server which should make it practically impossible to spoof sites.

As a quick follow-up to this, we were able to successfully implement this using Azure Functions on the back-end to populate the roles and this rule to attach the current host’s roles into the token:

function (user, context, callback) {
  
  var namespace = 'http://yourdomain.com/';
  var url = require('url');
  var hostname = url.parse(context.request.query.redirect_uri).hostname;
  hostname = hostname.replace(/\./g, '_');
  var roles = ];
  
  if(user.app_metadata === undefined || user.app_metadata[hostname] === undefined || user.app_metadata[hostname].roles === undefined){
    roles = ];
  } else {
    roles = user.app_metadata[hostname].roles;
  }
    
  context.idToken[namespace + 'roles'] = roles;
  
  callback(null, user, context);
}

Here’s what my user’s app_metadata looks like:

{
  "localhost": {
    "roles": 
      "Administration",
      "CheckIn"
    ]
  },
  "development_sportsmgmt_net": {
    "roles": 
      "Administration",
      "CheckIn"
    ]
  }
}