/ collar

Build an authentication server with collar.js + express + JWT

auth gif

You don't need a host server to run and test your backend modules. Collar.js dev tool provides a rich UI for your backend module. You can feed your backend module with input signal, and watch its output in collar dev server. What's more, you can see how your input is processed. You can even test your module directly from inside (by sending signal to a internal node).

In this post, we will build an authentication server with collar.js, express and json web token.

You will learn:

  1. how to build a module with collar.js
  2. how easy it is to debug backend module with collar.js
  3. how is collar.js compatible with your existing code

Preparation

Create a project folder, and initiate it with npm init. Then, install following dependencies:

npm install --save collar.js express jsonwebtoken body-parser
npm install --save-dev collar.js-dev-client

You need collar-dev-server to visualize your application, install it and run it at port 7500 (default port)

npm install collar-dev-server -g

collar-dev-server -p 7500

Build your application flow

Handle authentication event

Let's first build our application flow with collar.js: create appflow.js file in your project, and create authentication flow as following:

// import collar and enable collar dev tool
const collar = require("collar.js");
require("collar.js-dev-client");
collar.enableDevtool();  // enable dev tool
const ns = collar.ns("com.collarjs.example.auth");

const nonAuthInput = ns.input("auth-not-required input");
const output = ns.output("output");

// authentication event pipeline
nonAuthInput
  .when("'auth' event")
  .do("input validation check, or throw error")
  .do("get user from db, or throw error")
  .do("verify credential, or throw error")
  .map("generate token, or throw error")
  .to(output)

To make authenication, the user needs to send an 'auth' event to get an authentication token. This is handled by the 'auth' event pipeline.

Once an 'auth' event is received, we will first check if it is a valid request or not, for example, we can check if it contains email and password property, or if there are unexpected characters in it (to avoid injection attacks ).

If the request is validated, we will get the user profile from database according to the user name. With the returned user profile, we can compare the password in request with the one in user profile. If match, we generate a json web token and pass it to output.

Any error occurs during the authentication process will be threw and propagated to output.

You can visualize this process by running node appflow.js

auth flow 1

The auth event contains three fields:

{
  "event" : "auth",  // the event name
  "username" : "collarjs",  // the user identifier
  "password" : "collarjs123"  // the account password
}

To filter the auth event, we just check the event property:

.when("'auth' event", signal => signal.get("event") === "auth");

It is possible that the client send a bad request to the server. We need to validate the event before doing the authentication. In this example, we only check if the 'auth' event contains 'username' and 'password' field or not. You can add more security check here to avoid attacks (for example escape unwanted characters).

.do("input validation check, or throw error", signal => {
  var username = signal.get("username");
  var password = signal.get("password");
  
  // throw error, if no username or password found in signal
  if (!username || !password) throw new Error("Bad request");
  // otherwise pass to next node
})

Next, we get the user information from the database and put it in signal's result field. The implementation depends which database you choose to store your user information. To simplify the demo, an in-memory map is used as the user db.

.do("get user from db, or throw error", signal => {
  var username = signal.get("username");
  if (!users.hasOwnProperty(username)) {
    throw new Error("Unauthorized");
  }
  return users[username];
})

With the user profile in signal's result field, we can compare the password in it with the one in input event, and throw error if they do not match.

.do("verify credential, or throw error", signal => {
  var password = signal.get("password");
  var user = signal.getResult();
  if (password != user.password) {
    throw new Error("Unauthorized");
  }
  return user;
})

Finally, when everything is ok, a token is generated, and a token signal is emitted.

.map("generate token, or throw error", signal => {
  var user = signal.getResult();
  var token = jwt.sign(user, jwtSecret, {
    expiresIn: "24h" // expires in 24 hours
  });
  return signal.new({
    token : token
  });
})

Before we go to next step, let's test this flow first:

Run node appflow.js, and check collar dev server page, you will see the nodes in the flow are all white. It means that all of them are implemented.

Click "start recording" button, and "show send signal dialog" button. You will see a dialog named "Send Signal" pops up.

send signal dialog button

It allows you to send any signal to your flow from a specific node. Now let's click 'auth-not-required input' node (means we will send signal to it), and type following event in 'Send Signal' dialog's payload text field, and click send:

{
  "event" : "auth", 
  "username" : "collarjs",
  "password" : "collarjs123"
}

From the recorded signal sequence, you can see how the signal data changes and check if the token is generated and propagated to output node.

auth gif

You can play with different input signals to check if your implementation covers all the scenarios.

Access resource with authentication token

When a user want to access one resource, he must provide the token in his request. To serve such request, our application needs to verify the token first.

To demonstrate it, we build an example greeting resource. As authentication is required to access these resources, we create a new input endpoint called 'auth-required input' and receive events from it.

const authInput = ns.input("auth-required input");

authInput
  .when("'greeting'")
  .do("authenticate")
  .map("prepare greeting message")
  .to(output)

If you run appflow.js, you will see the following flow in collar dev server:

auth flow 2

We can implement the flow with following code:

authInput
  .when("'greeting'", signal => signal.get("event") === "greeting")
  .actuator("authenticate, or throw error", (signal, done) => {
    var token = signal.get("token");
    if (!token) throw new Error("Unauthorized");

    jwt.verify(token, jwtSecret, function(err, decoded) {
      if (err) {
        done(err);
      } else {
        console.log(decoded);
        done(null, decoded)
      }
    });
  })
  .map("prepare greeting message", signal => {
    var user = signal.getResult();
    return signal.new({
      greeting : "Hello, " + user.username
    })
  })
  .to(output)

Each resource accessing event must contain a token field with a valid json web token generated from our 'auth' pipeline.

We used actuator operator (async) instead of do operator (sync), because jwt.verify is an async function, we must use the async actuator to handle it. If the token is verified, the decoded user object is put to the signal's result field. The map operator get the user name from the user profile, and generate a greeting message.

Now let's test it with collar dev server:

Start recording in collar dev server, run appflow.js, and send the following message to the 'auth-required input' by using the 'Send Signal' dialog.

{
  "event" : "greeting",  
  "token": "the token you get from 'auth' pipeline"
}

You will get the following sequence of signal:

access auth resource

You can play it with a wrong token and see how error propagates:

access auth resource error

Use collar flow in express

Until now, we haven't talked about express yet. Actually, this is one of the advantages of using collar.js to build your backend. You don't need a host server to run and test your backend modules. Collar.js dev tool provides a rich UI for your backend module. You can feed your backend module with input signal, and watch its output in collar dev server. What's more, you can see how your input is processed. You can even test your module directly from inside (by sending signal to a internal node).

Once your backend module is implemented and well tested, you can use it in any host application. It could be a server, a desktop application, a web application or even a mobile application.

In this post, we will build a RESTful server with express. Let's first create a express host server:

index.js

var collar = require("collar.js");
var backend = require("./backend");
var express = require('express');
var app = express();
var bodyParser = require('body-parser');

app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

var port = process.env.PORT || 8080;

app.post("/login", (req, res) => {
});

app.get("/greeting", (req, res) => {
});

app.listen(port);
console.log('Magic happens on port ' + port);

In /login route, we need to send a 'auth' event to our auth flow, and respond according to the output of auth flow.

Collar.js provides a toNode(input, output) API to wrap a collarjs flow to a node.js style function. You need to pass two nodes to it, one 'input' node to accept input signals, and an 'output' node to get the output signal.

The return function is a node.js style function:

function nodeAPI(data, callback)

Let's first wrap our auth flow as a nodejs module

backend.js

module.exports = {
  login : collar.toNode(nonAuthInput, output),
  greeting : collar.toNode(authInput, output)
}

Now we can use these two methods in our server:

app.post("/login", (req, res) => {
  var username = req.body.username;
  var password = req.body.password;

  backend.login({
    event : "auth",
    username : username,
    password : password
  }, (err, result) => {
    if (err) {
      res.status(401).end(err.message);
      return;
    }
    res.json(result);
  });
});

app.get("/greeting", (req, res) => {
  var token = req.headers['x-access-token'];

  backend.greeting({
    event : "greeting",
    token : token
  }, (err, result) => {
    if (err) {
      res.status(401).end(err.message);
      return;
    }
    res.json(result);
  })
});

It's time to run your server, run node index.js and test it with Postman:

postman test 1

A token is correctly returned. Copy this token and make a GET to '/greeting' with the 'x-access-token' header:

You can get the greeting resource now:

postman test 2

If you use a wrong token, you will get an error response:

postman test 3

You can still test your module with collar dev server, until you comment out the collar.enableDevtool() in your code.

Collar.js collarjs.com

The source code can be found at github