Creating a site wide password module for Orchard CMS

We have been developing custom Orchard CMS modules for close to 5 years. Our first module was very simple.  It injected an Apple Touch Icon into your website and allowed you to easily upload the image via the admin menu. Since then, we have written over 50 modules.  Our most recent being Sitewide Password.

We developed this module to easily protect Orchard CMS sites from anonymous access with a simple password scheme.  This is ideal when we need to protect our client's sites while they're in a staging environment.  We do this so anonymous users can't see newly developed features before they're released and to make sure search engines do not index these staging sites.  You can remove anonymous access to the site via the Orchard Roles feature, however, this would still allow users to see newly developed headers, navigation, and footer sections of the site.  We wanted something that you could easily turn on/off as needed and something we could share with the Orchard CMS community. The sitewide password is a feature of many newer website platforms, i.e. Squarespace.  This allows novice users to add this functionality out of the box.

Using the module

After enabling the module, the admin user can browse to the sitewide password settings page via the Settings menu link. The module allows you to enable a sitewide password and set the password. You can configure custom messages that users will see and determine how long the login cookie will stay valid. Once a user logs in you can persist their login forever and not require them to re-authenticate.


The module will not try to authorize a user if they're already logged in using Orchard's standard authentication scheme. If you open another browser window you will get a message that lets you know you may not access the site without entering the password.

Upon entering the password, you will either be granted access, if it matches the configured password, or it will continue to require you to enter the correct password.

Customizing the module

If you would like to customize the screen for the user to see custom styling and theming, you can do so by editing the following templates provided by the module:

  1. Layout-SitewidePassword.cshtml
  2. SitewidePassword.cshtml

We provide these two files because we want to make sure the page the user is presented with does not take on the current theme of the staging site. If desired, you can simply delete the Layout-SitewidePassword.cshtml file.

The code behind the module

The key to this module is the SitewidePasswordActionFilter.cs file.  This file injects itself into the .MVC request pipeline and allows us to execute custom code during an HTTP request. 

//if the settings are turned on
if (_sitewidePasswordSettings.Enabled)
{
   var workContext = _wca.GetContext();
   var currentUser = workContext.CurrentUser;
   var request = workContext.HttpContext.Request;
   var url = request.RawUrl;
   HttpCookie sitePass = request.Cookies[Constants.CookieName];
   if (request.RawUrl != Constants.SetPasswordUrl)
   {
       if (currentUser == null)
      {
           var decryptedPassword = sitePass == null ? string.Empty : Encoding.UTF8.GetString(_encryptionService.Decode(Convert.FromBase64String(sitePass.Value)));
           if (decryptedPassword != _sitewidePasswordSettings.Password)
           {
              var shape = _services.New.SitewidePassword();
              shape.Message = "Not Authorized";
              shape.ReturnUrl = url;
              filterContext.Result = new ShapeResult(filterContext.Controller, shape);
              filterContext.ActionParameters["returnUrl"] = url;
              Logger.Warning("Un-authorized user attempted to access site.");
          }
       }
    }
}

First, we check to see if the module is enabled. If it is, we determine if the user is authenticated. If this proves to be false we check the cookie we're using to store the encrypted password and verify if it matches the stored site password. If it does, the request may continue. If it does not, then a new Orchard Shape of type SitewidePassword is returned. This allows us to easily customize the view returned. We also log the failed access attempt.


The next important file is the controller configure. This allows the user to enter the password where we store the cookie required for access. This lives in SitewidePasswordController.cs.

if (spm.Password == _sitewidePasswordSettings.Password)
{
   var encryptedPass = Convert.ToBase64String(_encryptionService.Encode(Encoding.UTF8.GetBytes(_sitewidePasswordSettings.Password)));
   WorkContext context = _wca.GetContext();
   HttpCookie cookie = new HttpCookie(Constants.CookieName, encryptedPass);
   if (_sitewidePasswordSettings.NeverExpires)
      cookie.Expires = DateTime.Now.AddYears(10);
   else
      cookie.Expires = DateTime.Now.AddMinutes(_sitewidePasswordSettings.ValidForMinutes);
   context.HttpContext.Response.Cookies.Add(cookie);
   Logger.Information("Sitewide Password Access Granted.");
   return new RedirectResult(string.IsNullOrEmpty(spm.ReturnUrl) ? "~/" : spm.ReturnUrl);
}
var shape = _services.New.SitewidePassword();
shape.Status = "Failed Password";
shape.Url = spm.ReturnUrl;
Logger.Warning("Failed password attempt to access site.");
return View("SitewidePassword", shape);

This code block checks the entered password by the user. If it matches the stored password, it will encrypt and store the password in a cookie inside the user's browser. We do this so we can check for this cookie and decrypt it for each subsequent request. We utilize Orchard's encryption service and the Orchard context to properly set and retrieve these values. This authentication scheme is not meant to be an incredibly strong means of protection, rather offer enough protection to properly secure a staging site. It should not be used as a scheme in a production environment for very sensitive information.


If you would like to configure the view files for the user, you can edit the SitewidePassword.cshtml file in the Views folder. Here we are injecting in an alternate Layout file so we can make sure we control the entire experience if we want to.

@{
   WorkContext.Layout.Metadata.Alternates.Add("Layout__SitewidePassword");
   SitewidePasswordSettingsPart _settingsPart = WorkContext.CurrentSite.As<SitewidePasswordSettingsPart>();
   string message = Model.Message == "Not Authorized" ? _settingsPart.FailedAuthorizationMessage : _settingsPart.FailedPasswordMessage;
}

In Conclusion

To close the post, adding custom modules to Orchard is very easy. This is the best way to extend the content management system and take advantage of the various theming and settings features provided by the Orchard framework. You can bevel your logic into the CMS easily.

If you would like to download the module you can do it right here: https://gallery.orchardproject.net/Packages/CloudConstruct.SitewidePassword

Please let us know if you would like to have additional features added or if have any questions on the implementation.