Adding a Log-in Page to Your Application

User management is one of the most important aspects of your web application, especially when connecting a CMS like Scrivito to it. You need to secure your content from being changed by unauthorised users and allow your editors to change the pages they need to. Scrivito does not include a user management system. However, it's not difficult to integrate an existing solution. 

In this tutorial, we will add a very simple log-in mechanism to an existing Scrivito application. If you want to, you can use your own application to follow along, or, if you don’t have one, you can use the guide for integrating Scrivito. Bootstrap is used in this tutorial, but you can also follow the instructions without using it. So let's get started.

Creating an object to represent the log-in page

The first thing we need in order to let users log in to our application is a page containing a form for entering the credentials. This log-in page is a new type of page currently not available in the CMS. Therefore, a new object class is required to represent it. To create the object class, we can use the page generator included in Scrivito. This generator creates the controller, the model, and the views needed. Please create the LoginPage object class now by running the following command:

Copy
$ rails g scrivito:page LoginPage
      create  app/models/login_page.rb
      create  app/controllers/login_page_controller.rb
      create  app/views/login_page/index.html.erb
      create  app/views/login_page/details.html.erb
      create  app/views/login_page/thumbnail.html.erb

The login page model is created with the basic attributes as seen here:

Copy
# app/models/login_page.rb

class LoginPage < Obj
  attribute :title, :string
  attribute :body, :widgetlist
  attribute :child_order, :referencelist
end

Next to the title and body attributes almost every kind of page can make good use of, the generator has added a referencelist attribute named child_order to the model class. child_order is for sorting subpages in navigations, however, we're not planning to add subpages to our log-in page, so you can safely remove this attribute from the model class.

Next, the CMS object that represents the page should be created. Usually, editors can create pages on their own. However, a log-in page is a special kind of page. Exactly one is required, and it must not be included in the normal navigation. Therefore, we create it using the Rails console:

Copy
$ rails c
> Scrivito::Workspace.create(title: 'AddLoginPage')
> Scrivito::Workspace.use('AddLoginPage')
> LoginPage.create(title: 'LogIn')

# Go ahead and publish the working copy so the new page is available 
# as we continue without having to switch working copies

> Scrivito::Workspace.find_by_title('AddLoginPage').publish

It is recommended to prevent editors from inadvertently creating further log-in pages. This can be done by not offering this page type in the page type browser when an editor wants to create a new page.

Logging In

To access the unique log-in page, the following model code is used here. It needs to be stored in app/models/login_page.rb:

Copy
# app/models/login_page.rb
class LoginPage < Obj
  ...
  def self.instance
    all.first
  end
end

Note that the instance class method returns the first LoginPage. As it happens, this is the page we have created before.

To make the log-in page accessible, a link should point to it. Please open app/views/layouts/application.html.erb and add the following code right after the last <% end %> of the navigation bar:

Copy
<%= link_to scrivito_path(LoginPage.instance), class: 'navbar-btn navbar-right' do %>
  <button class="btn btn-success">Log in</button>
<% end %>

If you are using your own application, just find a place in your layout where the link to the log-in page fits in.

The code adds a simple link to the log-in page using the instance method defined before. This link will be rendered as a button that looks something like this:

Now, a prominent link points to the log-in page, but when you click it, no log-in form is displayed. Let's change that and add a log-in form. Edit app/views/login_page/index.html.erb, and put the following code into this file:

Copy
<!-- app/views/login_page/index.html.erb -->
<%= scrivito_tag :h1, @obj, :title %>
<%= scrivito_tag :div, @obj, :body %>

<%= form_tag session_path, role: 'form' do %>
  <div class="form-group">
    <%= label_tag :login, 'Login' %>
    <%= text_field_tag :login, '', class: 'form-control', placeholder: 'Enter login' %>
  </div>
  <div class="form-group">
    <%= label_tag :password, 'Password' %>
    <%= password_field_tag :password, '', class: 'form-control', placeholder: 'Enter password' %>
  </div>
  <button type="submit" class="btn btn-default">Log in</button>
<% end %>

This renders a simple form with two input fields, one for the login and another one for the password. In addition, it lets an editor specify the title and the body of the page.

You may have spotted that the session_path is used in the form, which is currently defined nowhere. It's a good idea to separate rendering of the form from the logic of logging a user in. The latter will be handled by the SessionsController. Please create app/controllers/sessions_controller.rb and add the following content to it:

Copy
# app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
  def create
    if valid_credentials?(params[:login], params[:password])
      session[:user] = params[:login]
      redirect_to scrivito_path(Obj.root)
    else
      flash[:alert] = "The credentials you provided are invalid"
      redirect_to scrivito_path(LoginPage.instance)
    end
  end

  private

  def valid_credentials?(login, password)
    login == 'login' && password == 'password'
  end
end

Notice the valid_credentials? method in the SessionsController. In our trivial implementation, it only checks against hardcoded values (login and password). In a real implementation you should insert an authentication mechanism here.

If the credentials are valid, the user's login is stored in the session, and the user is redirected to the homepage.

If the provided credentials are invalid, a flash alert is triggered and the user is redirected to the log-in page once again. To display this alert to the user, add the following code at the top of the index template of the log-in page:

Copy
<% if flash[:alert].present? %>
  <div class="alert alert-danger" role="alert">
    <%= flash[:alert] %>
  </div>
<% end %>

We still need to tell Rails about the session. This can be done in the config/routes.rb file. Add the following line to it, to define it as a singular resource:

Copy
  resource :session, only: [:create, :destroy]

The user can now log in, but this doesn't have any effect on in-place editing. The editing panel is still displayed at all times, and the user cannot log out. Before we disable in-place editing, let's handle logging out.

Getting out

Our first step in letting users log out, is to implement a method that checks whether a user is logged in or not. Note that we had the SessionsController save the login to the “user” field of the session, which lets us determine whether a user is logged in or not. Add the following method as a private one to the application controller:

Copy
def logged_in?
  session[:user].present?
end
helper_method :logged_in?

Next, a link for logging out should be displayed, but only if a user is logged in. Also, this link should be displayed instead of the link for logging in we added previously. Change the code contained in application.html.erb that previously displayed a link to the log-in page to the following:

Copy
<% if logged_in? %>
  <%= link_to session_path, class: 'navbar-btn navbar-right', method: :delete do %>
    <button class="btn btn-warning">Log out</button>
  <% end %>
<% else %>
  <%= link_to scrivito_path(LoginPage.instance), class: 'navbar-btn navbar-right' do %>
    <button class="btn btn-success">Log in</button>
  <% end %>
<% end %>

If the user is logged in, the link for logging out is rendered. It just points to our normal SessionsController, but since we use the delete method, it will, by Rails convention, trigger the destroy method. If the user is not logged in, the log-in link is rendered as it was before.

To actually handle logging out, the destroy method needs to be defined in the sessions_controller.rb file. Add the following to this file:
Copy
def destroy
  session[:user] = nil
  redirect_to scrivito_path(Obj.root)
end

So, if the application receives a DELETE request, the “user” field of the session is reset, and a redirect to the homepage is issued. In full, the controller now looks like this:

Copy
# app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
  def create
    if valid_credentials?(params[:login], params[:password])
      session[:user] = params[:login]
      redirect_to scrivito_path(Obj.root)
    else
      flash[:alert] = "The credentials you provided are invalid"
      redirect_to scrivito_path(LoginPage.instance, login: params[:login])
    end
  end

  def destroy
    session[:user] = nil
    redirect_to scrivito_path(Obj.root)
  end

  private

  def valid_credentials?(login, password)
    login == 'login' && password == 'password'
  end
end

Tell Scrivito

Our application now features a mechanism for logging in, but Scrivito does not yet react to logging in and out. We want to enable in-place editing for logged-in users, and to disable editing for all other visitors. This can be done by means of an authentication callback that lets you code the rules according to which a user may edit all or parts of your website content. For more information, see Users and their Permissions.

This kind of configuration is usually done by means of an initializer. We recommend using config/initializers/scrivito.rb for this, so please open or create this file and add the following content to it at the bottom:
Copy
Scrivito.configure do |config|
  config.editing_auth do |env|
    request = ActionDispatch::Request.new(env)
    if request.session[:user].present?
      Scrivito::User.system_user
    end
  end
end

In line 2, we tell Scrivito that we're defining the edting_auth callback. To this callback the Rack environment is passed. Think of this environment as a big hash in which data required to perform a request is stored.

In line 3, a new ActionDispatch::Request is created from the rack environment. The new request object is the same as the request object you have available in a Rails controller.

Lines 4 to 6 check whether the user field of the session is set. If it is, the Scrivito system_user is returned. Basically, the system_user has all in-place editing privileges. You can specify the permissions of every user in detail, but this is out of scope here.

That's it. We just integrated authentication with Scrivito's in-place editing. Note that you will need to restart your server as changes to the initializers are only picked up after a server restart.

After logging out, you should no longer see the editing panel and should not be able to edit. Once you are logged in, this should be possible again.