When you implement an additional authentication provider in your Active Directory Federation Services (AD FS) identity provider (IdP) you soon start getting all manner of requests from application owners/managers within the business for multi-factor authentication (MFA) configuration. For the most part it’s nice and easy, each application has one or two requirements such as “MFA when accessing from outside of the corporate network” or “MFA for admins”, so you create a couple of additional authentication rules on the relying party trust for the app. This is nice and clean because there’s typically a one-to-one relationship between web application and federation service. For example, consider the following architecture diagram – two SaaS apps, each implemented within the idp.contoso.com federation service (FS) as a relying party trust, or RP:
This is, in effect, a type of conditional access. The AD FS claim rules allow you to implement a number of scenarios, for example:
- Require MFA when accessing SaaS app #1 from outside the corporate network
- Require MFA when accessing SaaS app #2 if you are not requesting a token from a registered device, i.e. a Workplace Joined (WPJ) or InTune-managed device
- Require MFA when accessing SaaS app#1 and/or SaaS app#2 if you are an administrator of the app (designated by group membership in AD DS)
I won’t go over conditional access here – I have another post on that subject coming soon – and Ramiro does an awesome job of describing additional authentication rules in this post. Suffice to say, there’s a lot of flexibility when it comes to controlling MFA in AD FS 2012 R2 and AD FS 2016 when the application in question trusts your FS directly.
Issues arise when the client needs to make a second hop for authentication however. That is, when the SaaS app trusts an IdP that is not your FS and that IdP trusts your FS, as is the case in the following scenarios:
- Azure Active Directory apps and federated users
- Application or location specific federation services that support your corporate users
- Partner organisations that trust your corporate users
The latter bullet is not really relevant here, but I am constantly coming across the first two points. Take the following architecture illustration as an example.
What have I drawn? Four web or SaaS apps and three different identity providers all of which actually support your corporate users.
- Firstly, external web app #1 and #2 trust a specific federation service that has two claims provider trusts: one for a custom STS or FS that handles external users, hosted in a Linux LDAP directory; the other being your corporate IdP that handles corporate user authentications.
- Next, SaaS app #1 is an Azure AD pre-integrated app, i.e. it trusts AAD for authentication, however your Azure AD tenant contains both managed (cloud) and federated identities, where the federated identities are your on-premises corporate users and the cloud identities are non-IT staff, e.g. factory workers, that need access to at least one of your SaaS apps.
- Lastly, SaaS app #2 is a typical federated application that trusts your AD FS IdP. Here you get SSO to your SaaS apps using your on-premises identities, i.e. your Active Directory Domain Services (AD DS) users.
Integrating MFA now just got a lot more complex (for a subset of your apps).
I want to gloss over Azure AD because I have another post planned for a more detailed discussion around Conditional Access Policy, or CAP. So for AAD we’ll just say if you want MFA you need to control or configure that within Azure AD – this is Conditional Access Policy in AAD and it’s powerful, granular and the scope of what is included is growing.
And as we discussed at the start of this post, the SaaS app that directly trusts AD FS is obvious and easy – that’s just additional authentication rules.
However, if you have apps that trust an FS that in turn trusts your FS and those application owners want some of the MFA action then you have a bit of a problem, and this is really the reason I wanted to write this post. To be clear, we’re specifically talking about this section of the architecture:
The issue that often confuses people is what the IdP is actually doing. If we quickly summarise the flow of authentication, it goes like this:
- User/client hits the web app and is redirected to the IdP for authentication (the first FS – sts.contososervices.com)
- User selects realm and is redirected to the corporate FS/IdP (the second FS – idp.contoso.com).
- (Users typically interact with the FS to perform Home Realm Discovery (HRD) a.k.a. IdP discovery – in the above case this involves a custom page with a button for external users and another button for corporate users.)
- User authenticates with the second FS (idp.contoso.com) and is issued a token for the first FS/IdP and is redirected back to the first IdP
- User authenticates with the first FS (sts.contososervices.com) using the token issued by the second FS and is then issued a new token for the web app and redirected back to the web app
- User is authenticated using the second security token – the token from the first IdP
All of this happens through java-script initiated browser redirects and HTTP POSTs. The request for a token to the first IdP from the second IdP (a request for a SAML token for sts.contososervices.com, issued by idp.contoso.com) has no information relating to the actual app the user is hitting. Only when the first IdP is issuing a token for the app is there any real information available about the app. When authenticating with the second FS the app is technically the first FS.
So making a decision about whether to invoke MFA or not is not really the responsibility of the second – the corporate – FS. The corporate FS/IdP cannot accurately make the right decision as it is simply issuing a token for another IdP (although it’s just another relying party to the FS). What options exist then? Assuming that you don’t want to discuss the obvious answer which is implement MFA at the first FS.
Options for implementing MFA for an app that indirectly trusts your FS/IdP that integrates with your MFA system
Note – I use the terms federation service (FS) and identity provider (IdP) interchangeably throughout this (and other) posts. As far as I am concerned they are synonymous.
- The easiest answer is to require MFA for members of one or more groups. This is typically done via an equality match on the value (which is a string representation of a SID) in the http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid claim type
- The benefit here is you lump users into a group and they get MFA
- The disadvantage here is that the users might be using other applications that do not require MFA and will still be expected to undergo MFA each time they get their SSO token from the corporate AD FS (once per day or once per browser session, depending on usage)
- You can control whether or not to invoke MFA based on endpoint here too, so you can permit browser-based authentication and bypass MFA for applications that talk directly to WS-Trust endpoints – this may or may not be a good thing as you can stop apps from breaking but obviously negate the security benefit of the secondary authentication
- Another option is that the application can request MFA itself, i.e. the federation service simply honours the request. This is typically done within the application itself. Options are:
- The application actually inspects the claims in the authentication token and determines whether or not MFA is needed, e.g. it looks to see if there’s an MFA authentication type value (http://schemas.microsoft.com/claims/multipleauthn) in the http://schemas.microsoft.com/claims/authnmethodsreferences claim type and perhaps also takes a look at the value of http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork claim type or maybe checks to see what role the authenticated user holds and, if the logic is such that you determine MFA is required, the app asks the IdP for MFA via the WAUTH (WS-Federation) or RequestedAuthNContext (SAML) request parameter. This is generally known as step-up authentication. In summary, your code looks for whether or not you have done MFA (you must issue the AuthNMethodsReferences claim to the app) and requests it if you have not and it feels it is needed. Again, Ramiro does a quality job of describing this with a .NET MVC example here.
- If the app simply requires MFA regardless of condition, then it can build the original sign-in request containing the WAUTH request parameter. You can find a nice succinct discussion on the topic in this TechNet wiki article.
- Beyond this you need to start venturing into the grey area of support that I like to think of as creative territory. Sure, it’s fun and you can make it work, but it’s unlikely to be an operationally sound option going forward.
- For example, you can look to rewrite the URL between the RP and the first FS and add the WAUTH query string, which the FS will honour. In one customer case I did a basic POC here, because the first FS was running AD FS 2.0 and therefore had IIS locally, so an HTTP Module simply added the WAUTH query string if it wasn’t there.
- You can also bake a “smart URL” where you craft the request yourself and again the FS will honour it, e.g. https://sts.contososervices.com/adfs/ls/?wa=wsignin1.0&wtrealm=urn:federation:dumpclaims&wauth=http://schemas.microsoft.com/claims/multipleauthn – users hitting this URL will undergo MFA and logon to the app, however you have to be sure that users will access the app via the URL.
What about invoking MFA at the first FS for users coming from the CP trust? (In other words, what if we look for users authenticating from the second FS and invoke MFA for the app here?)
If you try to do this using additional authentication rules as you would a user in the same realm as the FS (i.e. your AD DS forest or trusted forest) then this won’t work in AD FS 2012 R2. The request for MFA is ignored. It is not bounced back to the IdP. However, AD FS 2016 changes this…
AD FS 2016 exhibits the same behaviour as AD FS 2012 R2 by default. But this behaviour is configurable. There are two properties of interest:
- On the relying party trust, the Boolean property RequestMFAFromClaimsProviders – set this to TRUE and the RP authentication rules will fire for claims provider trusts that support MFA (for CP trusts that do not support MFA, then you will either get an access denied when requesting your token or will get your token without MFA, depending on the access policy)
- On the claims provider trust, the Boolean property SupportsMFA – set this to TRUE and the redirect to the claims provider will include the WAUTH parameter with the multipleauthn value which will request MFA (just like Azure AD does when your federated domain has SupportsMFA set to true)
- If your upstream FS is not AD FS or doesn’t support the multipleauthn value in WAUTH you can specify the URI that will trigger MFA using the property CustomMFAUri
So, to answer the original question, upgrade sts.contososervices.com to AD FS 2016 and configure per-app MFA on each in-scope RP trust and make use of the above properties to get idp.contoso.com to perform the MFA.
Controlling multi-factor authentication via conditional access policy is a very powerful feature of AD FS. The scope of AD FS conditional access policy must be fully understood, however. AD FS can make granular decisions to allow or deny access to a trust or require MFA to a trust based on the set of claims available to the policy engine. The important point here is that this configuration is made at the trust level, which is generally an application and occasionally two or three apps that are typically categorised the same. If there’s an intermediary federation service, such as Azure AD (in the use case where you utilise federated users to access AAD SaaS apps, for example) or another federation service between your AD FS IdP which handles MFA and the app then you have no ability to determine what application is requesting the token and therefore your decisions only relate to the FS that you are getting a token for, which complicates your ability to define per-app MFA policies. I have described some options in this space, but in reading them it should be obvious that the answer lies either within the app itself (generally not appropriate other than for administrative step-up) or the intermediary FS. This is important. I will cover off conditional access policy in more depth soon, but the purpose of this post want to highlight why your AD FS with MFA server can’t help mandate MFA for app #1 and not app #2 that trust an IdP (FS) that trusts yours. The good news is that AD FS 2016 can be a big help here…