Search code examples
authenticationidentityserver4adfsidentityserver3adfs4.0

Building Custom Authentication Method for ADFS 2019 (v4)


I am having trouble creating a custom authenticator for ADFS v4 on Windows Server 2019. My goal is to create a custom primary authenticator but right now I'd settle for getting a custom authenticator to work as an additional authentication provider. I followed this article by Microsoft and although it states the tutorial is for 2012, it's supposed to work for 2019 as well. I apologize if what follows comes across as a stream-of-consciousness but I'm fairly new to this and may have multiple things wrong with my implementation.

Initial struggles

When I follow the directions from Microsoft, I'm able to see the authenticator in the list of primary authenticators and select it. However, when I go through my authentication process the code never fires. I am never presented with the custom HTML fragment in the project. If I understand the code from this example correctly, I should be able to set the authenticator as primary and only get the HTML from my authenticator. The best I am able to do is get the friendly name to show up in a list of possible authenticators if more than one primary authenticator is selected.

/// Returns a Dictionary containing the set of localized friendly names of the provider, indexed by lcid. 
     /// These Friendly Names are displayed in the "choice page" offered to the user when there is more than 
     /// one secondary authentication provider available.
     public Dictionary<int, string> FriendlyNames
     {
         get
         {
             Dictionary<int, string> _friendlyNames = new Dictionary<int, string>();
             _friendlyNames.Add(new CultureInfo("en-us").LCID, "Matt's Friendly name in the Meatadata Class");
             _friendlyNames.Add(new CultureInfo("fr").LCID, "Friendly name translated to fr locale");
             return _friendlyNames;
         }
     }

friendlyName

If I click my custom authenticator it just errors and I get an entry in the ADFS event log that says it cannot find the specified user. I thought that by using the custom it would bypass any Active Directory lookup but apparently it's still doing the lookup and I'm never presented with my custom login page.

Then I added logging

I logged every method in my code to the Windows event log and for a little bit I would get a message that the program entered the OnAuthenticationPipelineLoad method.

        public void OnAuthenticationPipelineLoad(IAuthenticationMethodConfigData configData)
        {
            //this is where AD FS passes us the config data, if such data was supplied at registration of the adapter
            myNewLog.WriteEntry("Matt -> this is where AD FS passes us the config data, if such data was supplied at registration of the adapter");

        }

Unfortunately, this stopped working at some point and I can't get it back so it's like the code isn't even making it to here any more.

Microsoft's example doesn't even work

I scrounged GitHub looking for other people who had done this and found Microsoft's example provider. Unfortunately, Microsoft's code doesn't work either so it must be something that I have configured wrong but I don't know where to look.

Then I tried making it the secondary authenticator

I tried setting my custom authenticator as secondary but the code never fires in this case either.

Suspicions

Before my logging stopped working, I thought the code might have an issue with the AuthenticationMethods metadata.

        /// Returns an array of strings containing URIs indicating the set of authentication methods implemented by the adapter 
        /// AD FS requires that, if authentication is successful, the method actually employed will be returned by the
        /// final call to TryEndAuthentication(). If no authentication method is returned, or the method returned is not
        /// one of the methods listed in this property, the authentication attempt will fail.
        public virtual string[] AuthenticationMethods
        {
            get {
                myNewLog.WriteEntry("Matt -> AuthenticationMethods");

                return new[] { "https://example.com/myauthenticationmethod1" }; }
        }

I found hints that this could be an issue here and here. It says "IAuthenticationAdapterMetadata: defines adapter metadata including its name and the type(s) of authentication it supports" and "Both the Global and Relying Party MFA AdditionalAuthenticationRules claim rule sets are executed. (Box C). If the output claim set of either rule set contains a claim of type "http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod" and value "http://schemas.microsoft.com/claims/multipleauthn", then MFA will engage."

Question

I guess I don't know what I should even ask as my question. Has anyone created a custom ADFS authenticator before and seen this problem? Is there something obvious that I can check that could be causing this?


Solution

  • This turned out to be a two-part problem and was party caused by my lack of domain knowledge.

    Secondary authenticator fix

    My issues with using my code as a secondary authenticator had to do with setting the claims rules. This is the power shell script that the tutorial had me run:

    Set-AdfsRelyingPartyTrust –TargetRelyingParty $rp –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" );'
    

    My problem was that I was testing from inside my network as opposed to outside my network so the script was wrong for me. Once I edited the rule to force MFA for internal users, my code successfully hit.

    Set-AdfsRelyingPartyTrust –TargetRelyingParty $rp –AdditionalAuthenticationRules '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" );'
    

    Primary authenticator fix

    As it turns out, I cannot use my code as a primary authenticator in the way I was trying to. ADFS only allows you to authenticate against the AD identity provider. The message "cannot find the specified user" in the ADFS event log was a result of trying to bypass the step that sets the user. Since I did not want to query Active Directory for my user, I would then get an error that the authenticator could not find the specified user...makes sense.

    What I actually needed was a different identity provider. For my relying party trust in ADFS, I needed to specify an identity provider other than Active Directory. I found an example of this here. This solution uses identity server 3 hosted in an asp.net site as an identity provider. By using this solution, I was able to manually set the claims for the current user.

    This solution uses identity server 3 which is outdated and ideally you would use the currently supported identity server 4. However, the ws-federation part of identity server 4 has moved behind a pay wall.

    I still had a problem with my relying party trust since they would be presented with multiple identity providers (or claims providers as ADFS calls them) when users came to our site from the relying party. To prevent this screen from showing up, you can set your identity server as the default claims provider for your relying party.

    Set-AdfsRelyingPartyTrust -TargetName "Relying Party" -ClaimsProviderName ("IdentityServer")
    

    Additional notes

    There is a good write on what identity server is and the problem it is trying to solve here

    There is also an open source repo for filling in the ws-federation piece of identity server 4 here which I did not use