Additional Claims in JWT Tokens via Claims Mapping Policy

Recently I was asked how to add additional claims for a user in the JWT token that Azure AD generates. The case was that the JWT Token should include the sAMAccountName from Active Directory. After spending too much time looking at the documentation for Optional Claims in Azure AD and trying to get that to work, I switched to the Claims Mapping Policy which is in preview. The reason for the switch was basically that Optional Claims is for adding extra attributes that you define on a per Azure AD Application level, not for including standard attributes that is synchronized via Azure AD Connect.

Claims Mapping Policy

A Claims Mapping Policy is an object that you create and apply on an Azure AD Application registration. The policy is a definition of extra claims you want to include in the JWT token that is generated when doing an OAuth authentication towards the App. In my test Azure AD tenant, I’ll illustrate this by adding the attribute JobTitle to the JWT token. Why JobTitle of all attributes? Well, I don’t have sAMAccountName available since my test tenant isn’t synced with an on-prem AD, so I had to pick something else.

The policy definition is a JSON document that looks like this. Source = user indicates that the attribute exists on the user object. Other sources, like application, company, transformation are explained in this article https://docs.microsoft.com/en-us/azure/active-directory/active-directory-claims-mapping

$claimsMappingPolicy = [ordered]@{
    "ClaimsMappingPolicy" = [ordered]@{
        "Version" = 1
        "IncludeBasicClaimSet" = $true
        "ClaimsSchema" = @(
            [ordered]@{
                "Source" = "user"
                "ID" = "JobTitle"
                "JwtClaimType" = "JobTitle"
            }
        )
    }
}

If you where to include an extension attribute, synchronized via Azure AD Connect, it would look like this. The source is still user, but we use the ExtensionID instead and the value of the extension id is usually in the format extension_{guid}_<name>. The best way to get the value is via Graph Explorer https://developer.microsoft.com/en-us/graph/graph-explorer and run the query https://graph.microsoft.com/beta/users/<alias@domain.com>

[ordered]@{
    "Source" = "user"
    "ExtensionID" = "extension_18e31482d3fb4a8ea958aa96b662f508_PersonnelNumber"
    "JwtClaimType" = "employeeId"
}

Applying the Claims Mapping Policy

Applying the policy requires powershell and the Azure AD cmdlets. As mentioned in the documentation (see link above), you need to install the preview release to make this to work https://www.powershellgallery.com/packages/AzureADPreview/2.0.0.127. Once you’ve installed that, you logon and import the preview module.

Connect-AzureAD
Import-Module "AzureADPreview"

Creating the policy is just a few lines of powershell code. The code checks to see if you already have a policy and removes them first. Then the code creates the policy and applies it to the Application

$appID = "...guid-of-the-AppID..." 
$policyName = "Add JobTitle to claims"

$sp = Get-AzureADServicePrincipal -Filter "servicePrincipalNames/any(n: n eq '$appID')"
 
$existingPolicies = Get-AzureADServicePrincipalPolicy -Id $sp.ObjectId `
                    | Where-Object { $_.Type -eq "ClaimsMappingPolicy" }
if ($existingPolicies) {
    $existingPolicies | Remove-AzureADPolicy
}
 
$policyDefinition = $claimsMappingPolicy | ConvertTo-Json -Depth 99 -Compress
$policy = New-AzureADPolicy -Type "ClaimsMappingPolicy" -DisplayName $policyName -Definition $policyDefinition
 
Add-AzureADServicePrincipalPolicy -Id $sp.ObjectId -RefObjectId $policy.Id
Write-Output ("New claims mapping policy '{0}' set for app '{1}'." -f $policy.DisplayName, $sp.DisplayName)

Update the Application Manifest

Even though you have created the policy and applied it to the Application, the Manifest must be updated to tell that you are allowing mapped claims to be part of the token. The attribute “allowMappedClaims” attribute must be set to true.

The documentation also mentions that you need to upload a certificate as a signing key, but that is incorrect. That was required in the past and the preview have done away with that requirement.

You may get the error message “AADSTS50146: This application is required to be configured with an application-specific signing key.” but it has nothing to do with the certificate and it is not needed anymore. What the error message is telling you is that there is something wrong with the policy.

Testing that it works

I used this sample application from Office365 samples on Github https://github.com/dream-365/OfficeDev-Samples/tree/master/samples/Office365DevQuickStart/OAuth2-basic

In the OAuthBasic.cs I changed the code to this to make it logging on to Azure AD and my Native Application

var oauth = new OauthConfiguration
{
  Authority = "https://login.microsoftonline.com",
  Tenant = "common",
  ClientId = "f2cf4fd7-.....4fca5aea",
  RedirectURI = "http://localhost"
};

I also made some minor modifications to the source file AuthenticationCodeGrantFlow.cs in order to request access to the application as the resource and to have some error handling. The reason I call the AquireTokenWithResource with the AppID is so that I get the added claim in the access_token and not just the id_token.

var tokenResponse = auth.AcquireTokenWithResource(resource: "f2cf4fd7-....4fca5aea");

var err = tokenResponse.GetValue("error");
if ( err != null )
{
    err = tokenResponse.GetValue("error").Value<string>();
    var errdesc = tokenResponse.GetValue("error_description").Value<string>();
    Console.WriteLine("Error: {0} - {1}", err, errdesc);
}
else
{
    Console.WriteLine("access token:");

    var accessToken = tokenResponse.GetValue("access_token").Value<string>();
    var validator = new JsonWebTokenValidator();
    var jwt = validator.Validate(accessToken);
    Console.WriteLine(JsonConvert.SerializeObject(jwt.Payload, Formatting.Indented));
}

Console.Write("Find the Any Key on your keyboard and hit it with all you got to continue...");
Console.ReadLine();

Running the Windows Console application and logging on to my Azure AD account gives this output. As you can see the Claims Mapping Policy have kicked in and the attribute JobTitle is part of the claim.