How to implement SSO for ASP.NET MVC application with ADFS

The purpose of this blog post is to give you an overview of our experiences which we gathered some time ago when we implemented an SSO for a custom ASP.NET MVC application.

Background

This MVC application is hosted in an Azure VM (Windows Server 2012 R2) so the web server platform is IIS 8.5. There is a virtual network setup in this Azure IaaS environment so there are also Azure IaaS -hosted (private) domain controllers and domain where the application servers are joined. This means that application user accounts are hosted in this private domain. However, the actual end users of this application are in external, on-premise domain which is not extended to this private domain. And that on-premise domain cannot be extended to the private domain because of certain architectural reasons.

The solution into this SSO requirement is an AD-federation between the on-premise domain and the private domain.

Identity federation topology

Naturally the user identity needs to be transferred somehow from on-premise domain into the private domain without user intervention. ADFS provides proper features into this scenario. The requirement is that there is also ADFS provisioned in the on-premise domain. The following high level picture describes the authentication scenario.

1. Jose is authenticated to domain.local domain in his computer and he makes a browser request to application.domain.com

  • Application is configured to trust to the domain.com issuer (federation provider)

2. Application does not know where the request is coming from. It redirects the request to the domain.com issuer (federation provider)

3. In the Jose’s request, there is a query string parameter WHR which defines Jose’s home realm (which is domain.local issuer). Domain.com issuer utilizes this home realm information and redirects the request to Jose’s home realm issuer.

4. Domain.local issuer verifies Jose’s authentication, issues a token (with proper claims) and redirects the request back to the domain.com issuer

5. Domain.com issuer (federation provider) validates the token coming from the domain.local issuer, makes the claim transformation if needed and issues a new token which contains claims that application needs

6. Domain.com issuer redirects the request to the application

7. Application processes (using WIF) the new token which the application trusts. This token includes claims that verify who the user is and Jose is granted an access to application without a need to show the login form.

ADFS configuration

Domain.local

In the on-premise domain ADFS, we have the following setup:

  • Claims provider trust (local AD): proper claim rules for the claims the application requires

Domain.com

In the private ADFS, we have the following setup:

  • Claims provider trust (to the domain.local ADFS): this trust relationship is needed so that in general the identity federation is possible
    • Within this trust in the ADFS, proper claim rules are needed so that the claims that are sent from the domain.local ADFS are properly handled
  • Relying party trust (to the application itself): this trust relationship is needed to manage the claims received from the domain.local ADFS and passed through or transformed into the format that the application expects

Application claim handling

In the MVC application itself, the configuration part is very straightforward. Below are web.config and application source code examples which are needed to enable the federation and handling the claims.

Web.config

This is needed for Windows Identity Foundation:

<configSections>
<section name=”system.identityModel” type=”System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=abc123″ />
<section name=”system.identityModel.services” type=”System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=cbz123″ />
</configSections>

The following are the configuration rows which are needed in the <appSettings>:

<add key=”ida:FederationMetadataLocation” value=”https://sts.domain.com/FederationMetadata/2007-06/FederationMetadata.xml” />
<add key=”ida:Issuer” value=”https://sts.domain.com/adfs/ls/” />
<add key=”ida:ProviderSelection” value=”productionSTS” />

Location element is needed to enable access to the application’s federation metadata:

<location path=”FederationMetadata”>
<system.web>
<authorization>
<allow users=”*” />
</authorization>
</system.web>
</location>

This is needed to disable the native authentication and enable the WIF authentication:

<authorization>
<deny users=”?” />
</authorization>
<authentication mode=”None” />

Finally, this is needed for the Windows Identity Foundation configuration:

<system.identityModel>
<identityConfiguration>
<audienceUris>
<add value=”https://application.domain.com/” />
</audienceUris>
<issuerNameRegistry type=”System.IdentityModel.Tokens.ValidatingIssuerNameRegistry, System.IdentityModel.Tokens.ValidatingIssuerNameRegistry”>
<authority name=”http://sts.domain.com/adfs/services/trust”>
<keys>
<add thumbprint=”abc123ccc” />
</keys>
<validIssuers>
<add name=”http://sts.domain.com/adfs/services/trust” />
</validIssuers>
</authority>
</issuerNameRegistry>
<certificateValidation certificateValidationMode=”None” />
<securityTokenHandlers>
<remove type=”System.IdentityModel.Tokens.SessionSecurityTokenHandler, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=abc123″ />

<add type=”System.IdentityModel.Services.Tokens.MachineKeySessionSecurityTokenHandler, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=bbc123″>

<sessionTokenRequirement lifetime=”2:00:00″ />
</add>

</securityTokenHandlers>
</identityConfiguration>
</system.identityModel>
<system.identityModel.services>
<federationConfiguration>
<wsFederation passiveRedirectEnabled=”true” issuer=”https://sts.domain.com/adfs/ls/” realm=”https://application.domain.com/” requireHttps=”false” />
<cookieHandler requireSsl=”false” persistentSessionLifetime=”2:00:00″ />
</federationConfiguration>
</system.identityModel.services>

Claim handling

The following is a piece of C# code from the application itself. This is just for sample purposes to show how easy it is on the application’s side to handle the claims:

var Identity = (ClaimsIdentity)Thread.CurrentPrincipal.Identity;

if (Identity.IsAuthenticated)

{

            foreach (System.Security.Claims.Claim claim in Identity.Claims)

            {

                        if (StringComparer.Ordinal.Equals(System.Security.Claims.ClaimTypes.Upn, claim.Type))

                        {

                                    UserLoginID = claim.Value;

                        }

                        else if (claim.Type.Contains(“samaccountname”))

                        {

                                    SAMAccountName = claim.Value;

                        }

            }

}

Summary

ADFS provides clever features which can be utilized to offer SSO experience for end users even in scenarios where local domain cannot be extended to the domain where application resides. As per our experience, configuring the ADFS took more time than actually getting the application itself to be claims-aware.


More posts: