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).
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:
Relying Party Configuration:
Web App Configuration:
Authentication:
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.";
}
}
}
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 !