AD FS and MFA – configuring multiple additional authentication rules

Ever since Microsoft bought PhoneFactor 3 years ago, they have been heavily investing in incorporating it into different products, both on-prem and in the cloud. And they have no intention on slowing down – AD FS vNext will have ‘native’ integration with Azure MFA, eliminating the need to deploy the on-prem MFA server for organizations that sync to Azure AD. Apart from that, they are also making additional improvements on how the auth process works, with the switch to Modern auth and the introduction of the Access control policies for AD FS. Even with AD FS 3.0, it’s just a matter of few simple clicks to set up AD FS to require MFA only when accessing resources from outside of the corporate network, and things get even better in vNext. So there is no more need to play with those pesky claims rules?

For those that will stick to older version of AD FS however, and for people that want even more customizability, the claims rules are here to stay. In this post, I will briefly discuss how we can configure multiple additional authentication rules, so we can have a different behavior for MFA depending on the device or client used, the location of the user or any other information presented as a claim. The AdditionalAuthenticationRules were introduced with AD FS 3.0, and they use the same familiar claims rules syntax. They can be applied both Globally, or per specific Replying Party trust, and as the name suggests they will be executed after the initial Authentication takes place. The process is described in this excellent post by Ramiro Calderon, and here we will cover one more detail.

Basically, I want to just point out that the additional authentication rules are very similar to ‘regular’ claims rules and we can use every claim about the user/device with them. Those can include things such as UPN or group membership, but also information on the client (user agent string), the IP the request come from, the protocol used, etc. And, we can create and enforce multiple rules, but to do so we will have to rely on PowerShell in most scenarios that make sense! As detailed in the blog post above, if we use the GUI to configure the additional authentication rules, a new rule is created for each of the selected conditions, effectively resulting in OR-er configuration. For example this is what we will get if we enable off of them:

PS C:\> (Get-AdfsRelyingPartyTrust "O365").AdditionalAuthenticationRules
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid", Value == "S-1-5-21-1315440946-3826617302-920981253-512"]
=> issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod", Value = "http://schemas.microsoft.com/claims/multipleauthn");

c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid", Value == "S-1-5-21-1315440946-3826617302-920981253-500"]
=> issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod", Value = "http://schemas.microsoft.com/claims/multipleauthn");

c:[Type == "http://schemas.microsoft.com/2012/01/devicecontext/claims/isregistereduser", Value == "false"]
=> issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod", Value = "http://schemas.microsoft.com/claims/multipleauthn");

exists([Type == "http://schemas.microsoft.com/2012/01/devicecontext/claims/registrationid"])
=> issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod", Value = "http://schemas.microsoft.com/claims/multipleauthn");

c:[Type == "http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork", Value == "false"]
=> issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod", Value = "http://schemas.microsoft.com/claims/multipleauthn");

c:[Type == "http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork", Value == "true"]
=> issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod", Value = "http://schemas.microsoft.com/claims/multipleauthn");

In some cases however using this OR configuration is not enough – we might want to combine several criteria within a single rule and even apply multiple such rules. As you might have noticed from the output above, PowerShell stores the rules as a string and each of the above represent a separate rule. If we want to extract them one by one, we can use the split method:

PS C:\> $rules = (Get-AdfsRelyingPartyTrust "O365").AdditionalAuthenticationRules -split "`r`n`r`n"
PS C:\> $rules[0]
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid", Value == "S-1-5-21-1315440946-3826617302-920981253-512"]
=> issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod", Value = "http://schemas.microsoft.com/claims/multipleauthn");

PowerShell is definitely not the best tool to do text editing, so any changes to the actual rules are best done in proper editor. Once we have decided which changes to make however, we will have to rely on PowerShell to set or update the rules. Say for example we want to test the following scenario – force MFA for Chrome and Opera for requests coming outside of the corporate environment. The additional authentication rule that we want to configure in this case will look something like:

'c:[Type == "http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork", Value == "false"]
&& c1:[Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-user-agent", Value =~ "(Opera)|(Chrome)"]
&& c2:[Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-endpoint-absolute-path", Value =~ "(/adfs/ls)|(/adfs/oauth2)"]
=> issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod", Value = "http://schemas.microsoft.com/claims/multipleauthn");'

where we have also added the x-ms-endpoint-absolute-path claim to make sure we only enforce this on the Passive endpoint (doesn’t make much difference for browser traffic, but still). Again, as there is no way to configure a rule with multiple conditions via the GUI, we have to set it up via PowerShell. Assuming we start without any other additional authentication rules, we have:

PS C:\> Set-AdfsRelyingPartyTrust -TargetName "O365" -AdditionalAuthenticationRules 'c:[Type == "http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork", Value == "false"] && c1:[Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-user-agent", Value =~ "(Opera)|(Chrome)"] && c2:[Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-endpoint-absolute-path", Value =~ "(/adfs/ls)|(/adfs/oauth2)"] => issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod", Value = "http://schemas.microsoft.com/claims/multipleauthn");'

PS C:\> (Get-AdfsRelyingPartyTrust "O365").AdditionalAuthenticationRules
c:[Type == "http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork", Value == "false"]
&& c1:[Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-user-agent", Value =~ "(Opera)|(Chrome)"]
&& c2:[Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-endpoint-absolute-path", Value =~ "(/adfs/ls)|(/adfs/oauth2)"]
=> issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod", Value = "http://schemas.microsoft.com/claims/multipleauthn");

Next, we decide that we want our new helpdesk member Gosho to always be a subject of MFA, as should be all the members of a particular AD group, regardless of location, workload or client used. An example claims rule for this scenario will look like:

'c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn", Value =~ "(?i)gosho@sts.michev.info"]
&& c1:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid", Value == "S-1-5-21-1315440946-3826617302-920981253-512"]
=> issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod", Value = "http://schemas.microsoft.com/claims/multipleauthn");'

where we have provided the UserPrincipalName of Gosho and the SID on the group. As we already have one rule configured, we cannot directly use the -AdditionalAuthenticationRules parameter in this case. Instead, we will store the old rule(s) in a string variable and append to it:

PS C:\> $old = (Get-AdfsRelyingPartyTrust "O365").AdditionalAuthenticationRules
PS C:\> $old
c:[Type == "http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork", Value == "false"]
&& c1:[Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-user-agent", Value =~ "(Opera)|(Chrome)"]
&& c2:[Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-endpoint-absolute-path", Value =~ "(/adfs/ls)|(/adfs/oauth2)"]
=> issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod", Value = "http://schemas.microsoft.com/claims/multipleauthn");

PS C:\> $new = $old + 'c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn", Value =~ "(?i)gosho@sts.michev.info"] && c1:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid", Value == "S-1-5-21-1315440946-3826617302-920981253-512"] => issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod", Value = "http://schemas.microsoft.com/claims/multipleauthn");'

PS C:\> $new
c:[Type == "http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork", Value == "false"]
&& c1:[Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-user-agent", Value =~ "(Opera)|(Chrome)"]
&& c2:[Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-endpoint-absolute-path", Value =~ "(/adfs/ls)|(/adfs/oauth2)"]
=> issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod", Value = "http://schemas.microsoft.com/claims/multipleauthn");

c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn", Value =~ "(?i)gosho@sts.michev.info"] && c1:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid", Value == "S-1-5-21-1315440946-3826617302-920981253-512"] => issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod", Value = "http://schemas.microsoft.com/claims/multipleauthn");

Next, we use the $new variable to properly configure the set of claims rules:

PS C:\> $newset = New-AdfsClaimRuleSet -ClaimRule $new
PS C:\> $newset

ClaimRules ClaimRulesString
---------- ----------------
{c:[Type == "http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork", Value == "false"]... c:[Type == "http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork", Value == "false"]...

and then we set them via:

PS C:\> Set-AdfsRelyingPartyTrust -TargetName "O365" -AdditionalAuthenticationRules $newset.ClaimRulesString

Here’s how the end result will look like:

PS C:\> (Get-AdfsRelyingPartyTrust "O365").AdditionalAuthenticationRules
c:[Type == "http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork", Value == "false"]
&& c1:[Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-user-agent", Value =~ "(Opera)|(Chrome)"]
&& c2:[Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-endpoint-absolute-path", Value =~ "(/adfs/ls)|(/adfs/oauth2)"]
=> issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod", Value = "http://schemas.microsoft.com/claims/multipleauthn");

c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn", Value =~ "(?i)gosho@sts.michev.info"]
&& c1:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid", Value == "S-1-5-21-1315440946-3826617302-920981253-512"]
=> issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod", Value = "http://schemas.microsoft.com/claims/multipleauthn");

So, to recap the process, here are the steps needed to configure multiple additional authentication rules for AD FS:

  1. Save the existing rules to a variable
    $old = (Get-AdfsRelyingPartyTrust “O365”).AdditionalAuthenticationRules
  2. Append any new rules to the variable
    $new = $old + ‘new claims rule goes here’
  3. Prepare the new set of rules
    $newset = New-AdfsClaimRuleSet -ClaimRule $new
  4. And finally, set the new rules
    Set-AdfsRelyingPartyTrust -TargetName “O365” -AdditionalAuthenticationRules $newset.ClaimRulesString

Oh, and just to be on the safe side, before making any changes, do a backup! To backup all rules for a specific trust, use:

PS C:\> (Get-AdfsRelyingPartyTrust "O365").AdditionalAuthenticationRules | out-file "C:\Users\vasil\Desktop\AAR.txt"

and to restore from a backup, use:

PS C:\> Set-AdfsRelyingPartyTrust -TargetName "Device Registration Service" -AdditionalAuthenticationRulesFile "C:\Users\vasil\Desktop\AAR.txt"

8 thoughts on “AD FS and MFA – configuring multiple additional authentication rules

  1. Itai says:

    Hi,
    We’ve set 3rd party MFA which works with AD FS 2012, however, we do have a problem with AD FS 2016.
    The following claim works with 2012:
    Set-AdfsAdditionalAuthenticationRule -AdditionalAuthenticationRules ‘c:[type == “http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork”, value == “false”] => issue(type = “http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod”, value = “http://schemas.microsoft.com/claims/multipleauthn” );’

    For 2016, we can do Value==True, that works, but it prevents WS-trust and therefore device registration in Azure on federated environment.

    What should replace this claim on AD FS 2016?
    We need MFA for internal/external network and device registration (so we can configure WHFB)

    Reply
  2. MC says:

    Here is the command

    Set-AdfsRelyingPartyTrust -targetname “Microsoft Office 365 Identity Platform” -AdditionalAuthenticationRules ‘NOT exists([Type == “http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-application”, Value == “Microsoft.Exchange.ActiveSync”]) && NOT exists([Type == “http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-application”, Value == “Microsoft.Exchange.AutoDiscover”]) && c:[Type == “http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork”, Value == “false”] =>issue(Type = “http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod”, Value = “http://schemas.microsoft.com/claims/multipleauthn”);’

    Reply
    1. Vasil Michev says:

      This one should do it:

      Set-AdfsRelyingPartyTrust -targetname “Microsoft Office 365 Identity Platform” -AdditionalAuthenticationRules ‘NOT exists([Type == “http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-application”, Value == “Microsoft.Exchange.ActiveSync”]) && NOT exists([Type == “http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-application”, Value == “Microsoft.Exchange.AutoDiscover”]) && exist([Type == “http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork”, Value == “false”]) => issue(Type = “http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod”, Value = “http://schemas.microsoft.com/claims/multipleauthn”);’

      or in shorter form:

      exists([Type == ” http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork“, Value == “false”])
      && NOT exists([Type == “http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-application”, Value =~ “Microsoft.Exchange.ActiveSync|Microsoft.Exchange.Autodiscover”]) => issue(Type = “http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod”, Value = “http://schemas.microsoft.com/claims/multipleauthn”);

      Reply
  3. MC says:

    I believe this is the command you would use to for MFA on external connections only and exclude ActiveSync and Autodiscover. Is this Correct?

    Reply
  4. REN says:

    Hello Vasil,
    Thank you for sharing this. I have a little bit of an issue. By the way we are using 3rd party 2FA.
    I have activated the 2FA and applied it to a particular group by editing global authentication rules (by going to: Authentication Policies->Edit Global Multi-Factor Authentication->MultiFactor Tab->Add group). It works very fine but Office applications, Skype, and Email app on iphone are prompting for 2FA. We do have the script to exclude ActiveSync and Autodiscover but when I used the command, it replaces the old configuration which applies to the group.

    Basically, when i added that group and run the command “Get-AdfsAdditionalAuthenticationRule”, I get this,

    c:[Type == “http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid”, Value == “S-1-5-21-8915387”] => issue(Type = “http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod”, Value = “http://schemas.microsoft.com/claims/multipleauthn”);

    I run the command to append to the first command but it just replaces the one above.

    Set-AdfsAdditionalAuthenticationRule -AdditionalAuthenticationRules ‘NOT exists([Type == “http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-application”, Value == “Microsoft.Exchange.ActiveSync”]) && NOT exists([Type == “http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-application”, Value == “Microsoft.Exchange.AutoDiscover”]) =>issue(Type = “http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod”, Value = “http://schemas.microsoft.com/claims/multipleauthn”);’

    I am hoping i could combine the commands that’s why it brought me to your page. Please help.

    Reply
    1. Vasil Michev says:

      You need to combine them all into:

      Set-AdfsAdditionalAuthenticationRule -AdditionalAuthenticationRules ‘NOT exists([Type == “http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-application”, Value == “Microsoft.Exchange.ActiveSync”]) && NOT exists([Type == “http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-application”, Value == “Microsoft.Exchange.AutoDiscover”]) && exists([Type == “http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid”, Value == “S-1-5-21-8915387”]) =>issue(Type = “http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod”, Value = “http://schemas.microsoft.com/claims/multipleauthn”);’

      You probably need to replace all the quotes if copy pasting from the web though 🙂

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.