Azure Active Directory (AAD) has a wonderful Business-to-Business (B2B) collaboration feature which allows an enterprise to share its applications with external users. The users become 'Guests accounts' in AAD and can securely access the enterprise applications. If proper restrictions are not imposed on applications, the guest accounts could access content which is not intended for them. This blog post outlines some of the restrictions that can be put in place to prevent over-sharing of content with guest accounts. Target audience - Primarily developers.
AAD B2B Invite and Guest Accounts
AAD B2B Invite is a fantastic feature which promotes secure sharing of content without the need for sharing authentication material (e.g., passwords) with external users (guest users). The guest users can login with their existing organizational/personal email credentials and get access to the enterprise content.
Office 365 External Sharing
While the AAD B2B Invite is one way of inviting guest users, probably the most popular (but the least obvious) way is to use Office 365 external sharing. In other words, when you share a document in One Drive/Sharepoint Online or other O365 apps to external users, behind the scenes the AAD B2B invitation/authentication process is triggered (there are a few differences in the workflow between direct B2B invite and O365 sharing though). So if you wondered how a large number of guest accounts ended up in your corp AAD tenant, now you know why :)
The hidden problem
Now let's switch the story to a development team which builds an enterprise web application and intends to restrict access only to it's enterprise users (by implementing AAD authentication). What is not obvious immediately to the developers is that the application can also be accessed by guest users in the directory. Let us understand this through a demo.
For fun, I have registered the domain Identt.dev (pronounce "Identt" as "Identity") and added it as a custom domain in AAD. So Identt.Dev is my demo enterprise and my users get @identt.dev alias.
Firstly, let us login to the enterprise application as an enterprise user and check.
Looks good! Next, let us invite a guest user, firstname.lastname@example.org, to the Identt.dev enterprise. You will see that the "User type" column for the external user shows as "Guest"
Now let us try logging in to the application as the guest user.
ta-da! And guests can access apps which are meant to be internal-only (employees only). Let's blame the devs - who would just add authentication and forget about authorization? Well, if you look at the docs and code samples, they mainly focus on adding authentication layer to apps. They leave authorization aspects to developers, and very few articles talk about the guest account problem. Developers do take care of business-related authorization checks if required by the application, but they do not expect guest accounts and hence the surprise.
Restricting access to guest accounts
Broadly, the guest accounts problem can be solved in two ways:
- Using AAD groups and user assignment (the config way)
- Using AAD App roles and groups claims (the code way)
Using AAD Groups and User Assignment
The idea here is to add users to security groups, and add the groups to the AAD app's service principal. The end result is that AAD throws an unauthorized exception if a user who is not in the service principal's group attempts to login. The biggest benefit of this approach is that there are no code changes required in the application.
When an app is registered in an AAD tenant, an Application Object is created. It has configurable properties such as redirect URIs, API permissions and scopes which are required to make a successful authentication. Every Application Object has at least one Service Principal object (one per tenant) which enables functionalities such as configuring permissions for users in the tenant. Properties of a Service Principal can be modified in Enterprise Applications section of AAD pane. Check this article for a differences between Application Objects and Service Principals.
Before looking at some of the caveats of using AAD groups, let us quickly see how this would look like through screenshots from the demo tenant. For the sake of the demo, I have created a nested group called "Employees-only-assigned-group". This group has two users "Kris" and "Jarvis" and a child group "Interns", which has the user "Peter Parker". In the enterprise application, I have added the nested group and in the properties I have set "User assignment enabled" property to "Yes".
So essentially with the above changes, I am ensuring that the guest account "email@example.com" will not be able to login to the web application. Let us see what happens when the guest user logs in.
This is perfect! This is exactly what is required to solve the guest accounts problem. However, there is a caveat here. Let us login as the intern and see what happens.
This is unexpected! The intern is unable to login since permissions based on nested group membership is not supported. This is a common mistake which development teams make and they end up blocking access to genuine users.
In the previous screenshot for enterprise groups, I have not shown what type of group "Employees-only-assigned-group" is (well, the name has the group type). Broadly, AAD (premium) supports 2 membership types - Assigned (Static) groups and Dynamic groups.
We have seen the drawbacks of Assigned groups earlier. Dynamic groups have a flat hierarchy and their membership is controlled by attributed-based rules. E.g., I have created a dynamic rule which adds members only if "userType" attribute is "member". For a guest account, this attribute's value would be "guest" and hence the rule prevents guest users from being added to this dynamic group. The below screenshots depict this.
With this, the employees of the Identt.dev enterprise (including interns) can now login to enterprise applications, while guests are denied access. This is the perfect zero-code solution to the guest accounts problem.
The following points must be kept in mind before choosing AAD groups & user assignment:
- Group membership is supported only in Azure AD Premium P1 and P2
- As discussed earlier, permissions based on nested groups are not supported
- Only global administrators of a tenant can create a dynamic group.
So while dynamic groups work great, it is not always feasible to use them either due to licensing issues or inconvenience to developers in getting them created. This calls for the next option - App Roles and Group claims
Using AAD App roles and groups claims
AAD App Roles
The idea of AAD App roles is to declare roles specific to an AAD app (in AAD App's manifest), and assign users/groups to those roles. The owner of the AAD app, a developer in most cases, will always be in control of the roles. These roles show up as claims in a logged-in user's ID token.
The application consuming the ID Token can inspect the "roles" claim, retrieve it's value and make authorization decisions accordingly. This article and this GitHub sample explain in detail how this can be implemented in ASP.NET Core. The best part of App Roles is that it is not dependent on AAD pricing tiers and it can help refine fine-grained RBAC.
AAD Group Claims
The idea of group claims is similar to that of App Roles - Configure AAD apps to emit security groups of the logged-in user into the user's ID token. The relying party application can then make authorization decisions based on the groups. The following screenshots demonstrate how this looks like.
One of the downsides of relying on group claims is that AAD can emit a maximum of 200 group claims. In large organizations, users being a part of more than 200 groups is common. So if group claims have to be relied upon for authorization decisions, the application has to make explicit graph API calls in it's backend to take care of the above limitation. It should be noted that App Roles and Group claims can co-exist (groups can be assigned roles which are defined in App Roles).
I have linked various articles throughout this blog post. They point to official docs/code samples which provide detailed guidance. For a quick dose on implementing authorization in AAD-based applications, I would strongly encourage to check out this Microsoft Identity Platform community call.
Guest accounts pose significant security risks if enterprises do not enforce appropriate restrictions. There are several ways to mitigate this problem- using assigned security groups, dynamic security groups (preferred), App Roles (and/or) Group Claims. The traditional way of enforcing authorization checks through an external data store (database) works too.
Keep looking for uninvited guests who might visit your application!
Stay safe and stay secure!