However, these solutions have not worked for me so far. I can still get into the main site successfully, but if I try going to any other site, it tries to validate against ADFS and throws "Audience Validation Failed" error.
Few notes :
The multisites are separate domains but hosted on same instance, so they share code and web.config.
In ADFS, for troubleshooting purposes, we tried both types of setups : adding new domian to existing setup's relying party trust identifiers and setting up a new setuo for one of the domains. But neither seem to be working. They both throw the same "Audience Validation Failed" error stating that the only valid audience ADFS is looking at is the main site url and not the one we are passing now.
I've requested IT to share screenshots of ADFS setup so those can be reviewed.
_contentLoader.TryGet(ContentReference.StartPage, out homePage); if (homePage != null) { var signInPage = homePage.SignInPage; if (signInPage != null) { LoginPage = _urlResolver.GetUrl(homePage.SignInPage); }
//get WtRealm url from HomePage var epiSection = System.Web.Configuration.WebConfigurationManager.GetSection("episerver"); var enableScheduler = ((EPiServerSection)epiSection).ApplicationSettings.EnableScheduler; if (enableScheduler) { if (!environment.Equals("INTE")) { WtRealmUrl = homePage.WtRealmSchedulerSite; } else { WtRealmUrl = SiteDefinition.Current.SiteUrl.AbsoluteUri; } } else { WtRealmUrl = homePage.WtRealmMainSite; } }
if (String.IsNullOrEmpty(WtRealmUrl)) { _logger.Error("WtRealm url not found on homepage, so setting from config."); WtRealmUrl = System.Web.Configuration.WebConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"]; }
//Enable federated authentication app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions() { //Trusted URL to federation server meta data MetadataAddress = "https://adfs.centurycommunities.com/federationmetadata/2007-06/federationmetadata.xml", //Value of Wtreal must *exactly* match what is configured in the federation server //Wtrealm = System.Web.Configuration.WebConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"], Wtrealm = WtRealmUrl, TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = false, RoleClaimType = ClaimTypes.Role }, Wreply = WtRealmUrl, Notifications = new WsFederationAuthenticationNotifications() { RedirectToIdentityProvider = (ctx) => { HandleMultiSitereturnUrl(ctx);
//To avoid a redirect loop to the federation server send 403 when user is authenticated but does not have access if (ctx.OwinContext.Response.StatusCode == 401 && ctx.OwinContext.Authentication.User.Identity.IsAuthenticated) { ctx.OwinContext.Response.StatusCode = 403; ctx.HandleResponse(); }
//XHR requests cannot handle redirects to a login screen, return 401 if (ctx.OwinContext.Response.StatusCode == 401 && IsXhrRequest(ctx.OwinContext.Request)) { ctx.HandleResponse(); } return Task.FromResult(0); }, SecurityTokenValidated = (ctx) => { //set role claim to lower case var roleClaim = ctx.AuthenticationTicket.Identity.Claims.FirstOrDefault(x => x.Type == ctx.AuthenticationTicket.Identity.RoleClaimType); if (roleClaim != null && roleClaim.Value.ToLower().StartsWith("secgrp")) { ctx.AuthenticationTicket.Identity.RemoveClaim(roleClaim);
var updatedRoleClaim = new Claim(ctx.AuthenticationTicket.Identity.RoleClaimType, roleClaim.Value.ToLower()); ctx.AuthenticationTicket.Identity.AddClaim(updatedRoleClaim); } try { using (var context = new EpiServerCMSDbContext()) { context.Database.ExecuteSqlCommand("update [tblContentAccess] set Name = LOWER(Name) where LOWER(Name) like 'secgrp%'"); } } catch (Exception ex) { _logger.Error($"Error when setting secgrp roles to LowerCase", ex); } bool isSalesAgent = false; foreach (var claim in ctx.AuthenticationTicket.Identity.Claims) { if (claim.Type.Equals(ctx.AuthenticationTicket.Identity.RoleClaimType) && claim.Value.ToLower().Contains("saleteams")) { isSalesAgent = true; }
} //Sync user and the roles to EPiServer in the background ServiceLocator.Current.GetInstance<ISynchronizingUserService>().SynchronizeAsync(ctx.AuthenticationTicket.Identity); ctx.AuthenticationTicket.Properties.ExpiresUtc = DateTimeOffset.UtcNow.AddHours(2);
//Ignore scheme/host name in redirect Uri to make sure a redirect to HTTPS does not redirect back to HTTP var redirectUri = new Uri(ctx.AuthenticationTicket.Properties.RedirectUri, UriKind.RelativeOrAbsolute); if (redirectUri.IsAbsoluteUri) { ctx.AuthenticationTicket.Properties.RedirectUri = redirectUri.AbsoluteUri;//.PathAndQuery; _logger.Error("Return url with Path and Query : " + ctx.AuthenticationTicket.Properties.RedirectUri); }
if (isSalesAgent) { //change redirect login to site homepage instead ctx.AuthenticationTicket.Properties.RedirectUri = WtRealmUrl; }
return Task.FromResult(0);
} } }) ;
//Remap logout to a federated logout app.Map(LogoutPath, map => { map.Run(ctx => { ctx.Authentication.SignOut(); return Task.FromResult(0); }); });
// Add stage marker to make sure WsFederation runs on Authenticate(before URL Authorization and virtual roles) app.UseStageMarker(PipelineStage.Authenticate);
//Tell antiforgery to use the name claim AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.Name;
At this point, having tried multiple things, we aren't entirely sure whether we need to change something in code here, or some setup in ADFS. Any thoughts here would be really helpful.
Hi
I've referenced the below similar tickets on this topic to setup the ADFS Authentication for multisite :
https://world.optimizely.com/forum/developer-forum/CMS/Thread-Container/2020/12/sso-with-multisite/
https://world.optimizely.com/forum/developer-forum/CMS/Thread-Container/2020/2/how-to-integrate-adfs-with-episerver-in-case-run-as-multiple-sites/
However, these solutions have not worked for me so far. I can still get into the main site successfully, but if I try going to any other site, it tries to validate against ADFS and throws "Audience Validation Failed" error.
Few notes :
Here's my code snippet in Startup.cs :
public void Configuration(IAppBuilder app)
{
string WtRealmUrl = "";
HomePage homePage = null;
_contentLoader.TryGet(ContentReference.StartPage, out homePage);
if (homePage != null)
{
var signInPage = homePage.SignInPage;
if (signInPage != null)
{
LoginPage = _urlResolver.GetUrl(homePage.SignInPage);
}
//get WtRealm url from HomePage
var epiSection = System.Web.Configuration.WebConfigurationManager.GetSection("episerver");
var enableScheduler = ((EPiServerSection)epiSection).ApplicationSettings.EnableScheduler;
if (enableScheduler)
{
if (!environment.Equals("INTE"))
{
WtRealmUrl = homePage.WtRealmSchedulerSite;
}
else
{
WtRealmUrl = SiteDefinition.Current.SiteUrl.AbsoluteUri;
}
}
else
{
WtRealmUrl = homePage.WtRealmMainSite;
}
}
if (String.IsNullOrEmpty(WtRealmUrl))
{
_logger.Error("WtRealm url not found on homepage, so setting from config.");
WtRealmUrl = System.Web.Configuration.WebConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
}
//Enable federated authentication
app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions()
{
//Trusted URL to federation server meta data
MetadataAddress = "https://adfs.centurycommunities.com/federationmetadata/2007-06/federationmetadata.xml",
//Value of Wtreal must *exactly* match what is configured in the federation server
//Wtrealm = System.Web.Configuration.WebConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"],
Wtrealm = WtRealmUrl,
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
RoleClaimType = ClaimTypes.Role
},
Wreply = WtRealmUrl,
Notifications = new WsFederationAuthenticationNotifications()
{
RedirectToIdentityProvider = (ctx) =>
{
HandleMultiSitereturnUrl(ctx);
//To avoid a redirect loop to the federation server send 403 when user is authenticated but does not have access
if (ctx.OwinContext.Response.StatusCode == 401 && ctx.OwinContext.Authentication.User.Identity.IsAuthenticated)
{
ctx.OwinContext.Response.StatusCode = 403;
ctx.HandleResponse();
}
//XHR requests cannot handle redirects to a login screen, return 401
if (ctx.OwinContext.Response.StatusCode == 401 && IsXhrRequest(ctx.OwinContext.Request))
{
ctx.HandleResponse();
}
return Task.FromResult(0);
},
SecurityTokenValidated = (ctx) =>
{
//set role claim to lower case
var roleClaim = ctx.AuthenticationTicket.Identity.Claims.FirstOrDefault(x => x.Type == ctx.AuthenticationTicket.Identity.RoleClaimType);
if (roleClaim != null && roleClaim.Value.ToLower().StartsWith("secgrp"))
{
ctx.AuthenticationTicket.Identity.RemoveClaim(roleClaim);
var updatedRoleClaim = new Claim(ctx.AuthenticationTicket.Identity.RoleClaimType, roleClaim.Value.ToLower());
ctx.AuthenticationTicket.Identity.AddClaim(updatedRoleClaim);
}
try
{
using (var context = new EpiServerCMSDbContext())
{
context.Database.ExecuteSqlCommand("update [tblContentAccess] set Name = LOWER(Name) where LOWER(Name) like 'secgrp%'");
}
}
catch (Exception ex)
{
_logger.Error($"Error when setting secgrp roles to LowerCase", ex);
}
bool isSalesAgent = false;
foreach (var claim in ctx.AuthenticationTicket.Identity.Claims)
{
if (claim.Type.Equals(ctx.AuthenticationTicket.Identity.RoleClaimType) && claim.Value.ToLower().Contains("saleteams"))
{
isSalesAgent = true;
}
}
//Sync user and the roles to EPiServer in the background
ServiceLocator.Current.GetInstance<ISynchronizingUserService>().SynchronizeAsync(ctx.AuthenticationTicket.Identity);
ctx.AuthenticationTicket.Properties.ExpiresUtc = DateTimeOffset.UtcNow.AddHours(2);
ctx.AuthenticationTicket.Properties.IsPersistent = true;
//Ignore scheme/host name in redirect Uri to make sure a redirect to HTTPS does not redirect back to HTTP
var redirectUri = new Uri(ctx.AuthenticationTicket.Properties.RedirectUri, UriKind.RelativeOrAbsolute);
if (redirectUri.IsAbsoluteUri)
{
ctx.AuthenticationTicket.Properties.RedirectUri = redirectUri.AbsoluteUri;//.PathAndQuery;
_logger.Error("Return url with Path and Query : " + ctx.AuthenticationTicket.Properties.RedirectUri);
}
if (isSalesAgent)
{
//change redirect login to site homepage instead
ctx.AuthenticationTicket.Properties.RedirectUri = WtRealmUrl;
}
return Task.FromResult(0);
}
}
}) ;
//Remap logout to a federated logout
app.Map(LogoutPath, map =>
{
map.Run(ctx =>
{
ctx.Authentication.SignOut();
return Task.FromResult(0);
});
});
// Add stage marker to make sure WsFederation runs on Authenticate(before URL Authorization and virtual roles)
app.UseStageMarker(PipelineStage.Authenticate);
//Tell antiforgery to use the name claim
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.Name;
}
private void HandleMultiSitereturnUrl(
RedirectToIdentityProviderNotification<Microsoft.IdentityModel.Protocols.WsFederation.WsFederationMessage,
WsFederationAuthenticationOptions> context)
{
context.Options.Wtrealm = "https://" + HttpContext.Current.Request.Url.Host;
//context.Options.Wreply = "https://" + HttpContext.Current.Request.Url.Host;
}
At this point, having tried multiple things, we aren't entirely sure whether we need to change something in code here, or some setup in ADFS. Any thoughts here would be really helpful.
Regards
Ritu