A couple of days ago, I started implementing a rather interesting piece of code. It was late into the night, and I was not feeling like doing my regular work and neither was I sleepy.
We had recently integrated Claims Authentication support in one of our enterprise applications. And it soon-after went into evaluation for a prospective clients. We had advised them to use ADFS (Active Directory Federation Services) to create the authentication bridge between our application and the underlying authentication repository.
On the second day the application went into evaluation, the client came back telling us that Claims authentication was not working for the application against their Active Directory Services. Surprised, we started looking into their setup and immediately realised that they were using AD LDS (Active Directory Lightweight Directory Services) instead of its full-blown Active Directory counterpart.
The client was using ADFS 2.0 and Microsoft had pulled out LDS authentication support from ADFS 2 (whereas ADFS 1.1 supported authenticating against AD LDS too). We decided to create a custom Security Token Service (STS) to authenticate against LDS and use the same as a "Claims Provider Trust" for ADFS 2.0 (which basically means ADFS allows you to specify a custom STS for authentication, and it uses the claims returned by your custom STS to create SAML tokens and return them back to your application that uses Claims Authentication).
The Windows Identity Foundation (WIF) SDK (which can be downloaded from here) provides a WebSite template for creating a custom STS site. Creating a STS using that template is a pretty straight-forward task and I was able to quickly assemble the code for authenticating against a LDS which you can find attached with this blog post.
The attached code is a Visual Studio 2010 Web Application Project that can authenticate against a LDS. I thought of sharing it here because of some additional bells and whistles it provides, some gotchas I faced when authenticating with LDS and the ability to use this project as a better template for creating a custom STS than the WIF SDK template.
Here are some of the important points for the attached code you should be aware of:
- web.config contains a custom <lds> configSection which is used to manage all aspects of the custom STS. Here's a sample <lds> section from web.config:
<lds certificateIssuerName="AD LDS STS" signingCertificateSubject="CN=STSTestCert" signingCertificateThumbprint="92F67A99663BB2052058EC4F905A64B80964E6D7" signingCertificateStoreName="My" signingCertificateStoreLocation="LocalMachine"> <ldap serverName="localhost:389" container="CN=LDAP,DC=Test,DC=COM"> <contextOptions> <add option="SimpleBind" /> </contextOptions> </ldap> <relyingParties> <add name="MyApp" baseUrl="http://localhost/myapp" encryptingCertificateSubjectName="" encryptingCertificateStoreName="My" encryptingCertificateStoreLocation="LocalMachine" /> </relyingParties> </lds>
I will now explain the important aspects from this sample configuration:
- certificateIssuerName is any string you would want to appear as the issuer in the output SAML assertions.
- signingCertificateSubject is the subject name of the certificate that is used to sign the output SAML tokens. The certificate is searched based on signingCertificateStoreName and signingCertificateStoreLocation values as discussed below.
- signingCertificateThumbprint is the thumbprint for the certificate to use for signing. This helps distinguish multiple certificates with the same subject name but different thumbprints in a certificate store. The thumbprint should be all-caps without any spaces between characters.
- signingCertificateStoreName is the store name where the certificate having the specified signingCertificateSubject should be searched for. Valid values are from the System.Security.Cryptography.X509Certificates.StoreName enum which include:
AddressBook, AuthRoot, CertificateAuthority, Disallowed, My, Root, TrustedPeople, TrustedPublisher
In most of the cases, your certificate would be in My or TrustedPeople store.
- signingCertificateStoreLocation specifies the location of the store as a System.Security.Cryptography.X509Certificates.StoreLocation enum value.
The only 2 valid values for this option are CurrentUser and LocalMachine.
The combination of signingCertificateSubject, signingCertificateStoreName and signingCertificateStoreLocation give you all the flexibility you would need to locate a certificate with which to sign the output saml tokens.
- Next we have the <ldap> element under <lds> section. This defines the LDS specific settings.
- serverName is the name of the LDS server (including the port) against which credentials should be authenticated.
- container is the LDS container under which your users reside. All queries for identifying users are performed under this root.
- contextOptions is one or more values from the System.DirectoryServices.AccountManagement.ContextOptions enumeration. In most cases, the default of "SimpleBind" should suffice when authenticating against an AD LDS.
- Next we have the <relyingParties> section. This section defines the parties which are authorized to authenticate against this STS. You can define any number of parties here with the following options:
- name is any user-defined name to identify a relying party. Must be unique for all parties.
- baseUrl is the allowed return url for this relying party. A request for authencation is entertained only if the return url specified for the authentication request starts with one of the baseUrls for any of the relying parties, else an InvalidRequestException is raised.
- encryptingCertificateSubjectName, encryptingCertificateThumbprint, encryptingCertificateStoreName, encryptingCertificateStoreLocation define the encrypting credentials for the tokens to use on a per relying party basis. This is used only if encryptingCertificateSubjectName is not empty for a relying party, else the output tokens are not encrypted for that relying party.
Next I will discuss some of the important points when authenticating against an AD LDS:
- The PrincipalContext class in System.DirectoryServices.AccountManagement namespace provides a convenient ValidateCredentials method to check user credentials against any AD store (including regular AD as well as AD LDS). You need to pass ContextType.ApplicationDirectory as the contextType to the constructor when instantiating this classs object to authenticate with LDS.
- After successful authentication, you would most probably need to fetch additional details about the user from LDS to return to the relying party (e.g. role, email etc). For this, the System.DirectoryServices.AccountManagement.Principal.FindByIdentity method comes in handy, which returns a Principal object. In usual cases, the Principal object would be of type UserPrincipal as users would be authenticating against your STS (Principal is an abstract class with UserPrincipal being one of its concrete implementations, others being AuthenticablePrincipal, ComputerPrincipal, GroupPrincipal).
You can extract Email, Role or any other desired claim you want to return from the STS from this Principal object.
- If you are unable to successfully authenticate against your LDS store, you would want to check the following points:
- Ensure a password has been set explicitly for the user by right-clicking it and choosing "Reset Password" from the context menu in your AD LDS Edit tool.
- Ensure the user has msDS-UserAccountDisabled set to false.
- Please check the user has a unique displayName or a unique userPrincipalName set-up and you are using one of these to login.
When authenticating a LDS user, you must use LDAP simple bind and use a name format supported by LDS.
LDS accepts the user's full DN, their displayName (if set and unique) and/or the userPrincipalName (if set and unique) as a "bindable" username, so start with the full DN of the user (e.g. CN=testuser,CN=Users,CN=LDAP,DC=Test,DC=COM) and see if that works. If so, you can try the other user name values as well. Note that you can put whatever you want for displayName or userPrincipalName in LDS There is no validation. Just make sure the values are unique across the LDS instance.
Finally I would elaborate why I believe this project can serve as a better template for a custom STS than the one provided in WIF SDK (with the disclaimer being that I myself used the SDK project template to create this project in the first place):
- Better configuration support in web.config using a custom ConfigurationSection instead of using the ubiquitous AppSettings.
- More flexibility to uniquely identify the Signing certificate to use for the tokens including specifying the certificate's subject, thumbprint, store name and location.
- Ability to control (through configuration) relying parties authorized to use this STS using their return url.
- Ability to control (through configuration) per-relying party encryption of output tokens with an optional encryption certificate.
I believe all these benefits over the SDK template accrue to you even if you use a different authentication store than LDS used by the attached project. All you would need to do in case your authentication store is different would be to:
- Change the LdapSetting class and corresponding web.config configuration for your authentication store.
- Update btnSubmit_Click method in Login.aspx.cs to authenticate against your credentials store.
- Update Global.asax.cs to assign the HttpContext's Principal fetched from your authentication store.
- Update GetOutputClaimsIdentity method in LdsSecurityTokenService class to add outputIdentity claims with the information extracted from the Principal assigned in point 3) above.
I think enough has been explained for this custom STS token service to enable you to use it against your LDS store (or any other custom store subject to changes outlined above). I would now allow you to download the code attached, and adapt and use it for your purpose.
You would need to extract the contents of attached zip, create a IIS Virtual Directory (IIS 6 and earlier) or IIS Application (IIS 7.x) pointing to the directory of extracted contents and update configuration in web.config based on above explanation to use this STS to authenticate against your LDS.