8

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.";
            }
        }
    }  
TylerH
  • 20,799
  • 66
  • 75
  • 101
liorafar
  • 2,264
  • 4
  • 19
  • 39
  • 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 Aug 17 '15 at 14:58
  • @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 Aug 17 '15 at 15:05
  • 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 Aug 17 '15 at 15:20
  • @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 Aug 17 '15 at 16:38
  • The only remaining issue that comes to my mind is trying to switch the /adfs/ls endpoint to kerberos. One of screenshots reveals you have it set up to support anonymous authentication. – Wiktor Zychla Aug 17 '15 at 16:57
  • @Wiktor Zychla I can't seem to find a way to change the endpoint authentication. I can only enable\disable it. Are you familiar with how to do what you suggest? Thanks – liorafar Aug 17 '15 at 17:15
  • Nope, as I said, I have no experience with adfs3, only adfs2. – Wiktor Zychla Aug 17 '15 at 17:29
  • @Wiktor Zychla Can you please provide an example\link for an example of how doing windows integrated authentication with ADFS 2.0? maybe I can adapt it to ADFS 3.0. Thanks – liorafar Aug 17 '15 at 17:58
  • Adfs2 has a regular iis website. You just set up the website as you would set any other windows authentication websitr. That's why I asked you to create a dummy wia app. As far as I am aware, Adfs3 doesn't have an iis website, rather, it hosts the passive endpoint in its own system service. Thus, I believe setting the authentication in adfs2 would be easier. – Wiktor Zychla Aug 17 '15 at 18:45
  • @Wiktor Zychla I've checked with my Company and unfortunately our customers are going to work with win2k12 only which means ADFS3.0 only :( . I've tried a lot of test apps and I can't seem to do windows authentication without authentication prompt. in your first comment you wrote "The real issue is your adfs web app not willing the integrated authentication with no prompt for credentials". can it be something with my code? Thanks – liorafar Aug 18 '15 at 12:53
  • I don't think anything is wrong with your code. As far as I know how ws-fed works (and I believe I know it well), the client has no way to suggest which authentication method has to be activated at the sts side. Thus, the sts showing the auth prompt for 401 challenge means only that the server does't use ntlm/kerberos but rather the basic auth only. – Wiktor Zychla Aug 18 '15 at 14:19
  • 1
    do not expose your private data in forum ,like Certificate thumbprint ,this may lead to security vulnerability :) – Bibek Gautam Nov 18 '16 at 09:23
  • @Bibek Gautam hi, for some reason I'm not able to edit my question and remove the thumbprint. It says that I have not indented code. I've tried the Ctrl+K however it did not work. Can you please help me remove the Thubprint from the post? Thanks – liorafar Dec 04 '16 at 15:38

1 Answers1

12

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 !

liorafar
  • 2,264
  • 4
  • 19
  • 39
  • Yes...this is new in ADFS 2012R2. See https://technet.microsoft.com/en-us/library/dn727110.aspx – SamuelD MSFT Aug 18 '15 at 20:30
  • @liorafar - I am having similar issue, except mine is java application + SAML 2.0. In ADFS, we have both form based and WIA checkboxes enabled for intranet. By default, ADFS displays login form. However, we expect WIA should have priority over form based. I tried adding user agents as mentioned in your answer. but not working. Any help is appreciated. – Bhushan Karmarkar Jul 23 '19 at 11:15
  • @BhushanKarmarkar I'm sorry but I'm not familiar with Java. Part of the "magic" in .Net is that Microsoft provided WIF (Windows Identity Foundation) library that does a code behind logic. Maybe if you can find an equivalent in Java it might help. – liorafar Jul 24 '19 at 06:03
  • No issue. I am sending SAML 2.0 POST request to ADFS. I think we can enforce it through this request only. May be something related to "AuthnContextClassRef" – Bhushan Karmarkar Jul 24 '19 at 06:29