A quick summary – we are working on a custom STS which the ADFS will federate with. In our previous entry we have created a trust relationship between the ADFS and our custom STS so users are able to see the HomeRealmDiscovery page in the ADFS and pick up a correct identity provider – the ADFS itself or the custom STS.
In our very last entry we are going to provide a WSTrustFeb2005 endpoint on our custom STS so that WCF signin requests could be created directly from the ADFS to the custom STS without the HRD page. In fact, users will see the ADFS login page with username/password text fields and we will modify the logic behind the page so that the pair username/password will be first validated against our custom STS and then against the Active Directory without user being aware of that. This will complete our quest for customizing ADFS sign-in web pages.
A WSTrustFeb2005 Endpoint
Go to our custom STS project, and create a *.svc service. Since I like to follow conventions, I created it under /services/trust/2005/UserName.svc (since we are going to perform username/password validation).
The service itself is rather straightforward as it uses a built-in factory class. It means that the *.svc doesn’t need any code behind and the sole content of the *.svc file is:
Note that the Service attribute points to the security token service configuration class which we have created long, long ago somewhere at the begininng of our quest, when we have built a custom STS. In other words, the active WCF service uses the same STS infrastructure as the passive LoginForm/Default pages.
But we are not done yet. Go to the web.config file and create correct WCF sections:
Note that we are using a built-in contract interface (IWSTrustFeb2005SyncContract) and we create an explicit ws2007HttpBinding binding with security mode set to TransportWithMessageCredential and clientCredentialType set to UserName. This is the only correct combination and what’s important is that it works only when the service is exposed on the secure channel (SSL).
This is enough to test the service, just point your browser to the https://customsts.yourdomain.com/services/trust/2005/UserName.svc and check if it displays correct information page.
A custom token handler
By default the service we have just built will try to accept tokens for Windows users. However, we’d like to have a custom validation logic so for example we could validate users agains the SQL database.
For this, we need a custom token handler which could examine SAML request tokens, extract usernames/passwords from there and create claims accordingly.
This is surprizingly straightforward:
Note that my simple token handler just checks whether the password matches the length of the username (so foo/3 or foobar/6 are valid credentials) but this is the exact place you can plug any validation logic.
We only need to inform WIF to use this custom token handler instead of the default one. We do this in web.config in a proper section:
This new token handler will not interfere with the passive authentication scenario, where your browser asks for the Default.aspx page of the STS. Instead, it is only used during the active authentication scenario, where WCF requests are handled by the STS.
Passing through claims
The custom token handler we have created will now works correctly, however our WCF service still points to our CustomSecurityTokenService class where we create claims manually (the GetOutputClaimsIdentity method).
Because our custom token handler now creates its own claims, we have to modify the STS logic so that WCF claims could be passed through.
Modifying the ADFS login page
The ADFS login page can now be modified to use our WCF service. Go to ADFS installation, find the FormsSignIn.aspx.cs file and modify it to:
Note that instead of using the built-in SignIn( string UserName, string Password ) method (which would check the credentials against the active directory) we are providing a custom sign in method which creates a WCF request to the WSTrustFeb2005 service in behalf of http://fs.adfs.pl/adfs/services/trust (which is the ID of the ADFS, not it’s address! If you pass the address, the ADFS will refuse to accept the token because the token would not be issued for the ADFS).
When we get the token back from the service, we pass it to another built-in method which this time accepts the token instead of the username/password pair (SignIn( token )). Note that this would not work without the explicit trust relationship in the ADFS to our custom STS we have created in the previous blog entry. This time the ADFS would complain that the claims provider is not trusted.
Also note that this is the most difficult step in our tutorial as we are finally putting all blocks together. My advice is to test the SignInWithTokenFromOtherSTS method separately, from within a custom console application and when it finally works (you get the token and not the exception from the WCF service) you can move on to testing this code from within the ADFS.
I’ve spent at least a day at this point, still getting exceptions from the WCF service and when it finally started to work (I got tokens from the WCF service) making it work with the ADFS was easy.
Modifying the ADFS HRD page
The only remaining issue is that because of our trust relationship between the ADFS and our custom STS, the ADFS still shows the HomeRealmDiscovery page! This is rather unfortunate because we’d like it to show the default page using our enhanced logic. And the passive login using the loginpage of our custom STS should be now disabled.
To do so, open the HomeRealmDiscovery.aspx.cs in the ADFS installation and modify the Page_Init so that it immediately picks the home realm:
Our quest is complete. The default ADFS login page can now accept any validation logic, you can for example validate users against a database or accept the email/password pair against the AD.
If you need the complete source code of this tutorial, please contact me directly.