Persist Login Across Sites
In a multisite environment with Azure B2C for login, we were tasked with persisting a user s login across sites. The plan to accomplish this involves determining when we re linking to another site in a link provider, appending and passing (securely) the current logged in user when linking over, and determining when we re coming from another site in a request pipeline.
The first step is to determine when our link is pointing to an item on a different site, and then appending our user information as part of an encrypted query string. We start out by creating a custom link provider, inheriting from Sitecore.Links.LinkProvider.
public class CrossSiteSsoLinkProvider : LinkProvider
Our logic will go in the GetItemUrl override. Store the base GetItemUrl first so we can return this if any of our checks fail along the way, otherwise we ll use it to append our encrypted user later. Then, run through any necessary fail early checks: making sure we re on CD, the user is logged in etc.
public override string GetItemUrl(Item item, UrlOptions options)
{
if (item == null)
{
Log.Error("CrossSiteSsoLinkProvider.GetItemUrl() - Item is null.", this);
return string.Empty;
}
var baseUrl = base.GetItemUrl(item, options);
if (Sitecore.Context.Site == null || Sitecore.Context.Site.ContentDatabase != null || !Sitecore.Context.User.IsAuthenticated ||
Sitecore.Context.Site != null && (Sitecore.Context.PageMode.IsPreview || Sitecore.Context.PageMode.IsExperienceEditorEditing) ||
item.Name == "__Standard Values")
return baseUrl;
Next, we need to get the current item s site. How you do this depends on your Sitecore setup. Once you have that, compare with the context site and return the base url if they are the same.
if (itemSite == null || itemSite.Site == Sitecore.Context.Site.SiteInfo)
return baseUrl;
Now that we know we re linking to another site, we take what we need from the user profile, serialize and encode it, append to a query parameter and return.
var authData = Sitecore.Context.User.Profile.SerializedData as Dictionary;
if (authData == null || authData.Count == 0)
return baseUrl;
var token = Base64UrlEncoder.Encode(JsonConvert.SerializeObject(authData));
return baseUrl + "?token=" + token;
Don t forget to patch the link provider.
crossSiteSso
With links to our other sites providing the encoded user, we now need to a way to receive that token when the user follows the link. We need to create a request processor.
...
public class SiteSwitchLoginProcessor : HttpRequestProcessor
In our Process override, do some of our standard checks again, and return early if the query parameter we used in the link provider is not present.
public override void Process(HttpRequestArgs args)
{
if (!Sitecore.Context.PageMode.IsNormal || Sitecore.Context.Database == null || Sitecore.Context.Database.Name != "web")
{
return;
}
var token = GetQueryString("token", args);
// If there is no token, then we don't need to do anything.
if (string.IsNullOrEmpty(token))
{
return;
}
Next, we decode and deserialize the user object so it's not exposed.
var identityClaim = JsonConvert.DeserializeObject(Base64UrlEncoder.Decode(token));
Once we have this, we can run our normal Sitecore login to authenticate the user. Finally, call our RemoveTokenQuery method, mentioned earlier, to redirect to the requested page minus the encoded user query parameter.
private static void RemoveTokenQuery(HttpRequestArgs args)
{
var queryString = HttpUtility.ParseQueryString(args.RequestUrl.Query);
queryString.Remove("token");
var redirectUrl = args.RequestUrl.AbsolutePath + (queryString.Count == 0 ? string.Empty : "?" + queryString);
args.HttpContext.Response.Redirect(redirectUrl);
}
Now a user can log in on one site, link over to any of our other sites in the same instance and not worry about having to log in again.