ADFS SSO SAML Windows Integrated authentication does not work

liorafar picture liorafar · Aug 17, 2015 · Viewed 15.4k times · Source

The project we are working now is Single Sign On via ADFS using SAML Token.
The basic rule this project should follow is the following:
1. Agent logs in to windows using his\her credentials.
2. Agent logs in to a web application (Relying Party)
3. The web application should redirect to the STS in the ADFS (Active Directory is the Identity Provider) and login using the credentials which the agent used in his\her windows authentication (seamless authentication).
4. Therefore the STS login page should not appear and the user should be authenticated
5. After that claims and security token should be received in order for us to authorize the agent

Actual Result:
1. Redirection is done for the first time and authentication is required again (IE authentication page and Firefox\Chrome authentication page).
enter image description here
enter image description here

  1. Can do authentication with all kind of domain users and not only the windows authenticated user.
  2. After first login to the sts login page, authentication is not required again. However we DON’T want second authentication. Only at windows logon (true only to IE).

Configured Environment:
1. Domain Controller + ADFS server 3.0 on the same machine (Win2k12R2)
2. Web Application machines (Win2k12 + IIS8.5)
3. The machines are on the same domain

ADFS Configuration:

enter image description here
enter image description here
enter image description here

Relying Party Configuration:

enter image description here enter image description here
enter image description here

IE Configuration:
enter image description here
enter image description here
enter image description here

Web App Configuration:
Authentication:

enter image description here ASP.Net Project:
Web Config File:

<?xml version="1.0" encoding="utf-8"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=169433
  -->
<configuration>
  <configSections>
    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
    <section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
    <section name="system.identityModel.services" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
  </configSections>
  <connectionStrings>
    <add name="DefaultConnection" providerName="System.Data.SqlClient" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=aspnet-TestApp-20150730141753;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\aspnet-TestApp-20150730141753.mdf" />
  </connectionStrings>
  <location path="FederationMetadata">
    <system.web>
      <authorization>
        <allow users="*" />
      </authorization>
    </system.web>
  </location>
  <system.web>
    <authorization>
      <deny users="?" />
    </authorization>
    <authentication mode="None" />
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
    <pages>
      <namespaces>
        <add namespace="System.Web.Optimization" />
      </namespaces>
    <controls><add assembly="Microsoft.AspNet.Web.Optimization.WebForms" namespace="Microsoft.AspNet.Web.Optimization.WebForms" tagPrefix="webopt" /></controls></pages>
    <!--<authentication mode="Forms">
      <forms loginUrl="~/Account/Login" timeout="2880" defaultUrl="~/" />
    </authentication>-->
    <profile defaultProvider="DefaultProfileProvider">
      <providers>
        <add name="DefaultProfileProvider" type="System.Web.Providers.DefaultProfileProvider, System.Web.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionStringName="DefaultConnection" applicationName="/" />
      </providers>
    </profile>
    <membership defaultProvider="DefaultMembershipProvider">
      <providers>
        <add name="DefaultMembershipProvider" type="System.Web.Providers.DefaultMembershipProvider, System.Web.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionStringName="DefaultConnection" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/" />
      </providers>
    </membership>
    <roleManager defaultProvider="DefaultRoleProvider">
      <providers>
        <add name="DefaultRoleProvider" type="System.Web.Providers.DefaultRoleProvider, System.Web.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionStringName="DefaultConnection" applicationName="/" />
      </providers>
    </roleManager>
    <!--
            If you are deploying to a cloud environment that has multiple web server instances,
            you should change session state mode from "InProc" to "Custom". In addition,
            change the connection string named "DefaultConnection" to connect to an instance
            of SQL Server (including SQL Azure and SQL  Compact) instead of to SQL Server Express.
      -->
    <sessionState mode="InProc" customProvider="DefaultSessionProvider">
      <providers>
        <add name="DefaultSessionProvider" type="System.Web.Providers.DefaultSessionStateProvider, System.Web.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionStringName="DefaultConnection" />
      </providers>
    </sessionState>

  </system.web>
  <system.webServer>
    <modules>
      <!--<add name="WSFederationAuthenticationModule" type="System.IdentityModel.Services.WSFederationAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />-->
      <add name="FixedWSFederationAuthenticationModule" type="TestApp.FixedWSFederationAuthenticationModule, TestApp" preCondition="managedHandler" />
      <add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />
    </modules>
  </system.webServer>
  <system.identityModel>
    <identityConfiguration saveBootstrapContext="true">
      <!-- The identity configuration. No name means default configuration which is always used for passive federation scenarios. see federationConfiguration element -->
      <audienceUris>
        <add value="https://ccsp12.pj12.loc/testapp" />
      </audienceUris>
      <issuerNameRegistry type="System.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
        <trustedIssuers>
          <add thumbprint="91992FCF8B03FF9BD98A259FE93B92620E9DD89A" name="http://sts.pj12.loc/adfs/services/trust" />
        </trustedIssuers>
      </issuerNameRegistry>
      <certificateValidation certificateValidationMode="None" />
    </identityConfiguration>
  </system.identityModel>
  <system.identityModel.services>
    <federationConfiguration> <!-- Configures the WSFederationAuthenticationModule (WSFAM) and the SessionAuthenticationModule (SAM) when using federated authentication through the WS-Federation protocol -->
      <cookieHandler requireSsl="false" />
      <!-- passiveRedirectEnabled true means that a relaying party (test app) instead of having its own login page, it will redirect to the sts issuer for authentication and the sts will reply to the relaying party -->
      <!-- Due to WSFederationAuthenticationModule bug, the relaying party address must be with '/' at the end -->
      <wsFederation passiveRedirectEnabled="true" issuer="https://sts.pj12.loc/adfs/ls/" realm="https://ccsp12.pj12.loc/testapp/" reply="https://ccsp12.pj12.loc/testapp/" requireHttps="true" />
    </federationConfiguration>
  </system.identityModel.services>
  <!--<runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="DotNetOpenAuth.Core" publicKeyToken="2780ccd10d57b246" />
        <bindingRedirect oldVersion="1.0.0.0-4.0.0.0" newVersion="4.1.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="DotNetOpenAuth.AspNet" publicKeyToken="2780ccd10d57b246" />
        <bindingRedirect oldVersion="1.0.0.0-4.0.0.0" newVersion="4.1.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>-->
  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
  </entityFramework>
</configuration>

C# Code:

namespace TestApp
{
    public partial class _Default : Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            // local variables
            string claimsTypes = string.Empty;
            string claimsValues = string.Empty;
            string claimsValueTypes = string.Empty;
            string claimsSubjectNames = string.Empty;
            string claimsIssuers = string.Empty;
            // initialize claims and identity
            ClaimsPrincipal claimsPrincipal = Thread.CurrentPrincipal as ClaimsPrincipal;
            ClaimsIdentity claimsIdentity = Thread.CurrentPrincipal.Identity as ClaimsIdentity;
            BootstrapContext bootstrapContext =
            ClaimsPrincipal.Current.Identities.First().BootstrapContext
                                                    as BootstrapContext;

            if (claimsPrincipal != null)
            {
                signedIn.Text = "You are signed in.";

                foreach (Claim claim in claimsPrincipal.Claims)
                {
                    claimsTypes = string.Concat(claimsTypes, "; ", claim.Type);
                    claimsValues = string.Concat(claimsValues, "; ", claim.Value);
                    claimsValueTypes = string.Concat(claimsValueTypes, "; ", claim.ValueType);
                    claimsSubjectNames = string.Concat(claimsSubjectNames, "; ", claim.Subject.Name);
                    claimsIssuers = string.Concat(claimsIssuers, "; ", claim.Issuer);
                }

                //claims principals
                claimType.Text = claimsTypes;
                claimValue.Text = claimsValues;
                claimValueType.Text = claimsValueTypes;
                claimSubjectName.Text = claimsSubjectNames;
                claimIssuer.Text = claimsIssuers;

                // ClaimsIdentity
                isUserAuthenticated.Text = claimsIdentity.IsAuthenticated.ToString();
                authenticationType.Text = claimsIdentity.AuthenticationType;
                claimName.Text = claimsIdentity.Name;

                // Token
                // known bug : http://stackoverflow.com/questions/13514553/wif-4-5-bootstrapcontext-security-token-null
                SecurityToken token = null;
                if (bootstrapContext.SecurityToken != null)
                {
                    token = bootstrapContext.SecurityToken;
                }
                else if (!bootstrapContext.Token.Equals(string.Empty))
                {
                    var handlers = FederatedAuthentication.FederationConfiguration.IdentityConfiguration.SecurityTokenHandlers;
                    token = handlers.ReadToken(new XmlTextReader(new StringReader(bootstrapContext.Token)));
                }

                SamlSecurityToken sst = token as SamlSecurityToken;
                tokenId.Text = sst.Id;
                tokenAssertionId.Text = sst.Assertion.AssertionId;
                tokenIssuer.Text = sst.Assertion.Issuer;

            }
            else
            {
                signedIn.Text = "You are not signed in.";
            }
        }
    }  

Answer

liorafar picture liorafar · Aug 18, 2015

Finally I was able to achieve seamless windows integrated SSO !

I found some ADFS property called “WIASupportedUserAgents”. Which means: the supported browsers that are allowed for WIA(Windows Integrated Authentication).
Running the following in PowerShell:

Set-ADFSProperties -WIASupportedUserAgents @("MSIE 6.0", "MSIE 7.0", "MSIE 8.0", "MSIE 9.0", "MSIE 10.0", "MSIE 11.0", "Trident/7.0", "MSIPC", "Windows Rights Management Client", "Mozilla/5.0")  

Than restarted ADFS service.

After setting this property for all browsers support, seamless windows authentication SSO started working !
I now don’t get credentials window and the authenticated windows user is authenticated through ADFS automatically.

Works like charm.

Thanks all and especially Wiktor Zychla for his great willing to help !