Monday, August 28, 2017

AzureAD: attributes, groups and roles in SAML applications

Recently while migrating a bunch of things to AzureAD, I entered attribute hell, where the attributes required by the relying party (application) don't match the attributes sent from the IDP (AzureAD in this case), and the supposed convention of just swapping XML files and everything just working doesn't really work, and the terminology is different between different IDPs, and even IDPs from the same vendor (ADFS and AzureAD). But the biggest discovery was how groups and roles work in AzureAD SAML apps. I'll detail some of the backstory here.

So in the old days, you had an application that uses AD to authenticate, and also determine access levels using AD groups. In some cases these groups might already exist (students and staff), and can be used directly. Sometimes they exist, but the application needs to have a specific name, so you add the group (staff) to another group (appl-staff) and hope the application supports nested groups. Or sometimes they don't exist so you need to create them as required. Maintenance of these groups can suck. An an application tied into the AD has access to all users and all groups in the AD (perhaps this can be restricted using OU permissions, but that again creates maintenance hassles, and I suspect is not so often actually done).

When first trying to get a particular application rolling as an AzureAD SAML app, I tried to expose a user's group memberships as a SAML attribute. Eventually I managed to do this (it wasn't easy, and required powershell, or possibly downloading and upload a manifest JSON file). But this only provides the group GUIDs, not their names, which turns out to be useless for most cases. So this is where application roles come in. When creating a SAML app registration in AzureAD, you can create roles specific to that application (that are not visible anywhere else), then assign users and groups to those roles. So for a particular application (let's say a library system), you might have the roles SuperAdmin, Librarian, Staff and Student. Then you assign users or groups from AzureAD to those roles. When a SAML claim is processed, those roles will come through in the user.assignedroles, which you can map to whatever attribute the application requires (e.g. groupMembership).

This is all really quite sensible and logical.

Except for one thing.

Creating application roles is something of a pain. This seemingly simple thing can't be done through the Azure portal (at least at the time of writing; maybe they'll get to it one day). There are two ways to do this; one is via an application manifest, which the application provider has to provide. However, this assumes that the application provider knows what they're doing, which, in our experiences, hasn't been the case. A description of setting this up using an application manifest is available here and here, but I don't have any experience doing it this way as none of the vendors provided this). The way we did it was with the venerable PowerShell. A code snippet, largely borrowed from this repository follows:

Import-Module AzureAD
connect-azuread

Function CreateAppRole([string] $Name, [string] $Description)
{
    $appRole = New-Object Microsoft.Open.AzureAD.Model.AppRole
    $appRole.AllowedMemberTypes = New-Object System.Collections.Generic.List[string]
    $appRole.AllowedMemberTypes.Add("User");  # this has to be User; we can still add groups to it
    $appRole.DisplayName = $Name
    $appRole.Id = New-Guid
    $appRole.IsEnabled = $true
    $appRole.Description = $Description
    $appRole.Value = $Name;
    return $appRole
}

$app_id = PUT_APPLICATION_OBJECT_ID_HERE  # get this from the portal or some other AzureAD powershell command
$sponsors = CreateAppRole -Name "Librarians" -Description  "Librarian Role"

$app = Get-AzureADApplication -ObjectId $app_id
$appRoles = $app.AppRoles
# $appRoles = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.AppRole] # might need this if your app doesn't have any roles yet
$appRoles.add($sponsors)

Set-AzureADApplication  -ObjectId $app_id -AppRoles $appRoles


With app roles created, you can assign users and groups to those roles. In the portal, this is very tedious; whoever wrote that UI should be shot; if you have a lot of them you'll probably want to delve into PowerShell or the Graph API to implement it faster. 

One gotcha is that nested group membership doesn't appear to work, so if you have a role called AppStaff, and assign to that a group called Staff which has as a member another group called Teaching-Staff, and your teachers are only direct members of Teaching-Staff, then those teachers will not get the AppStaff role in the SAML claim - they need to be a direct member of the role group. Perhaps one day MS will implement nested groups for roles, at least as an option.