One of the new features we introduced in AD FS in Windows Server 2012 R2 is Multi-Factor Authentication (MFA) for WS-Federation, SAML-P and OAuth protocols.
In this post, I want to talk about some of the ways in which you can configure AD FS to implement several MFA policies to accomplish different authentication requirements.
Before authoring the policies, you need to prepare your AD FS farm for MFA by registering and enabling at least one MFA authentication provider (such as Windows Azure Multi-Factor Authentication or the built-in X509 Client Cert authentication). Once you configure your farm, then you are ready for the next step and decide how to engage MFA in your applications.
Engaging MFA for a relying party is very similar to way you configure its authorization requirements: using the claims engine and the claims pipeline.
The way all the pieces above work together is depicted in the following diagram:
The sequence goes as follows:
Now, everything boils down to what claims can we use and what the language allows us to do. While the full list of available claims is quite large, I would like to focus on the claims that can give us the most interesting MFA pivots:
You can use this claim to find out if the user belongs to a specific group.
When the value of this claim is true, it means that the user who authenticated is the one who originally registered the device.
When the value of this claim is false, it means that the user who authenticated is NOT the one who originally registered the device, which could be OK in the case of shared devices.
If you only care about the device itself being registered, you might want to use the EXISTS operator against this claim
Since MFA is supported only for browser applications, you can use this claim to tell apart browser from non-browser requests, in case you have both kinds of protocols in the same relying party trust, which is the typical case when issuing tokens to a federation provider STS.
When the value is false, it means the request came through a web application proxy. When true, it means the request came directly to the STS.
Claims that represent different fields and extensions of the X509 client certificate when used as an authentication method.
One interesting use case here is to use the EKU claim (http://schemas.microsoft.com/2012/12/certificatecontext/extension/eku) to ascertain whether the user used a smart card (the exact EKU depends upon the PKI infrastructure of the customer).
Used to indicate all authentication methods used to authenticate the user.
When the MFA rules are executed, this claim will contain the authentication method performed in the first stage.
When the Authorization or Issuance Transform rule set is executed, this claim will contain both the first and second factor authentication methods, if the MFA occurred (step 5 above).
Knowing that, we can now leverage the whole power of the AD FS Claims Language and Pipeline to go over some MFA Policy examples.
Let's start with the three options we streamlined in the AD FS Management Snap-in: Users/Groups, Devices, and Locations. I pasted the MMC UI for reference:
Per the table above, we use the Group SID claim:
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid", Value == "<<Group SID>>"]
=> issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod",
Value = "http://schemas.microsoft.com/claims/multipleauthn");
Note that the issuance condition (right side of the => operator). That will always be the same whenever you want to tell AD FS to perform MFA.
We can do this in two different ways. One is to require MFA if the user who registered the device is NOT the one who is authenticated using the "isregistereduser" claim (this is the one that the MMC snap-in uses):
c:[Type == "http://schemas.microsoft.com/2012/01/devicecontext/claims/isregistereduser", Value == "false"]
Alternatively, if you don't need to discriminate between the user who registered the device from the one who is authenticated (for example, shared devices), then you can use the EXISTS operator and check for any of the claims associated with the workplace joined state. I personally use the registrationid claim:
NOT EXISTS([Type == "http://schemas.microsoft.com/2012/01/devicecontext/claims/isregistereduser"])
In this case, we use the "insidecorporatenetworkclaim" the same way we used a Boolean claim in the example above
c:[Type == "http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork", Value == "false"]
As I mentioned above, the three scenarios above are the ones exposed in the AD FS management UI.
Now, let's take a look at a few more that require using the claims language directly:
Each check box in the management UI corresponds to one rule, which means a logical OR operation when they are put together. For example, when you check both "Unregistered devices" and "Extranet", the rule set will generate two rules, that will look like this:
For scenarios that require a logical AND operation between the conditions above me, you should combine them in a single rule and use the … AND … operator. If you want to enable MFA only when coming from extranet with a device that is not joined to the workplace, then the rule might look like this:
[Type == "http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork", Value == "false"] &&
[Type == "http://schemas.microsoft.com/2012/01/devicecontext/claims/isregistereduser", Value == "false"]
Given that MFA is plugged into the authentication pipeline for browser applications, if the MFA claim rules generate the claim that will engage MFA over WS-Trust will cause the request to fail with the following message in the ADFS Admin event log channel, with event ID 325. As you can see, this looks pretty much like an access denied failure.
The Federation Service could not authorize token issuance for caller 'DOMAIN\User'
The caller is not authorized to request a token for the relying party 'urn:dumptoken'. See event 501 with the same Instance ID for caller identity.
This becomes a problem for federation provider scenarios, such as Azure Active Directory and Office 365. In this case, there is a single RP trust to represent both rich clients that use WS-Trust (such as Lync and Outlook), as well as browser based applications (like Sharepoint Online and OWA). In that case, we have to use the claim "x-ms-endpoint-absolute-path" which contains the URL through which the token request came in to AD FS in order to derive the protocol of the request as follows:
Using the regex operations of the claims language, the condition above looks like this:
[Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-endpoint-absolute-path",
Value =~ "(/adfs/ls)|(/adfs/oauth2)"]
You can use this condition with any of the rules we discussed above to craft a more complex policy, using the AND operator. For example, if you want to require MFA for office 365 web applications when coming from extranet, then the rule is:
c:[Type == "http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork", Value == "false"] &&
c1:[Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-endpoint-absolute-path",
This one is a bit different from the previous examples because the rule to accomplish this will be in the authorization rule set, instead of the MFA rule set. As I mentioned above, the claim http://schemas.microsoft.com/claims/authnmethodsreferences contains the list of authentications methods performed. It will hold the value http://schemas.microsoft.com/claims/multipleauthn if MFA was performed.
NOT EXISTS([Type == "http://schemas.microsoft.com/claims/authnmethodsreferences",
Value == "http://schemas.microsoft.com/claims/multipleauthn"])
=> issue(Type = "http://schemas.microsoft.com/authorization/claims/deny",
Value = "DenyUsersWithClaim");
As you can see, with the set of claims available and the richness of the claims language, AD FS offers a lot of flexibility when it comes to engage MFA in your browser applications. We only went through a couple of examples, but you can do a lot more!
Stay tuned for more details on MFA on AD FS!.
How would you author the policy for an application with the following constraints:
Hint: you might need the claim language's add issuance statement