Claim rules for the Azure Active Directory (#AzureAD) Relying Party (RP) trust

When you establish a federation with Azure Active Directory (AAD) for the purpose of single sign-on (SSO) the majority of people will utilise the Azure AD PowerShell cmdlets to create or convert one or more verified domains into federated domains.  The PowerShell process has the added benefit of automatically configuring Active Directory Federation Services (AD FS) where you are using AD FS as the on-premises identity provider (IdP).  When this process happens either two or three claim rules are created, depending on whether or not you need to support child domains and therefore utilise the SupportMultipleDomain parameter.

I have documented these rules here and won’t dupliate that description here again.  As and when you light up other features within your tenant you inevitably end up introducing additional claim rules to the urn:federation:MicrosoftOnline relying party trust.  For example, those of you utilising on-premises multi-factor authentication will need to issue the http://schemas.microsoft.com/claims/authnmethodsreferences and http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork claim types.  Those of you implementing Windows 10 Domain Join++ with Azure Device Registration Service (DRS) will need three new claims to support computer authentication; others will want the PSSO claims sent over to AAD and SharePoint Online; and so forth.

The purpose of this post is to try and bring all of these claim rules together in one place.

Issuance Transform Rules for Azure AD Relying Party Trust

Here are the rules.

@RuleName = "User Principal Name"
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"]
 => issue(store = "Active Directory", types = ("http://schemas.xmlsoap.org/claims/UPN", "http://schemas.microsoft.com/LiveID/Federation/2008/05/ImmutableID"), query = "samAccountName={0};userPrincipalName,objectGUID;{1}", param = regexreplace(c.Value, "(?<domain>[^\\]+)\\(?<user>.+)", "${user}"), param = c.Value);

@RuleName = "Immutable ID"
iid:[Type == "http://schemas.microsoft.com/LiveID/Federation/2008/05/ImmutableID"]
 => issue(Type = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", Value = iid.Value, Properties["http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/format"] = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified");

@RuleName = "Issuer ID"
upn:[Type == "http://schemas.xmlsoap.org/claims/UPN"]
 => issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/issuerid", Value = regexreplace(upn.Value, ".+@(?<domain>.+)", "http://${domain}/adfs/services/trust/"));

@RuleName = "Inside Corporate Network claim"
corpnet:[Type == "http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork"]
 => issue(claim = corpnet);

@RuleName = "Keep Me Signed In (KMSI) claim"
kmsi:[Type == "http://schemas.microsoft.com/2014/03/psso"]
 => issue(claim = kmsi);

@RuleName = "Issue object GUID"
c1:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid", Value =~ "515$", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"]
 && c2:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"]
 => issue(store = "Active Directory", types = ("http://schemas.microsoft.com/identity/claims/onpremobjectguid"), query = ";objectguid;{0}", param = c2.Value);

@RuleName = "Issue account type for domain joined computers"
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid", Value =~ "515$", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"]
 => issue(Type = "http://schemas.microsoft.com/ws/2012/01/accounttype", Value = "DJ");

@RuleName = "Pass through primary SID"
c1:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid", Value =~ "515$", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"]
 && c2:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"]
 => issue(claim = c2);

A quick explanation of each rule

Here’s what each rule does:

  1. Lookup UPN and immutable ID and issue
  2. Issue the immutable ID as the name ID claim type too
  3. Adjust the issuer ID – this is only required if you have multiple contiguous DNS namespaces, i.e. you have multiple UPNs such as corp.contoso.com, uk.corp.contoso.com and emea.fabrikam.com
  4. Issue the inside corpnet claim – did you AuthN with the FS, i.e. on-corpnet, or the FS-P, i.e. from the Internet
  5. Issue the authentication methods claims – whether or not you underwent MFA; only really useful if you utilise the SupportsMfa property to offload MFA to your on-premises IdP
  6. Issue the PSSO/KMSI claim – this is present if you are authenticating from a registered device or you have enabled Keep Me Signed In and this has been selected
  7. Issue the password expiry claim – EXO/Outlook notify users when the password is nearing expiration.  This claim uses a built-in store to lookup whether or not the password is going to expire.  The claim is only issued if the password expiration is imminent, i.e. within 14 days (this value increased in AD FS 2016 as far as I can tell from basic testing)
  8. The remaining rules are required for Windows 10 Domain-Join++ – basically, Windows 10 AD DS domain-joined devices register in Azure AD if you have Azure DRS deployed.  The devices authenticate using the computer credentials in AD DS.  This feature lights up improved user-experience.  Full details can be found in Jairo’s excellent post.

This means you’ll issue the following claims:

Handling null immutable IDs

Something else that always crops up is how to handle two different immutable IDs.  Typically, when an organsisation has the means to implement on-premises-owned immutable IDs they only do so for a subset of objects.  One example being real user objects for employees and contractors are handled but administrators and shared mailboxes are not.  As already mentioned earlier, I discuss handling multiple immutable IDs in AD FS in this post, however for completeness I’ll re-discuss the pattern here, as there is often a more simple approach to that discussed previously, namely that the rules don’t care about what type of the object is authenticating but rather simply wish to issue one attribute (primary) or a second attribute if the first is not present, e.g. issue mS-DS-ConsistencyGuid if there’s a value and objectGUID if there is not.

The following ruleset is the first ruleset from above with the additional logic/rules to handle looking first for mS-DS-ConsistencyGuid and falling back to objectGuid if mS-DS-ConsistencyGuid is not populated. (For brevity I have ommitted the last three Win10 domain join++ rules as these remain unchanged.)

@RuleName = "Get LDAP attributes from AD DS"
sam:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"]
 => add(store = "Active Directory", types = ("http://schemas.xmlsoap.org/claims/UPN", "urn:abstractsynapse.com:claims/altguid", "urn:abstractsynapse.com:claims/adguid"), query = "samAccountName={0};userPrincipalName,mS-DS-ConsistencyGuid,objectGUID;{1}", param = regexreplace(sam.Value, "(?<domain>[^\\]+)\\(?<user>.+)", "${user}"), param = sam.Value);

@RuleName = "Issue UPN"
upn:[Type == "http://schemas.xmlsoap.org/claims/UPN"]
 => issue(claim = upn);

@RuleName = "Issue alt. GUID as Immutable ID (if present)"
iid:[Type == "urn:abstractsynapse.com:claims/altguid"]
 => issue(Type = "http://schemas.microsoft.com/LiveID/Federation/2008/05/ImmutableID", Value = iid.Value);

@RuleName = "Check if altguid exists"
NOT EXISTS([Type == "urn:abstractsynapse.com:claims/altguid"])
 => add(Type = "urn:abstractsynapse.com:claims/altguidnotpresent", Value = "true");

@RuleName = "Issue objectGUID as Immutable ID if altguid not present"
[Type == "urn:abstractsynapse.com:claims/altguidnotpresent"]
 && guid:[Type == "urn:abstractsynapse.com:claims/adguid"]
 => issue(Type = "http://schemas.microsoft.com/LiveID/Federation/2008/05/ImmutableID", Value = guid.Value);

@RuleName = "Name Identifier"
iid:[Type == "http://schemas.microsoft.com/LiveID/Federation/2008/05/ImmutableID"]
 => issue(Type = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", Value = iid.Value, Properties["http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/format"] = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified");

@RuleName = "Inside Corporate Network claim"
corpnet:[Type == "http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork"]
 => issue(claim = corpnet);

@RuleName = "Keep Me Signed In (KMSI) claim"
kmsi:[Type == "http://schemas.microsoft.com/2014/03/psso"]
 => issue(claim = kmsi);

@RuleName = "Password expiration claim"
pwd:[Type == "http://schemas.microsoft.com/ws/2012/01/passwordexpirationtime"]
 => issue(store = "_PasswordExpiryStore", types = ("http://schemas.microsoft.com/ws/2012/01/passwordexpirationtime", "http://schemas.microsoft.com/ws/2012/01/passwordexpirationdays", "http://schemas.microsoft.com/ws/2012/01/passwordchangeurl"), query = "{0};", param = pwd.Value);

Explanation

First we lookup UPN and immutable ID values – mS-DS-ConsistencyGuid and objectGuid.  Next we issue UPN.  The third rule issues mS-DS-ConsistencyGuid if it’s present.  If it’s not present, i.e. the value was null, then the rule doesn’t fire.  The fourth rule checks to see if the mS-DS-ConsistencyGuid claim is present in the pipeline.  Here I’m using my own custom URIs to make it clearer – you can use whatever you want.  If the value is not present I add a new claim type to the pipeline that expresses the missing value.  The next rule, the fifth, issues the objectGuid if the mS-DS-ConsistencyGuid is not present.  The remaining rules are the same as that above.

What about alternate login ID deployments?

So, what if I use alternate logon ID?  In this case you probably don’t want to issue the UPN claim as the UPN or Name ID claim.  You probably want to issue the http://schemas.microsoft.com/ws/2013/11/alternateloginid claim type instead.  But does everyone authenticate with alternate logon ID?  Possibly not!  So you might want to look at the mS-DS-ImmutableID logic above and apply the same logic to UPN.  Or maybe you just want to issue mail.  That would handle employees, contractors and shared mailboxes but maybe not administrators.  It’s going to vary from customer to customer based on the upstream identity provisioning processes.

More information on alternate login ID can be found here:

PowerShell to set the rules

<WARNING>This simple PowerShell example will overwrite what’s there!  Think before you execute!</WARNING>


# assuming you're running on the primary AD FS server (WID configuration; any FS if using SQL backend)
# as a member of local administrators group with an elevated shell

# Backup the current rules
Get-AdfsRelyingPartyTrust -Identifier 'urn:federation:MicrosoftOnline' | 
    select -ExpandProperty issuanceTransformRules | Out-File .\aadTrustRules.bak.txt

# Validate the ruleset syntax
$azureADClaims = New-AdfsClaimRuleSet -ClaimRule @'

@RuleName = "User Principal Name"
upn:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"]
 => issue(store = "Active Directory", types = (
    "http://schemas.xmlsoap.org/claims/UPN", 
    "http://schemas.microsoft.com/LiveID/Federation/2008/05/ImmutableID"), 
    query = "samAccountName={0};userPrincipalName,objectGUID;{1}", 
    param = regexreplace(upn.Value, "(?<domain>[^\\]+)\\(?<user>.+)", "${user}"), param = upn.Value);

@RuleName = "Immutable ID"
iid:[Type == "http://schemas.microsoft.com/LiveID/Federation/2008/05/ImmutableID"]
 => issue(Type = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", 
    Value = iid.Value, 
    Properties["http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/format"] = 
        "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified");

@RuleName = "Issuer ID"
upn:[Type == "http://schemas.xmlsoap.org/claims/UPN"]
 => issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/issuerid", 
    Value = regexreplace(upn.Value, ".+@(?<domain>.+)", "http://${domain}/adfs/services/trust/"));

@RuleName = "Inside Corporate Network claim"
corpnet:[Type == "http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork"]
 => issue(claim = corpnet);

@RuleName = "Authentication methods claim"
authn:[Type == "http://schemas.microsoft.com/claims/authnmethodsreferences"]
 => issue(claim = authn);

@RuleName = "Keep Me Signed In (KMSI) claim"
kmsi:[Type == "http://schemas.microsoft.com/2014/03/psso"]
 => issue(claim = kmsi);

@RuleName = "Password expiration claims"
pwd:[Type == "http://schemas.microsoft.com/ws/2012/01/passwordexpirationtime"]
=> issue(store = "_PasswordExpiryStore", types = (
    "http://schemas.microsoft.com/ws/2012/01/passwordexpirationtime", 
    "http://schemas.microsoft.com/ws/2012/01/passwordexpirationdays", 
    "http://schemas.microsoft.com/ws/2012/01/passwordchangeurl"), query = "{0};", param = pwd.Value);

@RuleName = "Issue object GUID"
gsid:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid", Value =~ "515$", 
    Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"]
 && sam:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname", 
    Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"]
 => issue(store = "Active Directory", types = (
    "http://schemas.microsoft.com/identity/claims/onpremobjectguid"), 
    query = ";objectguid;{0}", param = sam.Value);

@RuleName = "Issue account type for domain joined computers"
gsid:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid", Value =~ "515$", 
    Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"]
 => issue(Type = "http://schemas.microsoft.com/ws/2012/01/accounttype", Value = "DJ");

@RuleName = "Pass through primary SID"
gsid:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid", Value =~ "515$", 
    Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"]
 && psid:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid", 
    Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"]
 => issue(claim = psid);

'@

# Update the trust
Set-AdfsRelyingPartyTrust `
    -TargetIdentifier 'urn:federation:MicrosoftOnline' `
    -IssuanceAuthorizationRules $azureADClaims.ClaimRulesString


Download as a text/script file here.

Note

WordPress cannot seem to handle the correct formatting of the claim rules and I’ve wasted a lot of time trying to make this work.  I’ve given up.  Here are the rules as TXT files:

The PowerShell examples:

Advertisements

About Paul Williams

IT consultant working for Microsoft specialising in Identity Management and Directory Services.
This entry was posted in AD FS, Azure Active Directory and tagged , , , , , , , . Bookmark the permalink.

4 Responses to Claim rules for the Azure Active Directory (#AzureAD) Relying Party (RP) trust

  1. Lucian Franghiu says:

    Great post mate

  2. alex says:

    Suspected mismatch: you start the script with backing up issuanceTransformRules but update the trust wit hIssuanceAuthorizationRules.

    # Backup the current rules
    Get-AdfsRelyingPartyTrust -Identifier ‘urn:federation:MicrosoftOnline’ |
    select -ExpandProperty issuanceTransformRules | Out-File .\aadTrustRules.bak.txt

    # Update the trust
    Set-AdfsRelyingPartyTrust `
    -TargetIdentifier ‘urn:federation:MicrosoftOnline’ `
    -IssuanceAuthorizationRules $azureADClaims.ClaimRulesString

    • Thanks for pointing that out Alex! I’ll make the changes shortly (I usually have to have a couple of attempts at getting the formatting working) and will address the issue you are calling out. As you can see, I copied and pasted commands from different scripts to make the post!

      • Ronald Roos says:

        The Mismatch is not changed yet in the “UpdateAADTrustWithFullRuleset.ps1” script, Change commands from line 74 to this:

        # Update the trust
        Set-AdfsRelyingPartyTrust `
        -TargetIdentifier ‘urn:federation:MicrosoftOnline’ `
        -IssuanceTransformRules $azureADClaims.ClaimRulesString

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s