Access to an ASP.NET website via multiple authentications – revisited (v4.0)
Hi all
A couple of years ago I posted an article entitled 'Access to an ASP.NET website via multiple authentications' which I advise you to read if you want to make full sense of this new post. To briefly summarise for those short of time though, consider the scenario where you have a website deployed in Azure and in respect of securing access to the site:
"I want authN method X in production but in preview I want authN method Y followed by authN method X"
Out of the box Windows Identity Foundation (WIF) allows federation with Identity Provider X but does not support multiple federated sign-in such as "Identity Provider Y followed by Identity Provider X".
In the article I alluded to the fact that code existed for the scenario but I did not include it in the post; for various reasons it was not code I could simply make available. However, I did set myself the task of rewriting the entire stack of code in a more generic way so that it could be picked up by developers who wished to protect their website using multiple federated sign-ins.
Roll forwards two years and purely due to a colleague asking a question that skirted around federation, I span up my development VM and, before I knew what I was doing, the code samples were finished!
The code samples have been uploaded here. The rest of this article is a description of what is included and how it works.
Firstly though I have a confession to make. I envisioned that this solution would be possible using WIF 4.5 and the System.IdentityModel namespace but did not 100% confirm this to be the case. The scenario I knew that worked at the time was for WIF 4.0 and the Microsoft.IdentityModel namespace. i.e. – before WIF was baked into the .NET framework. So, the sample provided is for WIF 4.0 but I make a vow here and now to also get this working for WIF 4.5 in the very near future, if possible. Seeing as I have waited two years to get this far you can be sure that I will (eventually) research this.
Methodology
The approach is completely configuration driven by making sure the correct WIF modules are present in the web.config file for your site but preceded by a custom module I have called BouncerModule40 (Bouncer because the module acts like a nightclub doorman):
<system.webServer> <modules> <add name="BouncerModule40" type="Bouncer.WebApp40.BouncerModule40"/> <add name="SessionAuthenticationModule" type="Microsoft.IdentityModel.Web.SessionAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> <add name="WSFederationAuthenticationModule" type="Microsoft.IdentityModel.Web.WSFederationAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> </modules> </system.webServer> |
The order of these modules is critical because we want the custom code to run before out of the box WIF gets its hands on the request.
Multiple federated sign ins are driven by including one or more named WIF Service elements, and a custom configuration section, as shown below:
Custom config:
<identityProvidersToCall> <identityProviders> <add serviceName="IDP0" callSequenceNumber="0" /> <add serviceName="IDP1" callSequenceNumber="1" /> </identityProviders> </identityProvidersToCall> |
WIF config:
<microsoft.identityModel> <service name="IDP0"> <audienceUris> <add value="**RELYINGPARTYURI**" /> </audienceUris> <certificateValidation certificateValidationMode="None" /> <federatedAuthentication> <wsFederation passiveRedirectEnabled="true" issuer="**IDENTITYPROVIDERENDPOINT**" realm="**RELYINGPARTYURI**" requireHttps="true" /> <cookieHandler requireSsl="true" /> </federatedAuthentication> <issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"> <trustedIssuers> <add thumbprint="**IDPPUBLICKEYCERTIFICATETHUMBPRINT**" name="**IDENTITYPROVIDERFRIENDLYNAME**" /> </trustedIssuers> </issuerNameRegistry> </service> <service name="IDP1"> <audienceUris> <add value="**RELYINGPARTYURI**" /> </audienceUris> <certificateValidation certificateValidationMode="None" /> <federatedAuthentication> <wsFederation passiveRedirectEnabled="true" issuer="**IDENTITYPROVIDERENDPOINT**" realm="**RELYINGPARTYURI**" requireHttps="true" /> <cookieHandler requireSsl="true" /> </federatedAuthentication> <issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"> <trustedIssuers> <add thumbprint="**IDPPUBLICKEYCERTIFICATETHUMBPRINT**" name="**IDENTITYPROVIDERFRIENDLYNAME**" /> </trustedIssuers> </issuerNameRegistry> </service> <service> <audienceUris> <add value="**RELYINGPARTYURI**" /> </audienceUris> <certificateValidation certificateValidationMode="None" /> <federatedAuthentication> <wsFederation passiveRedirectEnabled="true" issuer="**IDENTITYPROVIDERENDPOINT**" realm="**RELYINGPARTYURI**" requireHttps="true" /> <cookieHandler requireSsl="true" /> </federatedAuthentication> <issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"> <trustedIssuers> <add thumbprint="**IDPPUBLICKEYCERTIFICATETHUMBPRINT**" name="**IDENTITYPROVIDERFRIENDLYNAME**" /> </trustedIssuers> </issuerNameRegistry> </service> </microsoft.identityModel> |
(Apologies for the place markers, I didn't want to give away the details of my Azure AD tenant)
The bouncer module will deal with each Service element in the order indicated by the custom identityProvidersToCall element. In this case, IDP0 will be used for authentication, followed by IDP1. After the bespoke authentications have succeeded, control is handed back to WIF proper and the unnamed Service element (at the bottom of the snippet above) is processed. This final Service element would ordinarily be the intended production sign-in for the site and ultimately produce the session cookie used to authorise all future access to the website. The bespoke authentications, after having been negotiated, are necessarily thrown away.
Conclusion
The scenarios that warrant the existence of this code are fairly niche but I have actually been asked where the code is a few times over the past two years. Whilst to properly exercise what I have given you will require the particulars of your own domain, I hope this code can be your starter for 10.
Good luck and no, I haven't forgotten my promise to try and make this work for WIF 4.5.
Written by Bradley Cotier