Umbraco v7 Members' Area with Protected Content Without Coding?

Tuesday 3rd June 2014, 13:43

1 Introduction

I was attempting to help a new user by answering a query in the Umbraco forum which eventually morphed into the original poster trying to create a Members' area. I'd noticed some base snippets (these are out of the box bits of Razor macro code that usually work quite well giving you good starts on creating menus or listing or child nodes etc).

There are three which look like they'd give you a fully fledged members' area;

  • Login
  • Login Status
  • Member Register

It looks like the aim of the Umbraco team is to provide an out of the box Members' area - let's see how useable this is without having to delve into code *.

* OK - maybe a little bit of Razor !

2 Setting up a Demo Site

I took the basic site template I used to write the tutorial Creating a Basic Site in Umbraco v7 or available as a PDF here and looked at how easy it is to implement these snippets into a full solution.

2.1 Document Types and Templates

First create the following Document Types (allowing Umbraco to create the matching template) with a simple content tab and a bodyText Rich Text Editor Property on each.

  • Homepage
  • Login
  • Register
  • Simple Content Page

Remember to copy the Initializr js scripts and CSS and update any references.

Template Structure

Members Document Types

Allow the homepage to be created at the root and the others to be legitimate children. Then create a Master template, update all of the other templates to be children of this and add the Initialzr template code to the master with a @RenderBody and something relevant in the child templates

The Document Types

Master and Child Template structure - I've updated the menu with links to the pages I'll create later to make my life easier

Child Template Markup

Child Template Markup

2.2 Content Nodes

Next create the following content nodes;

  • Homepage - type = Homepage
  • Login - type = Login
  • Register - type = Register
  • Open - type = Simple Content Page
  • Protected - type = Simple Content Page
Content Structure

Our test content structure

Now we should have a fully working site - though none of the pages do anything yet.

3 Adding the Members Razor Code

3.1 Creating Partial View Macro Files

Now go and create a Partial View Macro File (found under the Developer menu) for each of the following - remembering to select the relevant snippet and leave the Create Macro checkbox selected. A snippet is an Umbraco supplied Razor template.

  • Login
  • LoginStatus
  • RegisterMember
Partial Macro Views

Our partial macro views

3.2 Adding the Macros to our Templates

Now we need to go and include these macros in the relevant templates. First job put the login status into the footer of the master template so that we have this on all pages.

Login Status on the Master Template Footer

Login Status on the Master Template Footer

Now do the same for the Login and the Register templates (using the relevant macro).

Login Macro on the Login Template

Login Macro on the Login Template

3.3 Adding the Links to Login & Register

If you now visit the Register page you should see a register form.

Register Form

Register Form

Now users can register we need to fix a small issue in the login. To do this we need to break the "no code" rule a little bit to help us see what's happening and to help users. If we look at the Partial Macro View Login Status you'll see that this only outputs something if the member is logged in - let's add an else clause to provide a login link if they are NOT logged in - again you should style this using CSS to make it more useable.

Login Status - updated razor Login Status - updated razor

Login Status - adding a simple login link

3.4 Test the register

Let's test the register by creating a TestMember - use the registration form and you'll see that this will also log you in, you can now see the Login Status macro change its output

Logged in Member

Login Status in the footer shows the user is logged in

4 Protecting Content

4.1 Protecting Content in the Umbraco Back Office

We can now add protection to content - to do this click the right hand mouse button on the content node (e.g. the Protected content) and click the Public Access menu item.

Content - Setting Protection

Umbraco Content Protection Menu Item - Public Access

This gives you two options - Single User Protection and Role base protection. You'll see that it warns we don't have a member group. Let's create this first in the Members section. I call mine "WebsiteRegistrations".

Content - Setting Protection - Warning

We don't have a MemberGroup

4.2 Creating a MemberGroup

Create a WebsiteRegistrations Member Group.

WebsiteRegistrations Members Group

WebsiteRegistrations Members Group

4.3 Add Protection

Now we can go back and add the protection to the page.

Add WebsiteRegistrations Members Group to the Protected Page

Add WebsiteRegistrations Members Group to the Protected Page

Choose the Login page from the picker for Login Page and I just quickly created an Error page (a simple content page with help for the user to contact web admin etc.)

4.3 Testing the Solution

Let's test the solution - if you're still logged in as TestMember and you browse our Protected page you'll see the content from our Homepage... this is because the user isn't a member of the group we just created so we're being redirect to the error page.

Members doesn't have permission for the Protected Page

Members doesn't have permission for the Protected Page

If you edit the member and add this group you'll see this works. Obviously we could have our editor add this group manually for each registration... but ideally we'd like this to be automatic, we'll add this later.

Adding Member to WebsiteRegistrations Group Member now has permission for the Protected Page

Viewing the Protected Page

Test Logging out and logging in. Here is where you meet your second issue - the login page redirects a successful user to the login page. This would be particularly bad from a User Experience point of view.

I've updated the default Login Razor script to use the requested URL unless it's the login page itself (to avoid a circular redirect) and to accommodate the user getting their password wrong (otherwise it would try to redirect to a page which processes macros!). Though I said they'd be no code... - copy and paste this Razor code below over your Login Partial View Macro.

IMPORTANT You need to find your login page's node ID and update line 11

To do this go to the Properties tab of your Login content node

How to Find Your Login Page Node ID

How to Find Your Login Page Node ID

@inherits Umbraco.Web.Macros.PartialViewMacroPage

@using System.Web.Mvc.Html
@using ClientDependency.Core.Mvc
@using Umbraco.Web
@using Umbraco.Web.Models
@using Umbraco.Web.Controllers


@{
    // You have to set the login node ID otherwise you'll have an infinite loop if someone hits the login page first
    var loginNodeID = 1056;
    var loginModel = new LoginModel();
    var loginStatusModel = Members.GetCurrentLoginStatus();
    
    Html.EnableClientValidation();
    Html.EnableUnobtrusiveJavaScript();
    Html.RequiresJs("/umbraco_client/ui/jquery.js");
    Html.RequiresJs("/umbraco_client/Application/JQuery/jquery.validate.min.js");
    Html.RequiresJs("/umbraco_client/Application/JQuery/jquery.validate.unobtrusive.min.js");
}

@* NOTE: This RenderJsHere code should be put on your main template page where the rest of your script tags are placed *@
@Html.RenderJsHere()
    
@{
    var checkUrl = HttpContext.Current.Request.Url.AbsolutePath.ToString();
    
    // add a trailing / if there isn't one (you can access http://mydomain.com/login or http://mydomain.com/login/
    if (@checkUrl[checkUrl.Length -1] != '/') {
        checkUrl = checkUrl + "/";
    }
 
    @* if we don't have a session variable and have a request URL then store it *@
    @* we have to store it because if user tries an incorrect login then Current.Request.Url will show /umbraco/RenderMvc *@
    if (HttpContext.Current.Request.Url != null && HttpContext.Current.Request.Url.AbsolutePath.ToString() != "/umbraco/RenderMvc" && HttpContext.Current.Session["redirectURL"] == null) {
        if (checkUrl !=  @Umbraco.Content(loginNodeID).Url) {
            HttpContext.Current.Session["redirectURL"] = HttpContext.Current.Request.Url.ToString();
        }
    }

    if (loginStatusModel.IsLoggedIn)
    {
        var redirectUrl = (string)HttpContext.Current.Session["redirectURL"];
        
        var currentUrl = HttpContext.Current.Request.Url.ToString();
        if (HttpContext.Current.Session["redirectURL"] != null) {
            // clear the session variable for future logins
            HttpContext.Current.Session["redirectURL"] = null;
            HttpContext.Current.Response.Redirect(redirectUrl);
        }
        else {
            // Nothing in the session so we will go home 
            HttpContext.Current.Response.Redirect("/");
        }
    }
    
    
    using (Html.BeginUmbracoForm<UmbLoginController>("HandleLogin"))
    {
        <fieldset>
            <legend>Login</legend>
            
            @Html.ValidationSummary("loginModel", true)
    
            @Html.LabelFor(m => loginModel.Username)
            @Html.TextBoxFor(m => loginModel.Username)
            @Html.ValidationMessageFor(m => loginModel.Username)
            <br />
    
            @Html.LabelFor(m => loginModel.Password)
            @Html.PasswordFor(m => loginModel.Password)
            @Html.ValidationMessageFor(m => loginModel.Password)
            <br />
    
            <button>Login</button>
        </fieldset>  
    }
}

Figure 1 - /App_Code/UmbExtendTreeController.cs

What a Redirect Loop Looks Like

What a Redirect Loop looks like... avoid!

4 Remaining Problems / Limitations

There we are - we now have a working Members only area with a registration form and an enhanced login script to improve the User Experience (e.g. redirecting them to the page they requested after logging in).

Remaining Issues:

  1. Users which register online can't automatically get access to the area - perhaps this is what you'd want, e.g. to authorise each user. There is nothing in this solution that let's you email the users when you've approved them - it would be manual.
  2. There is no "forgotten password" functionality - a must on a professional site

For simple sites I think this solution would work - and it's useful as a learning exercise for those new to Umbraco but I think you're still looking at packages or custom Member providers (e.g. writing code) for a member area for now.

Comments

If you spot any typos or issues with this tutorial please leave a comment below or email me email iconsteve@SiempreSolutions.co.uk

Name: Mickey Nguyen

Date: 1549hrs 05/07/2015

Website:

Excellent post. Thanks you so muck

Mickey

Name: Gerhard

Date: 0808hrs 01/10/2015

Website:

Hi, great explanation on how everything comes together without (much) coding. I only left out the part where you check for empty value in session before adding the current page. "HttpContext.Current.Session["redirectURL"] == null" - otherwise users would always go to first restricted page they visited after login, even if they then went to another.

Name: Steve

Thanks Gerhard, Do you mean that if they went to:

  • Page x (protected)
  • Login (doesn't login but goes to y)
  • Page y
  • Login page
It would redirect to page x ?

Name: Steve

Ahh.. I see so line 36 should be: if (HttpContext.Current.Request.Url != null && HttpContext.Current.Request.Url.AbsolutePath.ToString() != "/umbraco/RenderMvc") // && HttpContext.Current.Session["redirectURL"] == null)

Name: shin

Date: 0950hrs 03/11/2015

Website:

Hi, may i know how to make sure when you log out it return to the login page?

Name: Steve

If you take a look at the Razor code for the LoginStatus partial view macro you'll see that there is a RedirectUrl value you can set on the logoutModel @Html.HiddenFor(m => logoutModel.RedirectUrl)

Hope that helps

Name: Amanda

Date: 1624hrs 03/05/2016

Website:

I actually get an infinite loop with the code you provided :( any ideas of why that would happen? I'm using Umbraco 7.4.3

Name: Steve

Hi,

Thanks for your comment - check the first line in /App_Code/UmbExtendTreeController.cs - this node ID should match your login page node ID.

Also check where you have set the error page when setting the public page permissions in Umbraco.

Hope that helps - Steve

Post a Comment

Comments are most welcome! Comments are published here after moderation to avoid spam.


Verification image
Change Verification Image

 

Siempre Solutions Limited is a company registered in England and Wales with company number 09019307