Integrated Windows Authentication (IWA) with Enhanced Protection for Authentication (EPA) and Chrome–it now works!

Some time back I posted quite a popular post describing the effect of a bug in Chrome that prevented the use of Enhanced Protection for Authentication, a.k.a. TLS channel binding, when authenticating via Integrated Windows Authentication (IWA).  I cared about this because it meant I had to disable EPA on Active Directory Federation Services (AD FS) farms where Chrome is a supported client.

Well, good news.  The bug has been fixed in Chrome 51.  Chromium 270219 was closed as fixed on March 26th, 2016.

What does this mean?  It means that you (a) don’t have to disable EPA in your AD FS farm; and (b) for those of you who have disabled it you can now turn it back on and close down those security waivers that you have open. 

 

Posted in AD FS, News | Tagged , , , , , | 1 Comment

Azure Multi-Factor Authentication (#AzureMFA) and Active Directory Federation Services (#ADFS)

Today, implementing Azure Multi-Factor Authentication (MFA) in an hybrid identity and access management solution based on Azure Active Directory (Azure AD, AAD) and Active Directory Federation Services (AD FS) more often than not requires that you implement the on-premises Azure MFA Server component.  This is because you typically have relying party (RP) trusts established in your on-premises AD FS federation service.  That is, you have some Software-as-a-Service (SaaS) apps that authenticate via AD FS and not Azure AD.  In order to introduce Azure MFA into the authentication process for these apps you require a secondary authentication provider deployed within AD FS.  Smaller deployments can install the MFA Server component directly on the federation service (FS) servers; larger deployments, i.e. more than two FS, will generally build out a load-balanced Azure MFA Server “farm” and utilise the Azure MFA “adapter” (the secondary authentication provider) to talk to Azure MFA Server via the load-balanced VIP (virtual IPv4 address).

I’ll provide a couple of drawings, to illustrate what I’m talking about.  Firstly, here’s the on-premises scenario – SaaS, AD FS and MFA Server.  In this case Azure MFA Server is mandatory – it’s no different to implementing any other MFA technology.  The cloud can’t really help with this use case today.

image

If you have an hybrid Azure AD and AD FS IdP then this is what it looks like: Azure AD, AD FS and MFA Server authenticating an Azure AD-hosted SaaS app.  In this case Azure MFA (cloud) is not used and again Azure MFA Server (on-premises) is because it’s a hybrid environment – it factors the AAD SaaS (and first-party) apps as well as on-premises apps.

image

If you’re building this now, especially if it’s for AD FS-only purposes, then this pattern is somewhat frustrating, more so if you have users registered with Azure MFA (the cloud service) or even Azure Password Management.  You cannot migrate registration data between cloud and on-premises (or between on-premises and the cloud) so you end up having to go all-out and deploy on-premises MFA Server, user and mobile registration portals, localisations and then manage the end-user communications and helpdesk management as well as the directory synchronisation.  Not ideal.  One example, I had to add eight (8) additional servers to an existing (I previously deployed) twenty (20) node AD FS deployment (8 FS, 8 WAP, 4 SQL) – 28 servers in total!

Which is why Windows Server 2016 Active Directory Federation Services (AD FS 2016) has a new and improved Azure MFA secondary authentication provider.  AD FS 2016 ships with a built-in “connector” for Azure MFA that talks directly to the cloud service and negates the need for any on-premises MFA Server infrastructure.  As long as the identities that AD FS is authenticating are synchronised to your Azure AD everything is in place.  Furthermore, AD FS 2016 actually enables Azure MFA as a primary authentication mechanism!  What does that mean?  It means that if you register the Azure Authenticator app, next time you access an AD FS protected resource you can authenticate by providing your username and the one-time-pin (OTP) code from the app.  No password!  This is one of several key new features arriving in AD FS 2016 that negate the need for users to provide credentials in the form of passwords!  I’ll blog about all of the no-password features in a separate post.  For now my focus is the Azure MFA [cloud] “adapter”.

May 2016 (Windows Server 2016 Technical Preview)

Windows Server 2016 Technical Preview 5 shipped a little while back.  When it shipped the Azure MFA secondary authentication provider was in private preview.  As of the 17th of May, 2016, the preview is public.  This means WS 2016 TP5 gives you everything you need to deploy Azure MFA as a primary or secondary authentication provider!

The official documentation was a few days behind the actual transition from private to public but is there now.  Here’s the URLs:

Configuration/deployment

I won’t re-document the configuration steps here, I’ll just summarise the actual process:

  1. For each federation service server/node in your AD FS farm, you create a certificate that will be used to authenticate with Azure MFA.  AD FS provides a PowerShell cmdlet for this – New-AdfsAzureMfaTenantCertificate
  2. Each certificate is assigned to an Azure AD Service Principal by creating a service principal credential.  This is done via New-MsolServicePrincipalCredential and is no different to assigning a credential to an Azure Web App or Web API, etc.  The app principal ID is a constant – it’s 981f26a1-7f43-403b-a875-f8b09b8cd720.
  3. Lastly, once you’ve created a cert for each FS node and created a service principal credential for each cert you configure the Azure MFA provider using another AD FS PowerShell cmdlet – Set-AdfsAzureMfaTenant.  This operation is performed once per farm and can be run from any node.

Once this configuration is done you’re ready to start configuring additional authentication policies for RP trusts other than urn:federation:MicrosoftOnline (The Azure AD/Office 365/MSOnline) trust.  For the Azure AD trust you configure the SupportsMfa Boolean property of each federated domain (Set-MsolFederatedDomain) and utilise Conditional Access Policy (CAP) to invoke MFA for Azure AD apps (SaaS, PaaS, App Proxy and first-party apps).  When using Azure MFA there is no need to offload MFA to AD FS – you just use the cloud.  This is different – the inverse – from the AD FS 2012 R2 and AAD hyrbid model.

Caveats and limitations

There are some features missing.  You have to enable (or enforce) at the per-user level if you want an in-line registration experience.  Per-app MFA with automatic “in-line” registration is not available.  You obviously cannot only utilise Azure MFA as a primary authentication provider either, so the typical pattern will be to enable in isolation for the extranet and utilise IWA and/or FBA for the intranet.

The per-user model is particularly frustrating.  It effectively mandates the use of app passwords which are difficult to deploy and use.  The alternative is getting users to register themselves and then utilising CAP, however as this will break non-registered users I can’t see this being a suitable option either.  Time will tell how useful this is and whether it is a sufficiently flexible implementation that truly negates the need for an on-premises MFA Server setup.

Summary

Prior to Windows Server 2016 integrating Azure MFA with on-premises SaaS apps that authenticate using AD FS required an on-premises implementation of Azure MFA Server

Windows Server 2016 (starting in public preview in Technical Preview 5) introduces a new and improved secondary authentication provider for Azure MFA that does not require any on-premises components.  The “adapter” talks directly to the cloud service; configuration of all MFA properties are managed in the cloud (it’s a hybrid of on-premises and cloud when you use the on-premises MFA Server).

In addition to negating the need for on-premises MFA components AD FS 2016 also introduces additional authentication scenarios for Azure MFA customers.  Namely, as part of the drive to never require a password when accessing corporate resources from outside of the corporate network, AD FS 2016 supports Azure MFA as a primary authentication provider, which means users can sign-in to AD FS-protected resources using their username, e.g. pawill@contoso.com, and their Azure Authentication one-time-pin (OTP) code.

Posted in AD FS, Azure MFA | Tagged , , , , , | 9 Comments

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:

Posted in AD FS, Azure Active Directory | Tagged , , , , , , , | 4 Comments

Azure Multi-Factor Authentication Server portal looping layer-8 issue

I’ve recently designed and implemented a large hybrid identity provider that comprises Azure Active Directory Premium, Active Directory Federation Services and Azure Multi-Factor Authentication Server.  One of the things I did was utilise Internet Information Services (IIS) URL rewrite rules to simply the user experience when interacting with the Azure MFA Server user portals.  In doing so I encountered a nasty issue far more prevalent on Chrome than IE or Firefox that I think is worth discussing.

First things first.  This is not a Chrome issue.  This is a layer-8 issue.  Ultimately my URL rewrite rules were a bit sloppy, or not tight enough.  Here’s what happened and how we fixed it.

Background

The MFA Server requires you implement a user portal and a mobile portal.  Collectively these two portals are referred to as the user portals and they’re deployed in the perimeter network or DMZ and maybe also internally.  In my case I duplicated the portals internally and externally, i.e. the URLs were available wherever you are, although through the use of split-brain DNS you would hit different load balancers.

So, we had the following portal URLs:

Internet DNS resolves mfa.customer.com to our DMZ load balancer VIP.  Corpnet DNS resolves to our internal VIP.  Users hit both the internal and external ~/user portal.  Only devices communicate with the ~/mobile portal (it’s for registering the Azure MFA app) therefore 99% of this traffic originates from the Internet with only VIP iPads having actual corpnet connectivity and using corpnet DNS.

IIS URL rewrite rules

To simplify the user experience I utilised URL rewrite rules to perform the following redirects:

http://mfa.customer.com https://mfa.customer.com/user/
https://mfa.customer.com https://mfa.customer.com/user/
http://mfa.customer.com/user https://mfa.customer.com/user/
http://mfa.customer.com/mobile https://mfa.customer.com/mobile/

In other words, if you just stick mfa.customer.com into your browser you’ll end up in the correct place.  Likewise if you actually utilise the HTTPS prefix but don’t add the subsite you’ll end up in the right place.  The mobile redirect is not really needed as the URL is baked into the web services and the device gets the full URL and will break if it hits the redirect rule, however it’s there for completeness and laziness, i.e. testing the WS is live.

The issue

Some users were reporting being unable to utilise the portal.  The specific behaviour was that they would logon to the portal, which requires MFA, and then clicking any URL, e.g. electing to Change Method or Activate Mobile App, would result in them being returned to the logon page and having to re-logon.  This looping persisted with no ability to actually do anything within the portal.

The interesting thing was that not all users were experiencing it.  And not all of us could reproduce it.  Interestingly I was affected, but only on Chrome and not using IE (my default browser), Firefox or Edge.

I checked that all the URL rewrite rules were consistent across all nodes and got the load balancing expert to recheck the persistence settings were in line with our design (client-IP persistence) and they were.

Anyway, to cut quite a long story short, the Premier case was raised and escalated and we checked and rechecked and traced and retraced the behaviour.  We had escalation engineer and developer in the mix.  Finally, a ninja named Richard Lang, identified the issue…

Root Cause

The failure to utilise the portal after a successful logon was determined to be due to the clearing of web server session variables during an extra request made to Login.aspx.  The error is far more prevalent in Chrome, however it is not believed to be a bug in Chrome, rather a different, albeit appropriate, way of handling favicon.ico requests.  (Browsers automatically generate requests for this favicon.ico to display small icons for shortcuts or bookmarks.)

During the login process, there was a request for a favicon at the root of the web application, e.g. http://hostname/favicon.ico.  In our environment/implementation, the configured URL Rewrite rules cause this request to be redirected, ultimately ending with a request to https://hostname/user/login.aspx.  This page is configured such that new sessions are started whenever you navigate to the page.  Thus, when this page is requested unexpectedly during the OTP page lifecycle, the session is cleared.  Under default/out-of-box configuration, the request to favicon.ico would not be loading login.aspx and this would not occur.  This behavior in login.aspx is by design and is not incorrect.

The difference between Chrome and other browsers (IE, Edge and Firefox were tested) is not fully understood, however it is certainly due to slightly different handling of favicon.ico requests.  It is believed that perhaps IE/FF are caching the favicon.ico failure and not sending as many retries as Chrome.

The resolution

Add an additional URL rewrite rule, before our ~/user redirect rule, to stop processing favicon.ico requests.  Here’s my new rule.

<rule name="Ignore favicon.ico" stopProcessing="true">
  <match url="^favicon.ico$" />
  <conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
  <action type="None" />
</rule>

So, the web.config for the root of my web app, i.e. e:\inetpub\mfa\, contains the following URL rewrite rules:

<configuration>
  <system.webServer>
    <rewrite>
      <rules>
        <clear />

        <rule name="Redirect to HTTPS" stopProcessing="true">
          <match url=".*" />
          <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
            <add input="{HTTPS}" pattern="^OFF$" />
          </conditions>
          <action type="Redirect" url="https://{HTTP_HOST}/" />
        </rule>

        <rule name="Ignore favicon.ico" stopProcessing="true">
          <match url="^favicon.ico$" />
          <conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
          <action type="None" />
        </rule>

        <rule name="Redirect to ~/User" enabled="true" stopProcessing="true">
          <match url="^.*$" />
          <conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
          <action type="Redirect" url="user/" />
        </rule>

      </rules>
    </rewrite>
  </system.webServer>
</configuration>

Let me summarise those rules:

  1. Redirect to HTTPS.  As the name suggests this redirects any request to HTTP host to HTTPS
  2. Ignore favicon.ico.  As the name suggests, and indeed this post hopefully explains, we need to stop processing URL rewrite rules if the request is for favicon.ico, so this rule has no action type and simply tells the rewrite rules engine to stop
  3. Redirect to ~/user.  This is the rule that redirects anything that is not https://mfa.customer.com/user/ or https://mfa.customer.com/favicon.ico to https://mfa.customer.com/user/

Summary

The [on-premises] Azure MFA Server user portal login.aspx page clears all existing sessions, so you do not want to accidently redirect a client to this page when they have already logged on, which is exactly what I was doing until I put an additional IIS URL rewrite rule into my configuration to do nothing but stop processing requests for favicon.ico.

If you want to improve your user experience and redirect users to the subsite, ensure you correctly handle requests to favicon.ico.

Lastly, just in case you want to see what the user interface and experience looks like, here is my ignore rule:

clip_image001

Hope this helps!

Posted in Azure MFA | Tagged , , , , | Leave a comment

The use of Distributed Key Manager (DKM) in Active Directory Federation Services (AD FS)

Something that crops up quite a lot when you’re involved in planning and designing an Active Directory Federation Services (AD FS) infrastructure is certificates and, for those of you who have worked anywhere where you have to justify your design to various control and governance authorities (think banks, government and defence for sure), what the certificates do and how they are controlled and protected.  Many people take for granted that an AD FS farm shares a set of common certificates.  Some have wondered how AD FS shares the private keys for the signing and encryption certificates.  Others have wondered why is there a bunch of containers with some blobs in some of the attributes created and maintained in the Active Directory Domain Services (AD DS) domain.

Well, hopefully I can explain this.

The token signing and token decryption certificates, including the corresponding private keys, are stored in the AD FS configuration database.  The certificates are encrypted using a technology called Distributed Key Manager (DKM).  AD FS creates and uses these DKM keys as and when needed, which basically means when you initialise the farm and when a key expires.

During the initial configuration of an AD FS farm the FS farm creation process creates a dedicated container in the on-premises AD DS domain of the account installing AD FS (which should be in the same domain as the service account for AD FS otherwise the service account can’t locate the container and will fail to start) to store a master encryption key using the aforementioned DKM solution (a more detailed explanation of the cryptography aspects of DKM can be found here).  This container is permissioned such that only the FS service account can read the key.

When a new certificate requires creation (either initially, during setup, or in an automatic roll over scenario), one of the nodes is elected to perform the following task:

  1. The server generates the certificate and stores it in the service account’s certificate store (the MY store for the service account)
  2. The certificate (the .PFX structure) is serialised; encrypted with the master key generated during farm initialisation, using DKM client APIs; and stored in the configuration database

When the AD FS service starts on one of the nodes the following happens:

  1. The server decrypts the table that contains all encrypted certificate information using the DKM client API
  2. The deserialised certificate is then stored in the service account’s certificate store on the local server

Lastly, the encryption key used by DKM, stored in AD DS, expires after a year.  AD FS manages the creation of a new key – AD FS uses a valid DKM key to encrypt the PFX to store in it’s configuration database; if a valid DKM key is not present, AD FS creates a new one.

So, in summary:

  • The token signing and decryption certificates are stored as PFX blobs in the AD FS configuration database
  • The table that houses the blobs is encrypted using a technology called Distributed Key Manager (DKM) and the DKM master key(s) are stored AD DS
  • The DKM master keys are created when required, i.e. during farm initialisation and when they expire
  • AD FS servers retrieve the DKM master key from AD DS and use this to decrypt the encrypted table in the AD FS configuration database; the decrypted PFX is installed into the MY store of the AD FS service account
  • Only the AD FS service account has permissions to read the key material stored in AD DS and only the AD FS service account has permissions to read certificates in its MY store by default
  • All federation service servers in the AD FS farm share the same token signing and decryption certificates

I hope you find this useful.  Special thanks to Ramiro Calderon for his assistance in making sense of this process.

Posted in AD FS | Tagged , , , , , , | 2 Comments

New WID support limit in AD FS

Excellent news!  The number of supported federation service (FS) servers in a farm with a Windows Internal Database (WID) backend has increased from 5/10 to 30:

A WID farm has a limit of 30 federation servers if you have 100 or fewer relying party trusts. If you have more than 100 relying party trusts, a WID farm has a limit of 5 federation servers.

From the AD FS design documentation: Federation Server Farm Using WID.

For MANY customers and implementors this means no SQL, which for AD FS is nothing but a good thing!

 

Posted in AD FS, Uncategorized | Tagged , , , , , , | 4 Comments

AD FS, Enhanced Protection for Authentication (EPA), Chrome and Integrated Windows Authentication (IWA)

Something that I’ve had the misfortune of working on to look into recently was the user experience when accessing federated business apps using a browser that isn’t Internet Explorer.  Suffice to say, my customer has “two” supported browsers: IE (9, 10 and 11) and Chrome.  A number of apps, such as Office 365 and Salesforce.com don’t support IE9, however other apps require IE9.  Wonderful.  So Chrome has its place (and thankfully supports GPO, which is one of the reasons I’m a big IE fan).

In its default state, Windows Server 2012 R2 Active Directory Federation Services (AD FS) will only perform Integrated Windows Authentication (IWA) for Internet Explorer.  Other browsers will fall back to Forms Based Authentication (FBA) *if* FBA, and failback, is enabled in the global authentication policy.  This configuration is made possible through two AD FS configurations (three individual settings):

  • The Global Primary Authentication policy is configured for both Windows Authentication and Forms Authentication for the intranet (Set-AdfsGlobalAuthenticationPolicy -PrimaryIntranetAuthenticationProvider) and failback to FBA (Set-AdfsGlobalAuthenticationPolicy -WindowsIntegratedFallbackEnabled $true)
  • The global AD FS property (Set-AdfsProperties -WIASupportedUserAgents) contains regex patterns for various versions of IE

What does this mean?  Basically you enable FBA on the intranet and ensure that the property WindowsIntegratedFallbackEnabled is TRUE.  Then, any user agent not matching an element in the WIASupportedUserAgents property list will hit the FBA page even on the corporate network.  The experience is like the Internet/WAP experience.

As and when new browsers come along you have to carefully consider how to match those that work with IWA via the WIASupportedUserAgents property, e.g. you might choose to add the following elements to handle Edge and IE11:

  • Windows NT 10.0; WOW64; Trident/7.0
  • Edge/1

To handle other browsers, you need to match them.  Mozilla/5.0 will capture a slew of other browsers, including mobile devices, but if you expand that to:

  • Mozilla/5.0 (Windows NT

Then that will limit it to Firefox, Chrome and Opera running on a supported build of desktop Windows.  This is the approach I took.

That mitigates the UX issues whereby Chatter users, using Chrome, for example are faced with the “external/Internet experience” while on corpnet.  Everyone wants SSO on corpnet right?

The real problem comes with how these browsers react after you make this configuration change.

Firefox

Firefox first, as this works.  It prompts a rubbish IWA prompt, you enter your creds (DOMAIN\sAMAccountName or user.name@domain.tld) and you’re done.  To stop it prompting and actually do IWA like you’re used to with IE you configure the following properties with your domain, e.g. corp.contoso.com,concorp.com:

  • network.negotiate-auth.trusted-uris
  • network.automatic-ntlm-auth.trusted-uris

Having done that Firefox is done.  Hit an RP and behold SSO like IE gives you.

Here’s a screenshot of configuring those properties via about:config:

ePAFirefoxIWASettingsView

Safari, Opera, A. N. Other browser

I’m led to believe that Safari on a domain-joined Mac and Opera on Windows also work as expected (i.e. they can correctly handle IWA) but haven’t tested this myself.  Let me know if you see differently.

Chrome

Chrome on the other hand has an issue with Extended Protection for Authentication (EPA).  I’m not going to go off-topic and try and fully explain EPA, suffice to say that EPA is intended to increase the strength of the binding for the authentication when run over a Transport Layer Security (TLS) connection.  It mitigates a number of man in the middle (MITM) attacks and thus provides additional protection of browser-based authentication traffic.  More info.:

What is the issue?  Look to #43 in Chromium #270219 (https://code.google.com/p/chromium/issues/detail?id=270219):

Because Chrome doesn’t use Schannel for the SSL implementation it is not correctly implementing channel binding, thus Chrome cannot perform IWA when EPA is enabled (mandated) or allowed (optional).

What does this really mean?  It means you have to disable EPA within AD FS to support Chrome until the above bug is fixed and a stable released.

There’s plenty of info. about this around the place.  Basically that means setting the AD FS property ExtendedProtectionTokenCheck to None, e.g.

Set-AdfsProperties -ExtendedProtectionTokenCheck None

Here’s the KB: https://support.microsoft.com/en-us/kb/2461628

I felt that I needed to post something because the KB doesn’t give me enough.  This is quite specific to Chrome and the reason is a bug in Chrome not some issue in AD FS.  Firefox isn’t affected.  It’s also worth noting that the obvious answer isn’t to downgrade the security of the TLS session by simply disabling the ability to use CBTs.  For many the fall-back to FBA might be perfectly adequate.  This is only an issue if you use Chrome in the enterprise, want IWA and not FBA on corpnet and have configured AD FS to request IWA from non-IE browsers.

Additional information

For completeness, the TechNet article Configuring intranet forms-based authentication for devices that do not support WIA, currently (as of December 2015) lists the following user agent strings (which is a larger set than that that ships with AD FS):

Set-AdfsProperties -WIASupportedUserAgents @(
'MSIE 6.0',
'MSIE 7.0; Windows NT',
'MSIE 8.0',
'MSIE 9.0',
'MSIE 10.0; Windows NT 6',
'Windows NT 6.3; Trident/7.0',
'Windows NT 6.3; Win64; x64; Trident/7.0',
'Windows NT 6.3; WOW64; Trident/7.0',
'Windows NT 6.2; Trident/7.0',
'Windows NT 6.2; Win64; x64; Trident/7.0',
'Windows NT 6.2; WOW64; Trident/7.0',
'Windows NT 6.1; Trident/7.0',
'Windows NT 6.1; Win64; x64; Trident/7.0',
'Windows NT 6.1; WOW64; Trident/7.0',
'MSIPC',
'Windows Rights Management Client'
)

In my environments I actually implement the following:

Set-AdfsProperties -WIASupportedUserAgents @(
'MSIE 6.0',
'MSIE 7.0; Windows NT',
'MSIE 8.0',
'MSIE 9.0',
'MSIE 10.0; Windows NT 6',
'Windows NT 6.3; Trident/7.0',
'Windows NT 6.3; Win64; x64; Trident/7.0',
'Windows NT 6.3; WOW64; Trident/7.0',
'Windows NT 6.2; Trident/7.0',
'Windows NT 6.2; Win64; x64; Trident/7.0',
'Windows NT 6.2; WOW64; Trident/7.0',
'Windows NT 6.1; Trident/7.0',
'Windows NT 6.1; Win64; x64; Trident/7.0',
'Windows NT 6.1; WOW64; Trident/7.0',
'MSIPC',
'Windows Rights Management Client',

'Windows NT 10.0; WOW64; Trident/7.0',
'Edge/1',
'Mozilla/5.0 (Windows NT'
)

…and one more thing

I promised my colleague John that I wouldn’t rant about the whole WIA vs. IWA nomenclature clash.  So I won’t.  But I’ll end the post with this question: what were the AD FS product group thinking?  (The correct and true acronym is IWA and has been for years!) 🙂 🙂

Summary

If you want to support non-IE browsers enable FBA on the intranet authentication provider and enable fallback to FBA, e.g.

Set-AdfsGlobalAuthenticationPolicy -PrimaryIntranetAuthenticationProvider @( 'WindowsAuthentication', 'FormsAuthentication' ) -WindowsIntegratedFallbackEnabled $true

If you want to enable some of these browsers to not perform FBA and work in the same way as IE, i.e. SSO using IWA, then you need to pick an appropriate user agent string and add it to the WIASupportedUserAgents property, as illustrated above using Set-AdfsProperties -WIASupportedUserAgents

If you want to use IWA to SSO to AD FS using Chrome you will need to disable EPA in the short term until Google fix Chrome.  Long term you should look to turn EPA on and make use of CBT.

 

Posted in AD FS, Uncategorized | Tagged , , , , , , , , , , | 5 Comments