次の方法で共有


How to create a Custom Authentication Provider for Active Directory Federation Services on Windows Server 2012 R2 - Part 2

In this series of five blog posts I want to show you how you can create your own Authentication Provider in AD FS on Windows Server 2012 R2. This Authentication Provider can then be used in AD FS for multi-factor authentication (MFA). The solution will use the users mobile device as a second factor for authentication, by sending a One-Time Password (OTP) or PIN to the device.

For those of you interested only in the AD FS related components, part 2 will be most interesting. That's where we will be creating the Authentication Provider itself. But if you are looking for a complete, working, solution, you might want to go over the entire set. So you might be interested by now what's in the other parts... or at least I hope you are. So here's a quick agenda;

  1. Introduction
  2. Creating the Authentication Provider
  3. Setting up Azure Mobile Services
  4. Creating a Windows Phone Authenticator App
  5. Putting it all together

Now, before we get started I have to remind you that this blog post series is only to show you how you can create this Authentication Provider. By no means is the solution we are creating an enterprise ready MFA solution. If you are looking into such an enterprise ready solution, I would recommend you take a look at the Windows Azure Multi-Factor Authentication.

Now... Let's get going with Part 2: Creating the Authentication Provider.

Part 2 - Creating the Authentication Provider

AD FS in Windows Server 2012 R2 allows a developer to create a custom Authentication Provider by implementing at least two interfaces to the Microsoft.IdentityServer.Web.Authentication.External class.This class is defined in the Microsoft.IdentityServer.Web.dll file that is present in all Windows Server 2012 R2 AD FS servers. AD FS is installed in the Windows directory, under ADFS. So by default, this file would reside in C:\Windows\ADFS.

Prerequisites

We will assume you use Windows 8.1 and Visual Studio 2013, and have a Relying Party configured in AD FS that can be used to test the custom attribute store. In this example a custom attribute store will be created using Visual C#.

Copy down the file Microsoft.IdentityServer.Web.dll from the AD FS Server to your PC. The default location of this DLL in Windows Server 2012 R2 is C:\Windows\ADFS.

Building the Authentication Provider

Start Visual Studio, and create a new Project by clicking File, New and then Project, or by hitting Ctrl+Shift+N.

Create a Project of type Class Library (located in the branch Installed, Templates, Visual C# , Windows) and make sure the target framework is .NET Framework 4.5 (for AD FS 3.0). Give the Project a proper name, in my example MyAuthenticationProvider and click OK.

In Visual Studio, the Project should now have been created and a new Class file (called Class1.cs) should be opened. On the right side of the page, locate the References branch under your Project and your Solution in the Solution Explorer and right-click it. Click on Add Reference… to add a reference to the Microsoft.IdentityServer.Web.dll file you copied from the AD FS Server.

Select Browse on the left side, and click the Browse… button.

Now, locate the Microsoft.IdentityServer.Web.dll you copied down from the AD FS Server, and click it.

Click Add to select the DLL to be referenced in your project.

Make sure the checkbox in front of the filename has been checked, and click OK.

A new reference to the Microsoft.IdentityServer.Web.dll should now have been created in your Solution. The reference shows up as Microsoft.IdentityServer.Web.

Since this file is already on the AD FS server, and we will place our Authentication Provider in the Global Assembly Cache of the server, there is no need for Visual Studio to copy the file to the output directory of the project when we compile it. In the Solution Explorer, click the Microsoft.IdentityServer.Web reference, and, at the bottom of the screen, in the Properties window, select False from the pull-down menu next to Copy Local.

Now that the proper reference is in place, we can start building our authentication provider in code. First, let's delete the Class1.cs file that Visual Studio created for us, by selecting it in the Solution Explorer on the right and pressing DEL. (Or right-click it and select Delete.)

Click OK to delete the Class1.cs file.

Now, in Solution Explorer, right-click your projects name (which should be MyAuthenticationProvider if you followed this guide), select Add and click Class. (Or simply press Shift-Alt-C.)

In the Add New Item dialog box, next to Name: , type AuthenticationAdapter and click Add.

Your newly created AuthenticationAdapter class file called AuthenticationAdapter.cs will now open in Visual Studio. This is the place where we will implement the Authentication Adapter.

Underneath the existing using statements, type a new using statement;

using Microsoft.IdentityServer.Web.Authentication.External;

Directly after the declaration of the AuthenticationAdapter class, which should show like this;

public class AuthenticationAdapter

on the same line, at the end, add:

 : IAuthenticationAdapter

This way, we inform Visual Studio that this Class, called AuthenticationAdapter, will implement the Interface IAuthenticationAdapter (which comes from the Microsoft.IdentityServer.Web.Authentication.External namespace). The line should look like this:

public class AuthenticationAdapter : IAuthenticationAdapter

Your AuthenticationAdapter.cs code page should now look like this:

Now, right-click on IAuthenticationAdapter that you typed in the last step right after class AuthenticationAdapter, and select Implement Interface and Implement Interface.

Now, Visual Studio will create all the methods you need to properly implement the Authentication Adapter. Your code should resemble this;

So let's first briefly go over all the methods that we now need to implement in our Authentication Provider.

The IAuthenticationProvider Methods and Properties

In order for the Authentication Provider to work properly, we need to implement seven different methods in our Authentication Adapter. We will briefly discuss these different methods, and what actions AD FS expects these methods to perform.

public IAdapterPresentation BeginAuthentication(System.Security.Claims.Claim identityClaim, System.Net.HttpListenerRequest request, IAuthenticationContext context)

This method is called by AD FS once AD FS decides that Multi-Factor Authentication is required (and available) for a user. It will pass the Identity Claim to the Authentication Adapter. The Authentication Adapter decides on what this identity claim should be. (We will show how this works later on.) Think of a UPN or SAM Account Name. AD FS also passes the context of the authentication request. This context store data required by AD FS and the Authentication Adapter to perform and complete the authentication.

The method has to return a variable that implements the IAdapterPresentation interface. Hey... That's new. We currently have no class in our Visual Studio project that implements this interface. So it appears we need to implement this interface as well! We'll do that in one of the next sections of this blog post. Anyway, this return-type contains information that AD FS uses to build the proper web page to authenticate the user (during a browser based logon). Let's say you want the ask the user for a PIN, then we have to create a few lines of HTML code that show this input box, together with an appropriate text, to the end-user. That's just what this return value does.

public bool IsAvailableForUser(System.Security.Claims.Claim identityClaim, IAuthenticationContext context)

The IsAvailableForUser method returns either true or false and is an indication to AD FS that your Authentication Adapter can actually perform Multi-Factor Authentication for the user. In order to decide whether to return true or false, an identity claim is passed from AD FS to the Authentication Provider. Check the IAdapterPresentation method we will cover in a few moments for the usage of the identity claim (identityClaim) and authentication context (context).

The method should return true if this Authentication Provider can handle authentication for this identity, or user, and false when it can not.

public IAuthenticationAdapterMetadata Metadata

Metadata actually is not a method but a property. It is used by AD FS to learn about your Authentication Provider (just like the FederationMetadata.xml can be used by AD FS to learn about a relying party or claims provider). The property should 'return' a variable of a type that implements IAuthenticationAdapterMetadata. Again, this is an interface we have not yet implemented in our Authentication Provider and has to be implemented. We'll do this later on. For now, just think of the IAuthenticationMetadata as information about the Authentication Provider. One example would be that you can 'tell' AD FS what the Identity Claim is your authentication adapter expects when the methods like BeginAuthentication and IsAvailableForUser (both of which we have seen before) that are called by AD FS.

public void OnAuthenticationPipelineLoad(IAuthenticationMethodConfigData configData)

The OnAuthenticationPipelineLoad method is called whenever the Authentication Provider is loaded by AD FS into it's pipeline. It allows your adapter to initialize itself. AD FS will 'tell' your adapter which information AD FS has. The IAuthenticationMethodConfigData contains a single property called Data. For the developers; this property is of type Stream. In our sample Authentication Provider will not use this configData.

The method returns nothing.

public void OnAuthenticationPipelineUnload()

The OnAuthenticationPipelineUnload is called by AD FS whenever the Authentication Provider is unloaded from the AD FS pipeline and allows the Authentication Adapter to clean up anything it has to clean up.

The method returns nothing.

public IAdapterPresentation OnError(System.Net.HttpListenerRequest request, ExternalAuthenticationException ex)

The OnError method is called whenever something goes wrong in the authentication process. To be more precise; if anything goes wrong in the BeginAuthentication or TryEndAuthentication methods of the authentication adapter, and either of these methods throw an ExternalAuthenticationException, the OnError method is called. This allows your adapter to capture the error and present a nice error message to the customer.

Because we have to present a nice error message to the user, this method returns an instance of a class that implements IAdapterPresentation. We've touched that interface before. We will implement and explain it later in this article.

public IAdapterPresentation TryEndAuthentication(IAuthenticationContext context, IProofData proofData, System.Net.HttpListenerRequest request, out System.Security.Claims.Claim[] claims)

This method is called by AD FS when the Authentication Adapter should perform the actual authentication. It will pass the IAuthenticationContext to the method, which we have seen before. It will also pass the proofData variable, that implements IProofData. This is a dictionary of strings to objects, that represents whatever you have asked the customer for during the BeginAuthentication method. In our sample Authentication Provider, the PIN that the user typed is passed to the TryEndAuthentication method in the proofData variable.

The method allows you to use the "claims" out parameter. If the Authentication Adapter has successfully performed the authentication, this variable should contain at least one claim with type https://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod. The value of this claim should contain the method of authentication used. It must be one of the values listed in the AuthenticationMethods parameter of the class that implements IAuthenticationAdapterMetadata. We have already seen this IAuthenticationAdapterMetada interface before, and already know we need to implement this one. So we'll get more details once we go over this interface.

The method returns a variable of a type that implements IAdapterPresentation. Typically, when authentication has succeeded you add the proper authentication method claim to the claims out parameter, and return null. Whenever authentication has failed, you can create a nice error message for the user and return this in the return variable. We've already see the IAdapterPresentation interface before, so we'll cover this interface in more detail later on.

More Interfaces

In the previous section we have seen that we need to implement at least two more interfaces, next to our IAuthenticationAdapter;

  • IAdapterPresentation
  • IAuthenticationAdapterMetadata

There is one additional complexity here; if we want to use Forms-Based Authentication (and our authentication adapter actually wants to use that), we also have to implement an interface that we haven't seen before. This is the IAdapterPresentationForm. So this one needs to be added to the list as well. We will implement the IAdapterPresentationForm in the same class that implements the IAdapterPresentation.

Now, using the exact same procedure as we used to create the interface implementation for the IAuthenticationAdapter interface, we will have to create two new classes in our project. By now, I'm sure you know how to create a class file. (Press Shift-Alt-C, type a name and click Add.) Using this method, create two new classes;

  • AdapterPresentation
  • AuthenticationAdapterMetadata

In both of these class files, add the using statement we've seen before;

using Microsoft.IdentityServer.Web.Authentication.External;

Have these classes implement the corresponding interfaces; so make sure the AdapterPresentation class implements IAdapterPresentation and IAdapterPresentationForm, and that the AuthenticationAdapterMetadata implements IAuthenticationAdapterMetadata.

The AdapterPresentation.cs file should cointain this line:

class AdapterPresentation : IAdapterPresentation, IAdapterPresentationForm

The AuthenticationAdapterMetadata.cs file should contain this line:

class AuthenticationAdapterMetadata : IAuthenticationAdapterMetadata

In both class files, right-click the interface names (IAdapterPresentation, IAdapterPresentationForm and IAuthenticationAdapterMetadata) and click Implement Interface. In the submenu, click Implement Interface again.

The class files will now contain the proper methods and properties that need to be implemented as well. We'll go over these briefly... We need to know what these do in order to successfully implement the AuthenticationAdapter Interface!

The IAuthenticationAdapterMetadata Methods and Properties

The IAuthenticationAdapterMetadata interface contains no methods, only properties. These properties are used to 'learn' AD FS about your Authentication Provider.

string AdminName { get; }

This is the friendly name of the Authentication Provider, shown to AD FS admins in the AD FS GUI.

string[] AuthenticationMethods { get; }

This should return a list (array) of strings, where each string is a supported authentication method. If, after successful authentication, the TryEndAuthentication method in the IAuthenticationAdapter interface return success, this methods must contain a claim of type https://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod. The value of this claim should be one of authentication methods listed in the property. In our sample Authentication Adapter we support only one authentication method; https://schemas.microsoft.com/ws/2012/12/authmethod/otp.

int[] AvailableLcids { get; }

This property should contain all the lcid's (or languages) that your Authentication Adapter supports. We'll only implement lcid 1033 on our sample Authentication Adapter.

Dictionary<int, string> Descriptions { get; }

This property should contain a list of descriptions for the Authentication Adapter, per language. I haven't seen this being used anywhere in AD FS...

Dictionary<int, string> FriendlyNames { get; }

If multiple Authentication Adapters (MFA providers) are available for a user, a form us presented to the user where he or she can chose how to perform additional authentication. The strings in this property (per language) that are used to identify your Authentication Adapter in that form.

string[] IdentityClaims { get; }

This property should contain the claim types that your Authentication Adapter requires. These claims, and values, are passed to multiple methods in the IAuthenticationAdapter. My testing revealed that only the FIRST one you enter here is presented to the adapter; so we will use UPN here.

bool RequiresIdentity { get; }

This is an indication whether or not the Authentication Adapter requires an Identity Claim or not. If you require an Identity Claim, the claim type must be presented through the IdentityClaims property.

The IAdapterPresentation Methods and Properties

The IAdapterPresentation interface defines how the Authentication Adapter 'presents' itself to the user.

string GetPageTitle(int lcid);

The GetPageTitle method is used by AD FS to query to Authentication Adapter for the title of the authentication page. It passes an integer called lcid that represents the browser language setting of the user. For example, value '1033' is for English - United States. To learn more about these lcid values, or Locale ID's, please check out this page; https://msdn.microsoft.com/en-us/goglobal/bb964664.aspx By passing this lcid value, AD FS allows you to create a user interface in multiple languages. (Our example will only support 1033; English - United States.) This GetPageTitle string will actually go into the <title> element of the logon page.

The method returns a string; the title of the page.

The IAdapterPresentationForm Methods and Properties

The IAdapterPresentationForm is very much like the IAdapterPresentation, but this interface let's you define what and how you want to ask the user for additional authentication. In our example, we want the user to input a PIN in the sign-in page.

string GetFormHtml(int lcid);

The GetFormHtml is used to represent the HTML code that is inserted in the AD FS sing-in page. The lcid code passed allows you to localize the page.

The method should return the string, plain old HTML, that represents the code for whatever you want to do in the sing-in page. One important thing to note here though; It could very well be the case that a user that is authentication, is first hitting one server in the farm, enters the PIN and submits the PIN. This postback to the server could hit another server in the farm. Now the context of the logon would be lost. AD FS cannot rely on session state or anything like that. We need to 'manually' transfer the context of the logon together with the proof data that the customer provided. So if we need to use a form, make sure to include a hidden input element, that contains the context of the request. This is best done through a constructor that can be called from the BeginAuthentication and TryEndAuthentication methods in the AuthenticationAdapter implementation. Also, if we have multiple Authentication Providers enables simultaneously for a Relying Party, we need to identify our own provider. This is done through a hidden form field called authMethod. So, whatever you do here, make sure you include at least two form fields; context and authMethod.

string GetFormPreRenderHtml(int lcid);

This method is used to allow the Authentication Adapter to insert any special tags etc. in the <head> element of the AD FS sign-in page. Again, with the same lcid value to localize your pages.

The method should return the HTML code you want to insert in the HTML <head> element of AD FS the sign-in page.

Implementing the required Methods

Now we have seen all the methods that we need to implement. Although this might seem a little intimidating at first, actually, you don't need to do a lot of work.

So let's start by implementing our AdapterPresentation class. (Only three simple methods to implement!) Now that we've seen what these methods do, combined with the fact that we're only implementing the Authentication Adapter in English, we need to replace the throw new NotImplementedException(); lines with proper code. Also, we want to create some constructors to be able to create new instances of the AdapterPresentation class that are useful for our specific use. We will introduce two private properties as you will see.

Here is the code I created to implement the AdapterPresentation class. You can replace the existing methods in your code with the ones here;

     class AdapterPresentation : IAdapterPresentation, IAdapterPresentationForm
    {
        private string message;
        private bool isPermanentFailure;
        public string GetPageTitle(int lcid)
        {
            return "My Authentication Provider";
        }

        public string GetFormHtml(int lcid)
        {
            string result = "";
            if (!String.IsNullOrEmpty(this.message))
            {
                result += "<p>" + message + "</p>";
            }
            if (!this.isPermanentFailure)
            {
                result += "<form method=\"post\" id=\"loginForm\" autocomplete=\"off\">";
                result += "PIN: <input id=\"pin\" name=\"pin\" type=\"password\" />";
                result += "<input id=\"context\" type=\"hidden\" name=\"Context\" value=\"%Context%\"/>";
                result += "<input id=\"authMethod\" type=\"hidden\" name=\"AuthMethod\" value=\"%AuthMethod%\"/>";
                result += "<input id=\"continueButton\" type=\"submit\" name=\"Continue\" value=\"Continue\" />";
                result += "</form>";
            }
            return result;
        }

        public string GetFormPreRenderHtml(int lcid)
        {
            return string.Empty;
        }
        public AdapterPresentation()
        {
            this.message = string.Empty;
            this.isPermanentFailure = false;
        }
        public AdapterPresentation(string message, bool isPermanentFailure)
        {
            this.message = message;
            this.isPermanentFailure = isPermanentFailure;
        }
    }

Here is the code I created to implement the AuthenticationAdapterMetadata class. You can replace the existing methods in your code with the ones here;

     class AuthenticationAdapterMetadata : IAuthenticationAdapterMetadata
    {
        public string AdminName
        {
            get { return "My Authentication Provider"; }
        }

        public string[] AuthenticationMethods
        {
            get { return new string[] { "https://schemas.microsoft.com/ws/2012/12/authmethod/otp" }; }
        }

        public int[] AvailableLcids
        {
            get { return new int[] { 1033 }; }
        }

        public Dictionary<int, string> Descriptions
        {
            get
            {
                Dictionary<int, string> result = new Dictionary<int, string>();
                result.Add(1033, "My Authentication Provider");
                return result;
            }
        }

        public Dictionary<int, string> FriendlyNames
        {
            get
            {
                Dictionary<int, string> result = new Dictionary<int, string>();
                result.Add(1033, "My Authentication Provider");
                return result;
            }
        }

        public string[] IdentityClaims
        {
            get { return new string[] { "https://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn" }; }
        }

        public bool RequiresIdentity
        {
            get { return true; }
        }
        public AuthenticationAdapterMetadata()
        {

        }
    }

Here is the code I created to implement the AuthenticationAdapter class. You can replace the existing methods in your code with the ones here;

     class AuthenticationAdapter : IAuthenticationAdapter
    {
        public IAdapterPresentation BeginAuthentication(System.Security.Claims.Claim identityClaim, System.Net.HttpListenerRequest request, IAuthenticationContext context)
        {
            return new AdapterPresentation();
        }

        public bool IsAvailableForUser(System.Security.Claims.Claim identityClaim, IAuthenticationContext context)
        {
            return true;
        }

        public IAuthenticationAdapterMetadata Metadata
        {
            get { return new AuthenticationAdapterMetadata(); }
        }

        public void OnAuthenticationPipelineLoad(IAuthenticationMethodConfigData configData)
        {
           
        }

        public void OnAuthenticationPipelineUnload()
        {
          
        }

        public IAdapterPresentation OnError(System.Net.HttpListenerRequest request, ExternalAuthenticationException ex)
        {
            return new AdapterPresentation(ex.Message, true);
        }

        public IAdapterPresentation TryEndAuthentication(IAuthenticationContext context, IProofData proofData, System.Net.HttpListenerRequest request, out System.Security.Claims.Claim[] claims)
        {
            claims = null;
            IAdapterPresentation result = null;
            string pin = proofData.Properties["pin"].ToString();
            if (pin == "12345")
            {
                System.Security.Claims.Claim claim = new System.Security.Claims.Claim("https://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod", "https://schemas.microsoft.com/ws/2012/12/authmethod/otp");
                claims = new System.Security.Claims.Claim[] { claim };
            }
            else
            {
                result = new AdapterPresentation("Authentication failed.", false);
            }
            return result;
        }
    }

Now that we have implemented all required methods, we can actually compile the project and start using it on our AD FS server(s).

Compiling the Authentication Provider

Now that we have implemented all the classes and methods required by AD FS, we can build our Authentication Provider. But before we do so, there is one more thing I need to point out. The output of this project, a DLL, will have to be installed into the Global Assembly Cache, or GAC, of the AD FS server. Now, how we do this is something that I will come back to, but in order to place a DLL in the GAC, the DLL needs to be signed. So this is something that we need to do in Visual Studio first.

Right-click the name of our project in the Solution Explorer on the right of the screen. From the menu, click Properties.

From the Properties page, click Signing, on the left, and then check the checkbox marked Sign the assembly. Then, from the Choose a strong name key file: pull down menu, select <New...> .

In the Create Strong Name Key dialog, type a name for the key file and a password that you can remember.

Then, click OK.

The newly created key file should now be present in your solution.

Now we are ready to build our project Authentication Provider!

Click Build and then Build Solution (or simply hit F6) to build the solution:

Check the Output window, at the bottom of the screen, to see if any errors occurred:

Our Authentication Provider is now ready for use and is in the \bin\Debug\ folder of our project folder. In my case, that's D:\Visual Studio Projects\MyAuthenticationProvider\MyAuthenticationProvider\bin\Debug\MyAuthenticationProvider.dll

First, since we need to register the DLL in AD FS by using the Register-AdfsAuthenticationProvider PowerShell command on the AD FS server, we need to get the Public Key Token. This public key token is 'created' when we created the key and signed the DLL using that key. To learn what the Public Key Token for the DLL is, you can use the SN.exe (More information: https://msdn.microsoft.com/en-us/library/k5b5tt23(v=vs.110).aspx) Start the Visual Studio x64 Win64 Command Prompt, and run the SN command with the -T parameter and the location of the file;

SN -T "D:\Visual Studio Projects\MyAuthenticationProvider\MyAuthenticationProvider\bin\Debug\MyAuthenticationProvider.dll"

For me, the Public Key Token is: 91f62883da29f7cd

This file must now be copied over to all the AD FS servers in the farm. I'll create a new directory, called MyAuthenticationProvider in C:\ and put the file there. So my DLL is at C:\MyAuthenticationProvider\MyAuthenticationProvider.dll

After you have copied the file, you need to add it to the Global Assembly Cache of the AD FS server. Our best practice would be that you create a proper installer for your project, and use the installer to add the file to the GAC. Another solution is to use Gacutil.exe. (More information on Gacutil.exe: https://msdn.microsoft.com/en-us/library/ex0ss12c(v=vs.110).aspx) This tool should be on your on development machine, but won't be present on the AD FS server. It's part of the Windows SDK and we don't want to install the Windows SDK on our AD FS server(s), so we're using method 3; PowerShell.

Here is the PowerShell to add our DLL to the GAC of the server. (This is run on the AD FS server, where the DLL now resides.)

Set-location "C:\MyAuthenticationProvider"
[System.Reflection.Assembly]::Load("System.EnterpriseServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")
$publish = New-Object System.EnterpriseServices.Internal.Publish
$publish.GacInstall("C:\MyAuthenticationProvider\MyAuthenticationProvider.dll")

Now that the DLL is in the GAC of the server, we can, finally, register the Authentication Provider in our AD FS server, again, by using PowerShell. Please note; we have to copy the file to each server, and add it to the GAC on each server, but we have to register it only once!

$typeName = "MyAuthenticationProvider.AuthenticationAdapter, MyAuthenticationProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken=91f62883da29f7cd"
Register-AdfsAuthenticationProvider -TypeName $typeName -Name "MyAuthenticationProvider" -Verbose

The $typeName here contains a special string. The first part, MyAuthenticationProvider.AuthenticationAdapter, is the namespace and the class name in our project;

namespace MyAuthenticationProvider
{
    class AuthenticationAdapter : IAuthenticationAdapter
    {
 ....

Then, after a comma, the name of the DLL itself (without .DLL), then the version of the file (which we can set in Visual Studio and see by checking the properties of the DLL in Explorer, the 'Culture' of the DLL (neutral) and the public key token we checked previously.

After running the Register-AdfsAuthenticationProvider PowerShell command, we should see a notification like this;

WARNING: PS0114: The authentication provider was successfully registered with the policy store. To enable this provider, you must restart the AD FS Windows Service on each server in the farm.

So, let's restart the AD FS service and we can start using the Authentication Provider! (And no, I'm not going to add screenshots or a howto on how to restart the AD FS service.)

After restarting the AD FS service, check the Eventlog (which is in the Event Viewer under Applications and Services Logs, AD FS, Admin.
Check if the service has successfully started (Event ID 100) and if the Authentication Provider is properly loaded (Event ID 106).

Now that we have confirmed that the Authentication Provider has been loaded, start the AD FS Management Console.

From the console, right-click Authentication Policies and select Edit Global Multi-Factor Authentication and select our Authentication Provider (My Authentication Provider) from the list under Select additional authentication methods.

Make sure you did not modify any other checkboxes; this will impact authentication for your existing relying parties!
Press OK to apply this setting.

Then, under Authentication Policies open Per Relying Party Trust. (we don't want to use our Authentication Provider for all relying parties on the AD FS server, only for a single test application.)
From that page, right-click the Relying Party you want to configure for Multi-Factor Authentication and click Edit Custom Multi-Factor Authentication...

We will now enable our Authentication Adapter for internal as well as external users (at least for testing), for registered and unregistered devices. So for now, we pretty much want MFA to happen whenever a users logs on. If we do not specify any users on which we want to apply MFA, MFA applies to all users.

Select all the checkboxes, and click OK.

By now, MFA should be enabled on this application. I'd say; test it! Navigate to the Relying Party application you just configured and, if required, logon to AD FS. You remember the PIN? It was hardcoded to 12345.

Entering the wrong PIN would prompt you for the PIN again. A correctly entered PIN would redirect you to the application you were trying to access.

Congratulations. Our hard work paid off! We have a working Authentication Provider for AD FS.

But using a hard-coded PIN doesn't make sense. So in the next chapters we will create a Windows Phone app that receives PIN codes from AD FS. Obviously, we have to come back to our Authentication Provider later to make some modifications to it.

Take me to Part 3 - Setting up Azure Mobile Services

Take me back to Part 1 - Introduction

Comments

  • Anonymous
    January 01, 2003
    @leblanc: You can load specific configuration information on a per AD FS server basis; so the same configuration for authentication requests on the same AD FS farm. You cannot use configuration information on a per-authentication request basis.
  • Anonymous
    January 01, 2003
    Can please let me know How to open ADFS Sign on page ? (which shown in last snap in this blog)
  • Anonymous
    January 01, 2003
    @leblanc @Thomas: You cannot provide your own Primary Authentication Provider. AD FS will always authenticate against Active Directory or use another Claims Provider.
  • Anonymous
    January 01, 2003
    @John: That is correct. At this point in time, you can only enable and disable Multi-Factor Authentication Providers on the Global level, and then select when to use these on the Relying Party level. Yet, using your own Multi-Factor Authentication Provider, you can try to modify the IsAvailableForUser method to either return True of False based on some additional information; like the Relying Party you can get from the Context.
  • Anonymous
    January 01, 2003
    awesome article. thanks for sharing.
  • Anonymous
    January 01, 2003
    @Sunil: This is the default forms-based sing-in page for AD FS on Windows Server 2012 R2. You will get there once you try to access any application that is using your AD FS server for authentication (a Relying Party).
  • Anonymous
    February 01, 2014
    In this series of five blog posts I want to show you how you can create your own Authentication Provider
  • Anonymous
    February 01, 2014
    In this series of five blog posts I want to show you how you can create your own Authentication Provider
  • Anonymous
    February 01, 2014
    In this series of five blog posts I want to show you how you can create your own Authentication Provider
  • Anonymous
    February 01, 2014
    In this series of five blog posts I want to show you how you can create your own Authentication Provider
  • Anonymous
    February 05, 2014
    Great article. I only wish there was a way to set the multi factor authentication method to use based on the relying party (not give the user the choice), but there does not seem to be a way to do this in the AD FS interface.
  • Anonymous
    February 10, 2014
    The comment has been removed
  • Anonymous
    February 10, 2014
    The comment has been removed
  • Anonymous
    February 18, 2014
    i followed these steps and have my custom multifactor authentication policy working. How can i create an adfs pluggin for "Primary" authentication method? Currently on the "Primary" tab - extranet has Forms Authentication which uses adfs as the backing store. I want a custom primary authentication method before it reaches my multifactor authentication policy. Do you have any steps to do that? Thanks for the help.
  • Anonymous
    February 18, 2014
    in regards to my first question about custom configuration - seems it's built in although haven't tested it yet: http://technet.microsoft.com/en-us/library/dn479355.aspx Import-AdfsAuthenticationProviderConfigurationData and in AuthenticationAdapter::OnAuthenticationPipelineLoad(IAuthenticationMethodConfigData configData)
  • Anonymous
    March 10, 2014
    In ADFS 2.0 i think the only way to authenticate was against Active Directory. Has this changed in ADFS 3.0 /2012 R2? Can we plugin our custom primary authentication?
  • Anonymous
    August 23, 2014
    Took me an hour to figure that you have to also add ", processorArchitecture=MSIL" to the $typeName...
  • Anonymous
    August 27, 2014
    We have a requirement to customize primary authentication in ADFS 3. Since the ls website is completely locked down, Will it be possible to customize primary authentication of ADFS 3 using AuthenticationAdapter?
  • Anonymous
    September 03, 2014
    Really nice guide, thank you!
    I'm working on making this into a Radius client but I'm missing the finishing touch, maybe you can point me in the right direction.

    First of all I'm wondering if you have a good way to let the administrator change some settings that the authentication provider uses. I'm thinking of things like IP address for Radius requests and shared secret. Can't really have those hard coded. Preferably some GUI solution.

    Second, it would be nice to have an installer, would you recommend using InstallShield Limited Edition for Visual Studio or something else?
  • Anonymous
    September 23, 2014
    Hi Tino,

    I believe there is a bug somewhere in the interface. No matter whats get specified (sammacountname/mobilephonenumber etc.) in

    ------string[] IdentityClaims { }

    It's just passing always the UPN towards the IdentityClaim object in

    ------public IAdapterPresentation BeginAuthentication(System.Security.Claims.Claim identityClaim, System.Net.HttpListenerRequest request, IAuthenticationContext context)

    Have you been apple to pass something else in the beginAuthentication method (e.g. a mobilenumber for SMS otp..?)

    Robin
    • Anonymous
      February 13, 2017
      Robin,Did you get a definitive answer to the problem with the IdentityClaims. Im trying to set it to emailaddress, and I can see on upn coming through in BeginAuthentication(). I dont really understand Keith's reply as I only have one item in my array. /// Returns an array indicating the type of claim that that the adapter uses to identify the user being authenticated. /// Note that although the property is an array, only the first element is currently used. /// MUST BE ONE OF THE FOLLOWING /// "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname" /// "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn" /// "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" /// "http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid" public string[] IdentityClaims { get { return new[] { "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" }; } }
      • Anonymous
        February 14, 2017
        Turns out that my plugin wasnt being re-registered as I was reusing that same powershell session. This meant that the previous instance of the assembly was already loaded into the powershell session and the registration uses that version to extract the metadata.
  • Anonymous
    September 23, 2014
    How to create a Custom Authentication Provider for Active Directory Federation Services on Windows Server 2012 R2 - Part 2 - The Cloud PFE - Site Home - TechNet Blogs
  • Anonymous
    September 29, 2014
    How to create a Custom Authentication Provider for Active Directory Federation Services on Windows Server 2012 R2 - Part 2 - The Cloud PFE - Site Home - TechNet Blogs
  • Anonymous
    September 29, 2014
    Agree with Robin above. Just ran into the same issue. I've supplied a string array with four claims and I only get UPN in BeginAuthentication. UPN isn't even the first one in my list. Anyone have an update or fix for that?
  • Anonymous
    September 29, 2014
    For anyone that cares, I found the answer here: http://msdn.microsoft.com/en-us/library/dn783423.aspx

    As it turns out it only uses the first one in the list WHEN YOU REGISTER THE PROVIDER IN ADFS.

    /// Returns an array indicating the type of claim that that the adapter uses to identify the user being authenticated.
    /// Note that although the property is an array, only the first element is currently used.
    /// MUST BE ONE OF THE FOLLOWING
    /// "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"
    /// "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"
    /// "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
    /// "http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid"
    public string[] IdentityClaims
    {
    get { return new[] { "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn" }; }
    }
  • Anonymous
    October 26, 2014
  • Anonymous
    October 26, 2014
    The comment has been removed
    • Anonymous
      March 13, 2018
      Why not writing own code
  • Anonymous
    December 23, 2014
    Hello I have done all the Steps..I got error While regestering Provider in adfs is:external authentication method MyAuthenticatioProvider Could not be loaded.could not load type MyauthenticationProvider.AuthenticationProvider from assembly MyAuthenticationProvider...please help me what is wrong...

    • Anonymous
      September 11, 2016
      The comment has been removed
  • Anonymous
    September 11, 2015
    Adding Multi-Factor Authentication to ADFS
  • Anonymous
    October 08, 2015
    after running the following command $typeName = "MyAuthenticationProvider.AuthenticationAdapter, MyAuthenticationProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken=xxxxxxxxx, processorArchitecture=MSIL"
    Register-AdfsAuthenticationProvider -TypeName $typeName -Name "MyAuthenticationProvider" -Verbose

    I receive the following error:
    The external authentication method xxxxAuthenticationProvider could not be loaded. Exception has been thrown by the target of an invocation.

    Any ideas?
  • Anonymous
    November 19, 2015
    I had the same problem,

    The external authentication method AuthenticationProvider could not be loaded. Exception has been thrown by the target of an invocation.

    when I run powershell as Administrator it worked for me.
  • Anonymous
    December 03, 2015
    Is there a way to add a samlp:AuthnRequest RequestedAuthnContext - as we could via the SignOnRequestParameters.RequestedAuthenticationContext in ADFS 2.0...Any help is appreciated...

    sorry probably not related to the post but any assistance or direction would help

    Thanks
  • Anonymous
    April 16, 2016
    Can you provide any tips for debugging via logging or some other way? What is the process by which after re-building the Provider, you can re-register the new provider with ADFS. Do you need to remove it from the GAC, Unregister from ADFS, stop ADFS, start ADFS, install it to GAC, Register it to ADFS, stop ADFS, start ADFS? So far that is the only way I can get changes to be reflected.
  • Anonymous
    August 21, 2016
    Hi,Is the format for "public string[] IdentityClaims" (array) going to be changed in Server 2016 for ADFS? Is it still going to be an array or a variable? (Was referring https://msdn.microsoft.com/en-us/library/dn783423.aspx) In other words, on what factors does "public string[] IdentityClaims" syntax depend up on which makes its declaration as an Array?
  • Anonymous
    August 30, 2016
    Can I use this way to implement Google Recaptcha ?!
  • Anonymous
    September 08, 2016
    Is there a way to check if a user is approved before the form is shown to the user, and show the form ONLY if he is not approved?
  • Anonymous
    November 17, 2016
    I followed these steps and have my custom multi-factor authentication policy working. I want to refer third party dlls in multi-factor authentication. System encounters error during federation passive request as it is unable to load third party dlls referred. Could you please let me know where to put third party dlls while registering multi-factor authentication dll. Any help is appreciated…
    • Anonymous
      August 28, 2017
      Typically, these need to go into the Global Assembly Cache (GAC).
  • Anonymous
    March 06, 2017
    Hi,I have enabled Azure MFA in ADFS 2016. This works great when using Active Directory as a Claims provider trust. But not when using other CPT. You know a way to user Azure MFA also on other CPTs?
    • Anonymous
      August 28, 2017
      This is currently not possible. AD FS does not authenticate the users that are authenticated at another Claims Provider. Hence, this other Claims Provider has to do the MFA for that user. You can only check whether the user did MFA or not.
  • Anonymous
    April 23, 2017
    We have configured SMS and RSA for MFA, once user logged in with user id/PWD and ADFS will list SMS and RSA as an option for 2nd-factor authentication and user can select any of the MFA auth.Querry:How can we list the 2nd-factor authentication based on the user role Eg: User part of say "SMS" group user will be redirected to SMS auth. instead of listing all available MFA option.
  • Anonymous
    June 06, 2017
    I installed ADFS4 on windows server 2016. everything works fine but under adfs management console ... Device Registration service is not available under Relying party Trusts folder. While this was automatically available in Windows server 2012.Please let me know the reason for it as due to this my custom page doesnot appear after AD authentication, but when i add a relying party trust manually, then my custom authentication page is appearing. Any idea?
  • Anonymous
    February 16, 2018
    Great article! Is there a way of getting a result from the page into a inbound claim in AD FS?For example, letting the user choose a context for this authn and then transport the answer into AD FS so that the 'right' claims can be applied? Not using this for authn but rather for letting user select context...