8
votes

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.";
            }
        }
    }  
1
Most of details you provided are unimportant. The real issue is your adfs web app not willing the integrated authentication with no prompt for credentials. First, this always worked only in ie, do not expect to easily make chrome/ff support it. Second, seems your configuration regarding the authentication is ok. Also, you have done most of required configuration steps at the client side. What comes to my mind - can you set up a dummy asp.net site beside adfs that would also support integrated auth and verify that this is adfs problem and not the problem with integrated auth on your server?Wiktor Zychla
@Wiktor Zychla Hi! Thanks for your quick comment. What do you mean by a dummy asp.net site beside adfs? To create my own sts site? service provider site? can you share please a link for a tutorial of how I can do that so I'll check it? Thanks !liorafar
No, just a site, not an sts nor an ip. Just a site to test whether or not the integrated auth works for this particular server. I have deployed dozens of adfses with integrated auth with no major issues, thus, I am trying to think of what could be wrong with your setup. Adfs integrated auth works exactly the same way any other website's integrated auth works. Thus, a dummy site is to test whether or not something is wrong with the server rather than adfs.Wiktor Zychla
@Wiktor Zychla Hi. I've tested the dummy application and it seems to work ! I've created ASP.NET MVC 4 via Visual Studio 2012. and chose "Intranet Application". then I published the project and put in the same server of my adfs testapp in application. Enabled the application authentication to be "Windows Authentication" and disabled "Anonymous Authentication". Ran the application and saw my windows user name in the right upper corner without providing credentials. what next? are there any more tests I need to do? Thanks !!!liorafar
do not expose your private data in forum ,like Certificate thumbprint ,this may lead to security vulnerability :)Bibek Gautam

1 Answers

12
votes

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 !