So by know you may have worked with ADFS 2.0 and SharePoint 2010. If not then you need to; they are match made in heaven for authentication and claims management. While working with a client recently I needed to use ADFS as the authentication mechanism but not use the out of the box approach. This also fell nicely into some demonstration stuff I am building for my presentations for SPTechCon Boston so I decided to "kill two birds with one stone" and work out the approach to use. I decided what would be ideal would be to a custom page within SharePoint that contains a login control that does not redirect to the ADFS server as is the default. Normally ADFS prompts with a windows login but if you modify the web.config order then you can get the form based login.
So as is always the case a quick search on Google to see what had been done before, there were a bunch of links that kind of did what I wanted but not 100%. Another requirement I wanted was to also allow the end user when asked to login, so either type credentials for the ADFS server, or click a link that would automatically log them in if they were internal domain users, not ADFS users. So my approach was the following:
- Configure Base ADFS as per usual
- Create custom Login Page – stored in "_layouts" directory
- Custom logic to allow login to ADFS without the redirect
- Allow internal domain users to click a link that would log them in using their network credentials
To achieve this for my environment I create a new Visual Studio project, a standard "Blank Solution" first and then an "Empty SharePoint Project". I then added a mapping to the "_layouts" directory.
This created the following structure within Visual Studio:
Next we need to add a custom application page; I called mine "LogonSelector.aspx".
Now it is time to add some code to the application page. Firstly let's change the UI elements of the page.
The above adds a regular ASP.NET login control with a custom hyperlink control underneath that will contain the link for internal users to login with their windows credentials. Now to add some code to the page; firstly we need to add references to the following components.
Now we need to change the class that the page inherits from:
We can now tie everything together with code events for the login control and our code for actually connecting to ADFS at the point of login. Firstly lets remove the page load event as this is not needed at this point, then we will add an "OnInit" event that will handle setting the text for the "InternalUserLoginLink" control, and set the method to be called by the sign in control on authentication.
Next we need to populate the "signInControl_Authenticate" code.
Nice and simple we are going to call accustom method called "ADFSLogin()". This code is what will actually make the call to ADFS and log the user in without the redirect to the ADFS server. This code requires an endpoint URL, applies to endpoint URL and the credentials that are being passed to it.
To break the code down a little, the first block is simply just checking that the endpoint has an appended "/" as this is needed by ADFS.
The Endpoint is actually going to be:
Next we are going to create a "WSTrustChannel" using this URL based on the endpoint URL.
We now need to pass our credentials to the channel.
Next we actually create the channel with the passed credentials.
Now we need to request the security token from the ADFS server by also passing in the "AppliesToEndpointUrl" which is the actual "Relying Party Identifier" within ADFS. In my ADFS Server is could be any of the following:
Now we have the channel, the ADFS server has responded and given us a request token now we iterate through the token, and actually set the SharePoint User Principle and create a session token to be used.
Once we have this then we can redirect the user within SharePoint, such as the default page unless there is a source query string in the URL.
There we have the core code, now we need some other methods to handle the internal users clicking the link and wanting to login using their normal windows credentials, not the ADFS domain credentials.
Firstly we add the actual click event code.
This check's to make sure the current Web Application is set to "Claims Based Authentication", retrieves the provider and calls another method called "RedirectToLoginPage passing in the provider. The last code block takes care of the redirect for the internal windows login.
Finally I decided to add the configuration of the Endpoint, Applies to Endpoint and Windows Login Text directly into the web.config as "AppSettings". SO I added code to retrieve these values:
You will notice that as I wanted the login to be used on multiple sites that I decided to dynamically generate the, applies to endpoint URL. All the code is done now, well almost. We just need to add a feature to activate for the web.config entries using feature activation code (you can find this on the web). And there we have, now deploy to your SharePoint server and we need to make a little change to the web application. Open up central administration and select the web application, then select the "Authentication Providers".
Select the zone that needs the new login; ensure that both NTLM and your ADFS server are selected as login mechanisms.
Set the "Sign in Page URL" to the following link or similar (depends on what you have.
Now we are able to test, but before we do there is a whole load of changes to make to the web.config for the SharePoint site. I am going to do these manually but in the real world you would want to automate this and NOT make manual changes. This is extensive changes so make a backup first, as this requires replacing everything between the "Microsoft.identitymodel" tags with the following:
The first thing to note is that you need to ensure that "saveBootstrapTokens" is enabled and that this configuration is in the web.config.
Next we need to add the "Audience Uris" that are allowed to be used with this login mechanism. If you have more than one SharePoint site then you need to add entries to them all, or dynamically generate these in your code.
Now we have the audience URI's configured we need to set the claim types that are available from the ADFS server. You can get these by creating a test web site in Visual Studio, adding an STS Reference which will update the web.config with the values you need.
Next we tell SharePoint that we are going to perform a federated authentication using the ADFS URL, a default realm (which we override in code).
IMPORTANT NOTE: Ensure the "PassiveRedirectEnabled" is set to "False" or you will get redirected to the standard ADFS login approach when clicking the internal login process
So far so good, now comes the painful part, we need to list out the SSL Certificate thumbprints for the following:
- ADFS Signing Certificate
- SharePoint STS Encryption Certificate
- SharePoint STS Certificate
- ADFS Web site Certificate
These can be found my access the MMC on the SharePoint server and finding the certificates to get the thumbprints. The SharePoint ones are found here:
To get the thumbprint open the certificate, select the details tab, and then select the "Thumbprint" field.
Now you need to be careful here, only select everything after the first space:
You need to then replace all the spaces, and the convert it to uppercase for the web.config. Replacing the spaces can be done manually in notepad with a find / replace of the spaces with nothing. The uppercase can be done either in word or I use a website for this: http://www.convertcase.net/
Now need to update the certificate thumbprints accordingly for all of them. The ADFS signing thumbprint can actually be found using either PowerShell or Central Administration in SharePoint.
Central Administration > Security > Manage Trust
The ADFS website certificate should be listed within the certificate store using the MMC in probably both the Personal > Certificates and Trusted Root Certification Authorities > Certificates. The same process applies, copy the thumbprints, remove the space, and switch to uppercase and update the web.config.
Finally we have the entire configuration completed, to test we should be able to access the site and when accessing something that needs authentication we should get redirected to the custom login page.
If you choose the "Sign In as a Different User", it should redirect back to the login page where you can choose either windows internal or type the ADFS credentials.
And there you have it, a bunch of code, and some configuration with a few components and we are done :-)
DISLAIMER: This is not production code as it has not been optimized or performance tested.