A random post on multi-tenant app registrations, permissions, etc.

This one’s going to be all over the place. Feel free to skip.

So I signed up for a Flexibits subscription today. The make the excellent Fantastical calendar app for iOS and until recently I didn’t bother signing up for a subscription as I only use a calendar occassionally, and iOS only was fine, so why bother paying. But off late I have been getting older and wiser 😎 and now I put my events and things to do in a calendar + task manager (Todoist). I have my work calendar (M365) which has the work related stuff, I have my personal calendar (FastMail) which has other stuff… and typically I manage the personal calendar via Fantastical in iOS or the Calendar app in macOS or just FastMail’s not-so-bad web UI for calendars. But if I am getting serious it’s time to start using Fantastical on macOS too, which means signing up for a subscription, which in turn means I might as well add my work calendar too to it so I have a unified view in Fantastical (and also hook up Todoist so now I have Fantastical as my one source for work & personal calendar and task manager – sweet!)

Anyways, all this is incidental. When I tried adding my work calendar in Fantastical it wanted the admin consent as you’d expect. Their website has a helpful Url you can provide your admins so they can consent:

I broke the Url with line-breaks above for better legibility. Put this in a browser and you get a pop-up like the following:

The permissions required are standard stuff you’d expect from a calendar app. It needs access to your calendar, be able to access other mailboxes as you, read-write to your calendar etc. It’s difficult to gauge from the wording but these are actually delegated permissions. Which means you are giving the app permissions to do all, but in reality it can’t do anything unless you the person logging in as this app has permissions to do the same. So if your account can’t read anyone else’s mailboxes or see their basic info then the Fantastical app too wouldn’t be able to do it. What you are doing above is defining the boundary of the app itself, but what it can really do comes down to what the user logging in to it can do. Conversely, if the user logging in to it is a super admin and can view everyone’s emails for instance, the Fantastical app wouldn’t be able to do so even as the logged in user as it has only rights to calendars and not emails. The effective permissions thus is what both you the person logging into the app, and what the app itself has been granted.

As an aside the other type of permissions is application permissions. Here you are giving the app itself permissions. The app doesn’t need anyone to login to it, it has whatever permissions you define available for itself. Suppose Fantastical were to ask for application permissions here’s an example of how its screen would look (this is a custom app I created; it doesn’t ask for all the permissions Fantastical asked for which is why the list looks smaller, but you get an idea of the eqvuivalent permissions based on the wording):

The problem is the wording is kind of subtle. In the application permissions version it says “all mailboxes” so I know that’s what it is asking for, but the requested permissions in the delegated permissions variant aren’t very explicit that it’s for the signed in user only. Sure, if you have experience doing a bunch of these you get the idea… or you could consent and then go to the Enterprise Applications section of Azure AD and see what the heck you have consented for in the Permissions section. But the thing that bugged me was 1) my understanding was that the Url asking for admin consent is supposed to also give a list of the permissions it needs and 2) if I try and add the Fantastical app beforehand to my Azure AD tenant so I can explicitly consent to the permissions as delegated permissions I can’t find the app at all (neither by name nor by its client id) – what was going on??

Reading more into this to clarify my understanding I found this Microsoft doc on admin consents. Here’s a small screenshot coz I am lazy:

First off this one was to an adminconsent end-point (which made sense) while the Fantastical one was to an authorize end-point but with a prompt=admin_consent parameter (which I understand is also fine and can be used to ask for admin consent (alternatively, do prompt=consent for user consent; this only works if none of the permissions requested for need an admin consent)). Either way of asking for consent is fine (see this doc for the adminconsent endpoint) but what bugged me was that both documents say the Url must contain a list of scopes (this is what part that had bugged me initially coz I remember reading you are supposed to pass on scopes).

Here’s a screenshot from the adminconsent doc, notice it says scope is required.

Here’s another doc for the adminconsent end-point, and it too says the same. (This doc’s a good read in general too).

And it’s similar for the authorize endpoint:

So that’s just silly! It’s a required parameter but apparently not so required.

To test this I made a new App Registration with some basic delegated permissions but including one that needed admin consent (I just picked one that required admin consent; but see this link for a list of permissions and which ones need admin consent).

I create a Url like this:

And yup, it works fine! So irritating. That scope parameter is definitely not required. I suppose that means there’s no way for me to find from the Url what scopes I am consenting for as an admin (except actually do it and then go back and check the permissions).

OK, onto the second question. This client_id of 395befa1-fd95-454c-8286-2948ada76320 – where was that coming from? It had to be a multi-tenant app registration, that was my hunch. I hadn’t created one of these before so tried creating one in one of my tenants and seeing if I can admin consent to it in another.

Creating this was straight-forward. It’s similar to a regular app registration but I select it to be multi-tenant.

I wanted to replicate Fantastical and added some permissions. One of the permissions Fantastical asks for is to Exchange Online, so I added that too via “APIs my Organization uses” and selecting “Office 365 Exchange Online”.

(Step 1)

(Step 2)

(Final list)

Once I admin consent to this app registration in its parent tenant (the one I am creating it in basically) then I can use that same client_id and create a link to admin consent to it in another tenant:

Something I should have mentioned earlier… notice there’s no prompt=admin_consent parameter above. That’s coz this is optional. If an app needs admin consent then it’s best you specify this coz else it defaults to user consent. If an admin signs in with the above Url (which has no prompt=admin_consent parameter) then they can consent and use the app, but it would be a consent only for them and not the whole organization (which is a good way to test out the permissions of an app actually – if you get a Url similar to the Fantastical one change the admin_consent to consent (or just remove it) and sign in with your admin account and consent to it so you can see what permissions are granted, and if all looks good you can then do an admin consent).

Back to what I was doing above though, the Url I put there with the client_id from my first tenant works fine in the other tenant. That’s interesting. So it explains why I didn’t see Fantastical but was able to add it – presumeably there’s a switch somewhere which lets one “publish” the app so others can see it under Enterprise Applications, but even if one doesn’t it is possible for others to add your app registration to their tenant as long as you have created it as multi-tenant one. You don’t even need to know the tenant_id, just the client_id is enough to add.

So that’s it! Like I said, this is probably common knowledge to most people, but I wasn’t aware… and it was good to stumble upon all this tangentially thanks to me being curious as to how Fantastical worked.

Peace! 🤟🏼

Just adding a bunch of links (which I linked to above already) for easy reference later: