I’ve had this exact same issue. It only happen when you visit a page in Sitecore which has an existing filePath. For example /sitecore/admin/showconfig.aspx
The problem happens in the Sitecore.Pipelines.HttpRequest.ExecuteRequest Process
method:
public override void Process(HttpRequestArgs args)
{
Assert.ArgumentNotNull((object) args, nameof (args));
SiteContext site = Context.Site;
if (site != null && !this.SiteManager.CanEnter(site.Name, (Account) this.ContextUser))
{
this.HandleSiteAccessDenied(site, args);
}
else
{
PageContext page = Context.Page;
Assert.IsNotNull((object) page, "No page context in processor.");
string filePath = page.FilePath;
if (filePath.Length > 0)
{
if (WebUtil.IsExternalUrl(filePath))
{
args.HttpContext.Response.Redirect(filePath, true);
}
else
{
if (string.Compare(filePath, HttpContext.Current.Request.Url.LocalPath, StringComparison.InvariantCultureIgnoreCase) == 0)
return;
args.HttpContext.RewritePath(filePath, args.HttpContext.Request.PathInfo, args.Url.QueryString, false);
}
}
else if (Context.Item == null)
this.HandleItemNotFound(args);
else
this.HandleLayoutNotFound(args);
}
}
As you can see if there is a filepath (filePath.Length > 0)
it does some logic, but it will never reach this code:
else if (Context.Item == null)
this.HandleItemNotFound(args);
That is the code which will send the user to the correct login page if they don't have access (cms is left idle).
private void HandleItemNotFound(HttpRequestArgs args)
{
string localPath = args.LocalPath;
string name = Context.User.Name;
bool flag = false;
bool loginPage = false;
string url1 = Settings.ItemNotFoundUrl;
if (args.PermissionDenied)
{
flag = true;
url1 = this.GetNoAccessUrl(out loginPage);
}
SiteContext site = Context.Site;
string str = site != null ? site.Name : string.Empty;
List<string> stringList = new List<string>((IEnumerable<string>) new string[6]{ "item", localPath, "user", name, "site", str });
if (Settings.Authentication.SaveRawUrl)
stringList.AddRange((IEnumerable<string>) new string[2]
{
"url",
Context.RawUrl
});
string url2 = WebUtil.AddQueryString(url1, stringList.ToArray());
if (!flag)
{
Log.Warn(string.Format("Request is redirected to document not found page. Requested url: {0}, User: {1}, Website: {2}", (object) Context.RawUrl, (object) name, (object) str), (object) this);
this.RedirectOnItemNotFound(url2);
}
else
{
if (loginPage)
{
Log.Warn(string.Format("Request is redirected to login page. Requested url: {0}, User: {1}, Website: {2}", (object) Context.RawUrl, (object) name, (object) str), (object) this);
this.RedirectToLoginPage(url2);
}
Log.Warn(string.Format("Request is redirected to access denied page. Requested url: {0}, User: {1}, Website: {2}", (object) Context.RawUrl, (object) name, (object) str), (object) this);
this.RedirectOnNoAccess(url2);
}
}
You should be able to reproduce the issue with these steps:
- Login into Sitecore.
- Visit a page which has a filePath like
/sitecore/admin/showconfig.aspx
- Stay on the page without refreshing until the cms becomes idle.
- If you are not logged in anymore and you refresh it will redirect you the wrong identity provider.
If you do the above steps, but you try to visit a page without filePath (for example /sitecore
) instead you will be redirect to the correct identity provider (Sitecore Identity) because that URL doesn't have a filePath. Sometimes /sitecore
does a 302 redirect to another page which does have a filePath. So it might also happen than.
I've fixed this by adding an extra pipeline:
public class FilePathAuthentication : HttpRequestProcessor
{
private readonly IUserContextFactory userContextFacotFactory;
private readonly ISiteContextFactory siteContextFactory;
private readonly IPageContextFactory pageContextFactory;
private readonly IContextWrapper contextWrapper;
private readonly ISiteManager siteManager;
private readonly ILogger logger;
public List<string> IgnorePaths { get; set; }
/// <summary>
/// In the ExecuteRequest pipeline the HandleItemNotFound check is not done when filepaths match.
/// The HandleItemNotFound check is required to redirect to the correct login page (identity provider).
/// This pipeline will make sure that this check still done for filepaths.
/// </summary>
public FilePathAuthentication(
IUserContextFactory userContextFacotFactory,
ISiteContextFactory siteContextFactory,
IPageContextFactory pageContextFactory,
IContextWrapper contextWrapper,
ISiteManager siteManager,
ILogger logger)
{
this.userContextFacotFactory = userContextFacotFactory;
this.siteContextFactory = siteContextFactory;
this.pageContextFactory = pageContextFactory;
this.contextWrapper = contextWrapper;
this.siteManager = siteManager;
this.logger = logger;
this.IgnorePaths = new List<string>();
}
public override void Process(HttpRequestArgs args)
{
var userContext = this.userContextFacotFactory.Create();
// Only check for the login page if not authenticated.
if (!userContext.IsAuthenticated)
{
var pageContext = this.pageContextFactory.Create();
var filePath = pageContext.FilePath;
// Make sure there is a filepath.
if (filePath.Length > 0)
{
// Ignore certain physical paths from the config.
if (this.IgnorePaths.Any(path =>
path.Equals(filePath, StringComparison.InvariantCultureIgnoreCase)))
{
return;
}
// This is the check in ExecuteRequest which skips redirect to the login page.
// So we only need to continue after this check here. Otherwise it should be done in ExecuteRequest.
if (string.Compare(filePath, contextWrapper?.CurrentRequestUrlLocalPath, StringComparison.InvariantCultureIgnoreCase) == 0)
{
// Get the login page for the correct identity provider.
var loginPage = GetNoAccessUrl(userContext);
if (!string.IsNullOrEmpty(loginPage))
{
var stringList = new List<string>
{
"url", WebUtil.NormalizeUrl(contextWrapper?.CurrentRequestRawUrl)
};
// Create a url to the identity provider with a return url to the protected page.
var redirectUrl = WebUtil.AddQueryString(loginPage, stringList.ToArray());
RedirectToLoginPage(redirectUrl);
}
}
}
}
}
private string GetNoAccessUrl(IUserContext userContext)
{
var siteContext = this.siteContextFactory.Create();
if (siteContext == null) return string.Empty;
// Check if there is a login page which we can redirect to.
// Also check if the physical folder contains sitecore. Those are the only paths which could be protected.
var hasLoginPage = siteContext.SiteLoginPage.Length > 0 && siteContext.SitePhysicalFolder.Contains("sitecore");
// Check if the user can enter the site, but is not authenticated yet.
var canEnter = this.siteManager.CanEnter(siteContext.SiteName) && !userContext.IsAuthenticated;
if (hasLoginPage && canEnter)
{
this.logger.Info("Redirecting to login page \"" + siteContext.SiteLoginPage + "\".");
// Return the url of the login page which should redirect to the correct identity provider.
return siteContext.SiteLoginPage;
}
return string.Empty;
}
private void RedirectToLoginPage(string url)
{
var urlString = new UrlString(url);
urlString["returnUrl"] = WebUtil.GetRawUrl();
contextWrapper.Response.Redirect(urlString.ToString(), true);
}
}
This is the config:
<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<pipelines>
<httpRequestBegin>
<processor type="Project.Foundation.Identity.Pipelines.FilePathAuthentication, Project.Foundation.Identity"
patch:after="processor[@type='Sitecore.Pipelines.HttpRequest.ExecuteRequest, Sitecore.Kernel']"
resolve="true">
<ignorePaths hint="list">
<ignorePath>/sitecore/default.aspx</ignorePath>
<ignorePath>/sitemap.xml</ignorePath>
<ignorePath>/layouts/system/visitoridentification.js</ignorePath>
<ignorePath>/layouts/system/vichecker.aspx</ignorePath>
<ignorePath>/layouts/system/visitoridentificationcss.aspx</ignorePath>
<ignorePath>/sitecore/shell/webservice/service2.asmx</ignorePath>
</ignorePaths>
</processor>
</httpRequestBegin>
</pipelines>
</sitecore>
</configuration>
If you add this pipeline the issue should be fixed and you will always be redirect to the correct identity provider. Even if you visit a page which has a filePath like /sitecore/admin/showconfig.aspx
.