Authentication

Authentication is done through a client side (http-only) cookie. You need to activate the authentication inside src/manifest.json, by setting enabled true:

src/manifest.json

"app-authentication": {
    "enabled": false,
    "onlySsl": true,
    "session-cookie": false,
    "ttl-sec": 1800,
    "loginView": "login",
    "strategies": [
        {
            "strategy": "",
            "loginView": "login"
        },
        {
            "strategy": "general2",
            "validateFunc": "src/authentication/validate"
        }
    ]
}

After that, it takes 4 steps to enable a full featured authenticated web app:

    1. Define routes for authentication
    1. Define rules for who is authenticated
    1. Enable and disable users for being authenticated
    1. Define a fall-back view for visiters who access a page without being authenticated

Setting up your page for authentication

1. Define inside the router if a route should be authenticated

Example src/routes.js with authentication

const routes = [
    {
        method: 'GET',
        path: '/private-page1',
        config: {
            auth: {
                strategy: 'general'
            },
            handler: function(request, reply) {
                reply.reactview('management/rsi');
            }
        }
    },

    {
        method: 'GET',
        path: '/private-page2',
        config: {
            auth: {
                strategy: 'general',
                scope: 'admin'
            },
            handler: function(request, reply) {
                reply.reactview('management/rsi');
            }
        }
    }
];

module.exports = routes;

Every route needs a auth.strategy which should match a strategy that is defined in manifest.json.

2. Define rules for who is authenticated

This can be done by 2 different ways, which can be combined:

A. By giving all users a scope at their athentication-cookie
B. By making use of a validateFunc, which location is set in manifest.json

A. Using scope

This is the most easiest way and is handy when you do not need to finegrain authentication per single user specifically. You just need to give the users a scope property at their authentication-cookie. If the scope mathes the route, the get authenticated.

B. Using validateFunc

If you have defined a validateFunc for a strategy (inside src/manifest.json), then every request to a route that has this strategy, will be passed through to the validateFunc.

The validateFn should be a CommonJS module that might looks like this:

Example validateFn

const r = require('rethinkdb');

const validate = async function(request, reply, authCookie) {
    let validated = false,
        connection, cookieProps, record, userId;

    if (authCookie.isLoggedIn()) {
        try {
            cookieProps = authCookie.getProps();
            userId = cookieProps.id;
            if (userId) {
                connection = await r.connect(databaseConfig);
                record = await r.table('users').get().run(connection);
                validated = (cookieProps.password===record.password) && !record.userBlocked;
            }
        }
        catch (err) {
            console.warn(err);
        }
        // connection might not exists when an error is thrown during r.connect()
        // check this before you close the connection
        if (connection) {
            connection.close();
        }
    return validated;
};

module.exports = validate;

The validateFn will be invoked by itsa-react-server with 3 arguments. The third is the authentication cookie of the request.

    1. Enable and disable users for being authenticated

In order to manage the authentication cookie, there are 3 special methods available:

  • request.getAuthCookie()
  • reply.login()
  • reply.logout()

Enable and disable a user from being authentication should be done by sending a from and handle it by an Action.

Example login Action

const r = require('rethinkdb');

const actionFn = async (request, reply, options, language, manifest) => {
    let status, message, connection, records, user;
    const payload = request.payload,
        email = payload.email,
        password = payload.password,
        stayLoggedin = payload.stayLoggedin;

    try {
        connection = await dbLineaService.getConnection();
        records = await r.table('users')
                         .getAll(email, {index: 'email'})
                         .limit(1)
                         .run(connection);
        user = records[0];
    }
    catch (err) {
        console.warn(err);
    }
    // connection might not exists when an error is thrown during r.connect()
    // check this before you close the connection
    if (connection) {
        connection.close();
    }

    if (user && (user.password===password)) {
        status: 'OK',
        reply.login({
            id: user.id,
            password: user.password,
            scope: user.scope
        }, !stayLoggedin);
    }
    else {
        status: 'ERROR',
        message: 'invalid login'
    }

    return {
        status,
        message
    };
};

module.exports = actionFn;

Example logout Action

const actionFn = async (request, reply, options, language, manifest) => {
    reply.logout();
    return {status: 'OK'};
};

module.exports = actionFn;

    1. Define a fall-back view for visiters who access a page without being authenticated

Whenever a user visits a page and has no authentication, itsa-react-server will return the view that is defined by app-authentication.loginView (inside src/manifest.json). This should act as a login form where the user can enter its credentials to access the page. If the login-action returns with {status: 'OK'}, then the web app will retry to access the original page and returns the authenticated view.

OFFLINE