Sitecore use OAuth2 login with OWIN

As an experiment I wanted to see if it would be possible to use the social logins such as google with Sitecore using a similar approach as a plain MVC app. More details around doing this without Sitecore can be found here Notice that the code used in ASP .NET MVC relies on OWIN authentication middleware and ASP .NET Identity. This makes sense in ASP .NET, but we’ll have to reconsider this when integrating with Sitecore. A similar post using OWIN in Sitecore using Federated Authentication can be found here.

Sitecore and OWIN

Using OWIN with Sitecore makes sense, it is certainly possible to not use OWIN and rely on a custom implementation instead. There are some blog posts as well that will help you get started.

Given the security implications of getting a custom implementation slightly incorrect, it is highly recommended to use a proven solution like the OWIN middleware.

Additionally OWIN is widely used in regular ASP .NET applications. OWIN comes as a nuget package which makes it straightforward to update and take advantage of new features.

Sitecore and ASP .NET Identity

Using Sitecore with ASP .NET Identity does not make sense to me as Sitecore still uses ASP .NET membership. Using ASP .NET Identity would add another component that I could not see a use or benefit for, but please let me know in case anyone does. Some code in this post is from this post which describes how to use OWIN without ASP .NET Identity in ASP .NET MVC.

Configuring OWIN Middleware

Below code configures the OWIN middleware to use google authentication:

using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.Google;
using Owin;

namespace SitecoreOwinGoogle
{
    public partial class Startup
    {
        private void ConfigureAuth(IAppBuilder app)
        {
            var cookieOptions = new CookieAuthenticationOptions
            {
                LoginPath = new PathString("/Login")
            };

            app.UseCookieAuthentication(cookieOptions);

            app.SetDefaultSignInAsAuthenticationType(cookieOptions.AuthenticationType);

            app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions
            {
                ClientId = "your client id",
                ClientSecret = "your client secret"
            });
        }
    }
}

Notice in the usings that only OWIN is referenced, there are no ASP .NET Identity referenes added. ClientId and ClientSecret can be obtained from from Google Developers Console Never store passwords and other sensitive data in source code, for best practices see here

Login Through Google

In the OWIN middleware /Login is set as the login path. This can be set to a different path as well as long as there is code running when that path is hit to handle the login. I added a controller rendering here to transfer control to google:

public ActionResult Login(string returnUrl)
{
    return new ChallengeResult("Google",
      string.Format("/Login/ExternalLoginCallback?ReturnUrl={0}", returnUrl));
}

The ExternalLoginCallback will redirect the user to secure page that he was trying to navigate to before the OWIN middleware kicked in. It is important to run this code in the path specified in the redirectUri parameter on the ChallengeResult constructor, in this case “/Login/ExternalLoginCallback”.  Again I’m using a controller rendering for this.

public ActionResult ExternalLoginCallback(string returnUrl)
{
    return new RedirectResult(returnUrl);
}

I have used the same ChallengeResult class as ASP .NET MVC:

// Used for XSRF protection when adding external logins
private const string XsrfKey = "XsrfId";

internal class ChallengeResult : HttpUnauthorizedResult
{
    public ChallengeResult(string provider, string redirectUri)
        : this(provider, redirectUri, null)
    {
    }

    public ChallengeResult(string provider, string redirectUri, string userId)
    {
        LoginProvider = provider;
        RedirectUri = redirectUri;
        UserId = userId;
    }

    public string LoginProvider { get; set; }
    public string RedirectUri { get; set; }
    public string UserId { get; set; }

    public override void ExecuteResult(ControllerContext context)
    {
        var properties = new AuthenticationProperties { RedirectUri = RedirectUri };
        if (UserId != null)
        {
            properties.Dictionary[XsrfKey] = UserId;
        }
        context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
    }
}

Redirect back to Sitecore

After google completed validating the user it will redirect back to Sitecore. By default OWIN expects this at /signin-google. Here OWIN will set the cookie used for the CookieAuthentication. Make sure to configure this page as an authorized redirect URI in google’s console.

Sitecore will try to handle the request which is made to /signin-google, Sitecore should not touch this request and let OWIN handle it. The easiest way to achieve this is by adding this page to the IgnoreUrlPrefixes setting. From there OWIN will call the callback URL specified in the ChallengeResult and this will redirect back to page that is secured.

Securing pages

That is all for the OWIN and OAuth code. Now that this is in place a controller action method can be decorated with the Authorize attribute to trigger the authentication flow. Below diagram shows the end-to-end flow:

Authentication flow

Further considerations

As mentioned before this blog post is a start in using OWIN with OAuth2 in Sitecore. However there are many other items to explore:

  • Multisite support: perhaps you have a multisite solution and some sites need social logins and other sites need a different login method. OWIN supports branching the pipeline to allow for different configurations based on a condition e.g. hostname
  • Support different social provider: this blog post only works with google, but OWIN supports several different logins like Facebook, Twitter, Linkedin or Microsoft.
  • Logoff: this post only covers login, but it should be fairly straightforward to implement logoff in a similar fashion.
  • Sitecore Virtual Users: the authentication in this post is basic, either you are successfully logged in from google or you are not. Most real world applications are more complicated and different users have different permissions. In these cases it can be helpful to create a Sitecore virtual user and assign Sitecore roles.
  • OAuth2 scope: there is a scope parameter which supports retrieving additional information about the user from google. The users will have to accept this first on the consent screen when they use google’s system to log in.

Summary

This post showed how to set up OWIN with Sitecore and OAuth2. Using OWIN significantly simplified working with OAuth2 and google. There are a number of additional considerations that must be taken into account before using this in a production scenario.

How to leverage Sitecore Media Request Protection in javascript/client side code

Sitecore Media Request Protection is an important feature which protects a Sitecore instance from an image resize attack. It protects Sitecore image scaling parameters by ensuring that only server generated requests are processed. This creates issues if you have any client side code that needs to leverage image scaling. For example what if you have some client side databinding that needs to show images in lower dimensions? This blog discussed 2 techniques that can be used to get around this issue while still leaving Sitecore protected from a resize attack.

1. Protect each image server side when sending image data to client

Most likely the information about the images to display will come from the Sitecore server. Instead of just sending information about the image path also already send the image scaling parameters and call ProtectAssetUrl method as described here This will add the hash value to the path so any javascript code can safely use this image URL

2. Allow known dimensions that will be requested client side

You will know in which dimensions your client side code will request images. This approach will allow all valid images dimension while all other images dimensions will only be allowed when a valid hash is provided.  Media request protection is implemented in the MediaRequest pipeline and can be customized just like almost anything else in Sitecore. Below code will check if an unsafe request is using a  ‘safe’ dimension and if so allow that:

public class CustomMediaRequest : sc.Resources.Media.MediaRequest
{
    protected override bool IsRawUrlSafe
    {
        get
        {
            bool isSafe = base.IsRawUrlSafe;

            if (!isSafe)
            {
                var safeQueryStrings = new List
                    {
                        "mh=123&mw=456",
                        "mh=654&mw=321"
                    };

                foreach (var safeQueryString in safeQueryStrings)
                {
                    if (this.InnerRequest.RawUrl.Contains(safeQueryString))
                    {
                        return true;
                    }
                }
            }

            return isSafe;
        }
    }
}

This pipeline can be included as follows:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <mediaLibrary>
      <requestParser patch:instead="*[@type='Sitecore.Resources.Media.MediaRequest, Sitecore.Kernel']" type="namespace.CustomMediaRequest,  assembly" />
    </mediaLibrary>
  </sitecore>
</configuration>

Conclusion

The first method is the better method as it uses the OOTB functionality to protect image URL’s and does not allow additional image scaling to take place. I find it hard to think of a scenario where this method will not be feasible and will recommend this for any new development. However the MediaRequest feature is still relatively new as it is introduced in Sitecore 7.5. You might need to upgrade a solution from an older version and run into many issues with a limited amount of distinct image dimensions that are requested. In this case it is probably easier and less risky to use approach 2.

Protocol relative URL not working with Sitecore Media Request Protection

Using a protocol relative URL (e.g. “<img src=”//www.mysite.com/~/media/image.png?mh=50” />) is a great way to load a resource from a different domain over HTTP when the page it is referenced on is on HTTP and over HTTPS when it is on a page which is loaded over HTTPS. You want to ensure Media Request Protection is turned on in case you want to do some scaling of the image as in the sample above to protect your Sitecore instance.

In above scenario it is easy to run into issues as the hash generated to protect the image does not handle the protocol relative url properly. Consider below hash values generated by admin page at /sitecore/admin/mediahash.aspx

  1. /~/media/image.png?mh=50 gives hash  07205019C3F1F44438D529F64842DBEE98C0632B
  2. http://www.mysite.com/~/media/image.png?mh=50 results in same hash
  3. https://www.mysite.com/~/media/image.png?mh=50 also results in same hash
  4. //www.mysite.com/~/media/image.png?mh=50 gives a different hash 5A309EF4DC195929BA7EE946A0E66AD6D349712B

I did run in a number of issues because the difference in hash value and decided against using protocol relative URL and instead always load the images over HTTPS which is working well with Media Request Protection. Recent developments also indicate that loading as much as possible over SSL is the better approach see here