Surendra Sharma

Surendra Sharma

Search This Blog

Sunday, August 19, 2018

Protect your Sitecore dev, demo, QA, test website by login page

If you are developing Sitecore projects and for SPRINT demo to client we generally host it publically. Your development, demo, QA or test websites should be access publically but only to handful of people. One simple way is to keep login screen before accessing any page for that session. 

We have to develop this login feature in such a manner that it will be enable disable easily by single setting.


How can we implement this login screen on Sitecore website?


For this we will create some files in Visual Studio and items in Sitecore.


In Visual Studio


In HELIX based project I like to keep login related files in  “Sitecore.Feature.Navigation” feature.


Create one model "UserLogin.cs" in "Sitecore.Feature.Navigation" as

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace Sitecore.Feature.Navigation.Models
{
    public class UserLogin
    {
        public string UserName { get; set; }
        public string Password { get; set; }
    }
}

Create one view "SecureLogin.cshtml" in "Sitecore.Feature.Navigation" with below code

@model Sitecore.Feature.Navigation.Models.UserLogin

<!DOCTYPE html>
<html>
<head>
    <style>
        body {
            font-family: arial;
            font-size: 14px;
        }

        h1 {
            margin: 0;
            font-size: 20px;
            font-family: arial;
            text-align: center;
            margin-bottom: 22px;
        }

        .loginContainer {
            padding: 31px 28px;
            width: 430px;
            background-color: #7ad;
            border: #ddd solid 1px;
            margin: 100px auto 0;
            border-radius: 10px;
            -moz-border-radius: 10px;
            -webkit-border-radius: 10px;
        }

            .loginContainer label {
                color: #333333;
                float: left;
                line-height: 35px;
                width: 80px;
                clear: both;
            }

            .loginContainer input {
                border: 1px solid #e7e6e6;
                color: #333333;
                float: right;
                line-height: 22px;
                padding: 5px 10px;
                width: 310px;
                margin: 0 0 10px;
            }

                .loginContainer input#BtnLogin {
                    background-color: #ddd;
                    color: #333;
                    cursor: pointer;
                    float: left;
                    font-size: 14px;
                    font-weight: bold;
                    margin-left: 18px;
                    width: 100px;
                }
    </style>
</head>
<body>
    <div id="loginWrapper" class="group">

        @using (Html.BeginForm("Login", "SecureLogin", FormMethod.Post))
        {

            <div class="loginContainer">
                <div class="loginLogo"></div>
                <h1>Login</h1>
                <span style="color: #E60000; font-size: 15px;">
                    @Html.ValidationSummary(false)
                </span>
                @Html.Label("User Name")
                @Html.TextBoxFor(model => model.UserName)

                @Html.Label("Password")
                @Html.PasswordFor(model => model.Password)
                @Html.Hidden("requestedurl")

                <label>&nbsp;</label>
                <input type="submit" title="Login" value="Login" id="BtnLogin" onclick="return checkForNullValues()" class="loginBtn" />

                <div style="clear: both"></div>

            </div>
        }
    </div>
</body>
</html>

<script>

    function checkForNullValues() {
        var Username = document.getElementById("UserName").value;
        var Password = document.getElementById("Password").value;
        if (!Username.match(/\S/)) {
            alert("Username can not be blank");
            return false;
        }
        else if (!Password.match(/\S/)) {
            alert("Password can not be blank");
            return false;
        }
        else {
            return true;
        }

    }

    $(document).ready(function () {
        var input = $('.input-validation-error');

        if (input) {
            input.addClass("validationchanges");
        }
    });
    $(document).ready(function () {
        $('#Password').bind('copy paste cut', function (e) {
            e.preventDefault(); //disable cut,copy,paste
        });
    });

    $(document).ready(function () {
        $("form input[name=UserName]").val("");
    })

</script>


Create one controller “SecureLoginController.cs” in “Sitecore.Feature.Navigation” feature as 

using Sitecore.Configuration;
using Sitecore.Feature.Navigation.Models;
using System;
using System.Web.Mvc;

namespace Sitecore.Feature.Navigation.Controllers
{
    public class SecureLoginController : Controller
    {
        // GET: SecureLogin
        [HttpPost]
        public RedirectResult Login(UserLogin user, FormCollection form)
        {
            return Redirect(GetLoginURL(user, form));
        }

        private string GetLoginURL(UserLogin user, FormCollection form)
        {
            string loginPage = Request.UrlReferrer.ToString();

            try
            {
                if (Settings.GetSetting("SecureLogin.Enable").Equals("True") && user.UserName.ToLower().Equals(Settings.GetSetting("SecureLogin.UserName").ToLower())
                    && user.Password.Equals(Settings.GetSetting("SecureLogin.Password")))
                {
                    loginPage = Request.Url.Scheme + "://" + Request.Url.Authority + form["requestedurl"];
                    Session[Settings.GetSetting("SecureLogin.SessionName")] = "True";
                    Session.Timeout = 30;
                }
            }
            catch (Exception exception)
            {
                Sitecore.Diagnostics.Log.Error("NavigationController > Initialize Exception", exception, this);
            }
            return loginPage;
        }
    }
}
 

Create a config “Feature.Navigation.config” at “App_Config\Include\Feature” location to get value of different keys. You can create these values in Sitecore dictionary as well, but I like to create them in config file. Below keys names are self-explanatory as

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
    <sitecore>
        <settings>
            <setting name="SecureLogin.Enable" value="True" />
            <setting name="SecureLogin.UserName" value="steve" />
            <setting name="SecureLogin.Password" value="jobs" />
            <setting name="SecureLogin.SessionName" value="Success" />
            <setting name="SecureLogin.RedirectPage" value="/securelogin" />
        </settings>
    </sitecore>
</configuration>

Check this session is exist for every request. Best place to check this is header or footer as they are part of every page. I am placing session check code in “PrimaryMenu()” action method in “NavigationController.cs” file as

public ActionResult PrimaryMenu()
{
      if (Settings.GetSetting("SecureLogin.Enable").Equals("True"))
      {
           string sessionKey = Settings.GetSetting("SecureLogin.SessionName");

           if (Session[sessionKey] == null || (Session[sessionKey] != null && !System.Convert.ToString(Session[sessionKey]).Equals("True")))
           {
                Response.Redirect(Settings.GetSetting("SecureLogin.RedirectPage"));
           }
       }

       var items = this.navigationRepository.GetPrimaryMenu();
       return this.View("PrimaryMenu", items);
}

In Sitecore


Create one Model item at “/sitecore/layout/Models/Feature/Navigation/LoginUser” and set Model Type value as "Sitecore.Feature.Navigation.Models.UserLogin, Sitecore.Feature.Navigation"

sitecore login model
Sitecore Login Model



Create one layout item at “/sitecore/layout/Layouts/Feature/Navigation/SecureLogin”. Set Path for view as "/Views/Navigation/SecureLogin.cshtml" and insert link for above model as "/sitecore/layout/Models/Feature/Navigation/LoginUser"

Sitecore Login Layout
Sitecore Login Layout




Create “SecureLogin” item under Home item with any page template. Keep its icon as "Network/16x16/key1.png" to identify easily in content tree and assign above layout in its Final Layout as

Sitecore Login Final Layout
Sitecore Login Final Layout
 
That’s it!!!


Now whenever any visitor tries to access public page, he must have to authenticate via login screen.

Login Screen
Login Screen
 

One last thing, while going LIVE, we must remove this login screen by disabling it through “Feature.Navigation.config” file with below key settings as

<setting name="SecureLogin.Enable" value="False" />

I hope you enjoy this Sitecore article. Stay tuned for more Sitecore related articles.

Till that happy Sitecoring :)

Please leave your comments or share this article if it’s useful for you.

Monday, August 13, 2018

SSL certificates role in Sitecore 9

When you installed Sitecore 9 and above, setup should finished with two websites. Normally names like "*.dev.local" and "*_xconnect.dev.local".

Lets take an example of Habitat site.
 

"habitat.dev.local" runs on port 80 and 443 while "habitat_xconnect.dev.local" runs on HTTPS using 443 port. For running on HTTPS, we have to bind it to certificates.

Some of the questions about certificates that come to our mind are

  • How to bind certificates to websites?
  • How to change certificates to websites?
  • Where to find certificates?
  • Where to specify their thumbprint values?

How to bind/change certificates to these websites
Select website -> Click on Bindings -> Select Site running on port 443 and click on Edit -> Click on View button to view SSL certificate.
or change certifcates from available SSL certificates dropdownlist list.



SSL Certificate in IIS
SSL Certificate in IIS

 

Where to find certificates
For this click on Start -> search for "Manage Computer Certificates" -> This should open "certlm" -> Expand Personal -> Select "Certificates" -> Select your website and double click website -> This should open "Certificate" window -> Select "Details" tab -> Check "Thumbprint" property as shown below


Available certificates in machine
Available certificates in machine


You will get three certificates for each Sitecore 9 instance.

  • habitat.dev.local
  • habitat.dev.local.xConnect.Client
  • habitat_xconnect.dev.local

We can guess what are "habitat.dev.local" and "habitat_xconnect.dev.local", but what is "habitat.dev.local.xConnect.Client"?

Sitecore 9 is like client-server model where client is Sitecore webiste "habitat.dev.local" whereas xConnect instance "habitat_xconnect.dev.local" act as a server.

So if client want to communicate with server over secure HTTPS channel, they must agree with one thumbprint key. This thumbprint key is specified in new certificate "habitat.dev.local.xConnect.Client".

Where to specify thumbprint values for Sitecore and xConnect instances?
You have to specify
thumbprint value of "habitat.dev.local.xConnect.Client" at below locations.

For Sitecore website instance

 
Open "ConnectionStrings.config" from "C:\inetpub\wwwroot\habitat.dev.local\App_Config" and check below keys for client certificate thumbprint value

  • xconnect.collection.certificate
  • xdb.referencedata.client.certificate
  • xdb.marketingautomation.reporting.client.certificate
  • xdb.marketingautomation.operations.client.certificate


Thumbprint value in connectionstring.config
Thumbprint value in connectionstring.config


For xConnect instance
Open "AppSettings.config" from "C:\inetpub\wwwroot\habitat_xconnect.dev.local\App_Config" and check "validateCertificateThumbprint" key value.




Thumbprint value in AppSettings.config
Thumbprint value in AppSettings.config


Certificate is complex subject but I hope these details helps you to understand certificates role in Sitecore 9. Stay tuned for more Sitecore related articles.

Till that happy Sitecoring :)