1. Introduction

In this tutorial, we will learn about the features of the signin plugin, by creating a simple application allowing users to subscribe and create a profile page. We will be using a SQL database, meaning you should already be familiar with the ODB plugin (see Getting started tutorial).

1.1 Preparations

Start by creating a crails application using the following command:

$ crails new -n userspace -d sqlite -p html
$ cd userspace

Add the signin plugin that we are going to use in this tutorial:

$ crails plugins signin install

And prepare a layout for our html views:

$ crails scaffold layout

We'll now make some changes to the ApplicationController:
- we want our new layout to be the default layout,
- we want it to inherit from Crails::Odb::Controller.
Here's how it should look like:

app/controllers/application.hpp
#pragma once
#include <crails/controller.hpp>
#include <crails/odb/controller.hpp>

class ApplicationController :
  public Crails::Odb::Controller<Crails::Controller>
{
public:
  ApplicationController(Crails::Context& context) :
    Crails::Odb::Controller<Crails::Controller>(context)
  {
    vars["layout"] = std::string("application");
  }
};

2. Authenticable models

The first step we need to take is to create a model which can be used to store a user's data. It'll need to provide at least a username and a password, so we'll use the following command to generate the model, controller and views for user management:

$ crails scaffold resource -m user -p std::string/name Crails::Password/password
The Crails::Password type is provided by the signin plugin. It behaves as an std::string, but encrypts any value set into it using the AES-256-CBC algorithm. Optionally, it can be overloaded to implement other encryption strategies.

We will now enable authentication from this model, by having it inherit from Crails::AuthenticableModel, using multiple inheritance:

app/models/user.hpp
#pragma once
#include <crails/odb/model.hpp>
#include <crails/datatree.hpp>
#include <crails/password.hpp>
#include <crails/signin/model.hpp> // header for AuthenticableModel

#pragma db object
class User :
  public Crails::Odb::Model,
  public Crails::AuthenticableModel
{
  odb_instantiable()
public:
  ...

private:
  std::string name;
  std::string password;
};

The AuthenticableModel object will generate and store session data for a user, including an authentication token and an expire time. The session duration can be configured in config/signin.cpp by customizing the AuthenticationModel::session_duration variable.

3. Opening a session

3.1 The SessionController

At this point, we can already create new users, but we cannot connect as one of the users. In order to remedy that, we'll have to create a session controller. Create the following file:

app/controllers/session.hpp
#pragma once
#include <crails/signin/session_controller.hpp>
#include "application.hpp" // ApplicationController
#include "app/models/user.hpp" // our model
#include "lib/odb/application-odb.hxx" // query implementations

class SessionController :
  public Crails::SessionController<User, ApplicationController>
{
public:
  SessionController(Crails::Context& context) :
    Crails::SessionController<User, ApplicationController>(context)
  {
  }

  void on_session_created() override
  {
    redirect_to("/user");
  }

  void on_session_destroy() override
  {
    redirect_to("/user");
  }

  void new_()
  {
    render("session/new");
  }

  std::shared_ptr<User> find_user() override
  {
    std::shared_ptr<User> result;
    std::string name = params["name"].as<std::string>();
    Crails::Password password = params["password"].as<std::string>();

    database.find_one(result,
      odb::query<User>::name == name &&
      odb::query<User>::password == password
    );
    return result;
  }
};

The Crails::SessionController class will do most of the heavy-lifting for us: we just need to implement:
- hooks for when sessions are created and destroyed: on_session_created and on_session_destroyed,
- an action to display the sign-in form: here it is the new_ method
- a find_user method which will find a user in database matching a set of username and password provided by a query's parameters.

3.2 Sign in routes

The signin plugin also provides a helper to connect our session controller to the router:

app/routes.cpp
#include <crails/router.hpp>
#include "controllers/user.hpp"
#include "controllers/session.hpp"

void Crails::Router::initialize()
{
  resource_actions(user, UserController);

  // adds POST and DELETE routes for connecting and disconnected
  signin_actions("/session", SessionController);

  // adds a route to display our sign in form
  match_action("GET", "/session/new", SessionController, new_);
}

The signin_actions macro generates routes for creating and destroying a session. We added another route to handle the new_ action that will display our sign in view.

3.3 Sign in form

Let's now create the form that will display when going to http://localhost:3001/session/new. Create the following file:

app/views/session/new.html
<%= form({{"action","/session"},{"method","post"}}) yields %>
  <!-- Name input -->
  <div class="form-outline mb-4">
    <input type="text" name="name" class="form-control" />
    <label class="form-label" for="name">Username</label>
  </div>

  <!-- Password input -->
  <div class="form-outline mb-4">
    <input type="password" name="password" class="form-control" />
    <label class="form-label" for="password">Password</label>
  </div>

  <!-- Submit button -->
  <button type="button" class="btn btn-primary btn-block mb-4">
    Sign in
  </button>

  <!-- Register buttons -->
  <div class="text-center">
    <p>Not a member? <a href="/user/new">Register</a></p>
  </div>
<% yields-end %>

4. Session and current user

4.1 Fetch the current user

The users can connect, so we probably want to display data relative to each user. The signin plugin provides the AuthController class to help you interact with a session. Let's update our ApplicationController so that all our controllers may be aware of the current user:

app/controllers/application.hpp
#pragma once
#include <crails/controller.hpp>
#include <crails/odb/controller.hpp>
#include <crails/signin/auth_controller.hpp> // AuthController
#include "app/models/user.hpp"

/*
 * This is a chain of controller components: it's something you'll
 * use when you want to combine components from multiple crails
 * plugins.
 * In this case, we use Crails::AuthController for managing the
 * session, and Crails::Odb::Controller to provide a database
 * backend.
 */
typedef Crails::AuthController<
  User,
  Crails::Odb::Controller<Crails::Controller>
> ApplicationControllerBase;

class ApplicationController : public ApplicationControllerBase
{
protected:
  ApplicationController(Crails::Context& context) :
    ApplicationControllerBase(context)
  {
    vars["layout"] = std::string("layouts/application");
  }

public:
  /*
   * Overload the initialize method and call
   * user_session.get_current_user() to fetch the current
   * user (if there is one). Then share a pointer to the
   * current user with the views.
   */
  void initialize() override
  {
    std::shared_ptr<User> user = user_session.get_current_user();

    vars["current_user"] = user.get();
    ApplicationControllerBase::initialize();
  }
};

Since all the views can now see the current user, we'll update the layout to display the current user name at the top of each page. First, we add the current_user variable we shared earlier:

app/views/layouts/application.html
#include "lib/assets.hpp"
#include "app/models/user.hpp"

User* @current_user;
const char* @yield = nullptr;
// END LINKING
...

And we'll add a navbar with a greeting to the user:

app/views/layouts/application.html
...
      <% if (current_user) do %>
        <nav class="navbar navbar-expand-lg navbar-light bg-light">
          <span class="navbar-brand">
            Hello, <%= current_user->get_name() %> !
          </span>
        </nav>
      <% end %>
...

4.2 Disconnect the user

In the same navbar we just created, let's add buttons for the user to connect or disconnect:

<div style="float:right">
  <% if (current_user) do %>
    <%= link("/session",
             "Disconnect",
             {{"method","delete"},{"class","btn btn-primary"}}) %>
  <% end else do %>
    <%= link("/session/new",
             "Connect",
             {{"class","btn btn-primary"}}) %>
  <% end %>
</div>

And here we are: you can now connect or disconnect of an account from any page in the application !

4.3 Require a connected user

There are some spaces in your application that you may not wish to be accessible to users that aren't authenticated. The AuthController class provides some helper methods to quickly set up such restrictions.

We'll give it a try by making our UserController accessible only to logged users:

app/controllers/user.hpp
#pragma once
#include "app/controllers/application.hpp"
#include "app/models/user.hpp"

class UserController : public ApplicationController
{
public:
  UserController(Crails::Context&);

  // Add this method to make the magic happen:
  bool require_authenticated_user() const override { return true; }

  ...
};

With this tiny change, users trying to access the User controller will now receive a 401 Unauthorized response. The session check happens on controller initialization, so you're also guaranteed to have access to a non-null current_user in all the controllers actions,

But there's a problem ! Now that the controller is exclusively accessible to connected users, newcomers cannot access the user creation form at /user/new. To fix that issue, we'll rewrite require_authenticated_user with a condition that excludes user creation from our new restrictions:

app/controllers/user.hpp
#pragma once
#include "app/controllers/application.hpp"
#include "app/models/user.hpp"
#include <algorithm> // required for std::find

class UserController : public ApplicationController
{
public:
  UserController(Crails::Context&);

  // Add this method to make the magic happen:
  bool require_authenticated_user() const override
  {
    const std::vector<std::string> excluded_actions{"new_", "create"};

    return std::find(
      excluded_actions.begin(),
      excluded_actions.end(),
      params["controller-data"]["action"].as<std::string>()
    ) == excluded_actions.end();
  }

  ...
};

With these new changes, the new_ and create methods are no longer concerned by current user restrictions and will be available to non-connected users.

4.4 Redirect non-connected users

We've restricted most of the user controller to connected users... but when non-connected users try to access private contents, their browser merely receives a 401 response with no content.

This behavior works great for web services... but for web applications, a more appropriate behavior is to redirect users to the application's public space. To do so, we'll implement the on_user_not_authenticated method and override the default behavior for unauthenticated users:

app/controllers/user.hpp
#pragma once
#include "app/controllers/application.hpp"
#include "app/models/user.hpp"

class UserController : public ApplicationController
{
public:
  UserController(Crails::Context&);

  bool require_authenticated_user() const override;

  void on_user_not_authenticated() override
  {
    redirect_to("/session/new");
  }

  ...
};

5. What's next ?

You are now able to authentify your application users and accurately manage private and public content !

A good follow-up for this tutorial would be to implement OAuth using libcrails-oauth.