Build a SailsJS App: From API to Authentication

Build a SailsJS App: From API to Authentication

Free Course

Build Your First Node.js Website

Node is a powerful tool to get JavaScript on the server. Use Node to build a great website.

TL;DR There are lots of JavaScript frameworks out there and it can be overwhelming to keep up with all of them. SailsJs is a Node.js MVC framework. Currently, SailsJs has over 15,000 stars on GitHub. In this tutorial, I'll show you how easy it is to build a web application with SailsJs and add authentication to it. Check out the repo to get the code.

SailsJs is a Node.js framework that is designed to emulate the familiar MVC pattern of frameworks like Ruby on Rails and Laravel, but with support for modern apps architecture such as building data-driven APIs and realtime apps. SailsJs saves you time and effort because it is designed for building practical, production-ready Node.js apps in a matter of weeks and ships with a lot of features out of the box. These features include the following:

  • Waterline ORM
  • Rest APIs Generator
  • WebSocket Support
  • Policies ( Effective role-based access control)
  • Flexible Asset Pipeline

SailsJs makes good use of already written and well-tested modules from the JavaScript community. The documentation is very detailed, and there is a vibrant community of users and collaborators who engage on Gitter, Google Forum, and IRC.

What We'll Build: A Quote Listing App

We'll be building a simple application that retrieves Chuck Norris quotes from a NodeJS backend with SailsJs. Scratch that, we’ll build our own Chuck Norris API. Once we add authentication to the app, all logged-in users will have the privilege of getting access to a set of private quotes.

Exploring the Directory Structure

A typical SailsJS application has the following structure:

├── api │ ├── controllers │ ├── models │ ├── policies │ ├── responses │ └── services ├── assets │ ├── images │ ├── js │ │ └── dependencies │ ├── styles │ └── templates ├── config │ ├── env │ └── locales ├── node_modules │ ├── ejs │ ├── grunt │ ├── grunt-contrib-clean │ ├── rc │ ├── sails │ └── sails-disk ├── tasks │ ├── config │ └── register └── views

The api directory constitutes the “M” and “C” in a typical MVC framework. In a nutshell,

  • Models query your database and return the necessary data.
  • Views are pages that render data. In SailsJs, they are .ejs template files.
  • Controllers handle user requests, retrieve data from the models and pass them on to the views.
  • Policies are used to restrict access to certain parts of your app. It can be used for anything: HTTP Basic Auth, 3rd party Single Sign on, OAuth 2.0 or your own custom authorization scheme. Services are more like helpers that you can write once and use anywhere in your app. They are globalised by default, so you don’t need to require them.

Let's Get Started.

SailsJs uses Npm to manage its dependencies. So, before using SailsJs, make sure you have node and npm installed on your machine. We can install the latest release of SailsJs from the command line like so:

npm -g install sails

Once it is installed, we can now create a new app from the command line like so:

sails new app
cd app
sails lift

sails lift starts the web server and points you to the url where the app is currently been served.

Read more about the anatomy of a sails app here

Setting Up the Routes

Sailsjs has a beautiful way of quickly generating API routes and actions based on your application design called Blueprints. The SailsJs Blueprint API powers the restful API that is obtained anytime a model and controller is created. For example, if you create a Task.js model and TaskController.js controller file in your project, blueprints will give you the ability to visit this url /task/create?id=1&name=code to create a new task. The local sails-disk db is used by default, so you don’t even have to worry about a data store just yet. Wow, just awesome!!!!

Blueprints are great for prototyping. They can be easily enabled and disabled anytime.

SailsJs ships with a scaffolding tool that you can use to generate your controller and model files easily with for an api resource.

Running the command above creates a Task.js model and a TaskController.js controller file. For this project, we’ll manually create our files and routes. Open up config/routes.js and add this to the routes like so:

'get /api/random-quote': 'QuoteController.getQuote',
'get /api/protected/random-quote': 'QuoteController.getProtectedQuote'

Once a user hits those routes, the QuoteController is invoked, then the respective actions are responsible for processing the incoming requests and returning a response back to the client. Next, let’s set it up!

Setting Up the Controllers

Open up api/controllers directory and create a QuoteController.js file like so:

 * QuoteController
 * @description :: Server-side logic for managing quotes
 * @help        :: See!/documentation/concepts/Controllers

module.exports = {
    getQuote: function(req, res) {
        return res.json({ quote: quoter.getRandomOne() });

    getProtectedQuote: function(req, res) {
        return res.json({ quote: quoter.getRandomOne() });

By convention, Sails controllers are Pascal-based. For example, you must define your controllers like the following: TaskController.js, ItemController.js. If you have experience with using ExpressJs, you will discover that the syntax is very familiar. Well, SailsJs is built on top of ExpressJs!

Note: You can also scaffold your controllers with action names like so:

Now, where is quoter.getRandomOne coming from? quoter is a custom Sails service, getRandomOne is a method from the service. Let’s go ahead and create the service.

Setting up the Service

Services are simply functions( helpers ) that you can use anywhere in your app. They are globalized by default like I mentioned earlier. Open up api/services directory and create a quoter.js file like so:

var quotes = require('./quotes.json');

module.exports.getRandomOne = function() {
  var totalAmount = quotes.length;
  var rand = Math.floor(Math.random() * totalAmount);
  return quotes[rand];

Create a quotes.json file within that directory and dump this array of quotes in it. It’s that simple!!!

Let’s test our API routes with Postman. In my opinion, Postman is one of the best tools on the planet for testing your APIs during development. Run sails lift from your console and put the API url in your postman like so:

Do the same for /api/protected/random-quote. We can now access our quotes from these two routes. The next phase is to protect the second route /api/protected/random-quote. We need some form of Authorization that will run as a middleware to verify that a particular user can access that route. SailsJS has an inbuilt Access-control-logic system called Policies.

Setting Up Policies

Policies are simply Sails way of authorizing and granting access control. They let you allow or deny access to your controllers. Open up api/policies directory and create the file isAuthenticated.js like so:

 * isAuthenticated
var jwt = require('express-jwt');

var authCheck = jwt({
  secret: new Buffer('AUTH0_CLIENT_SECRET', 'base64'),
  audience: 'AUTH0_CLIENT_ID'

module.exports = authCheck;

Make sure you quickly npm install express-jwt to ensure that module exists. Auth0 is an authentication and authorization platform that you can use to easily secure your APIs. When signing up for an account, you will need to give your app a domain name which cannot be changed later. Since you can have multiple apps under the same account, how you name your domain will depend on your situation. In most cases, it’s best to name it with something that is relevant to your organization, such as your company’s name. If it makes sense, you can also use your application’s name–it’s up to you.

Your Auth0 domain takes the pattern and is used when configuring the Auth0 tools that we’ll see below. Once you’ve signed up, you’ll be asked what kind of authentication you’d like for your application. It’s fine to leave the defaults in place, as you’ll be able to change them later. After you have signed up, head over to your dashboard to check things out. If you click the Apps / APIs link in the left sidebar, you’ll see that your account gets created with a Default App. Click the Default App to see your credentials and other details.

So, replace AUTH0_CLIENT_ID and AUTH0_CLIENT_SECRET with the real values. Next, we need to map the isAuthenticated policy to the QuoteController.

Note: Allowed Callback URLS and Allowed Origins should also be filled with the proper URLS. If you are running on port 3000, it will be http://localhost:3000/

Open up config/policies.js and add this like so:

module.exports.policies = {

  *                                                                          *
  * Default policy for all controllers and actions (`true` allows public     *
  * access)                                                                  *
  *                                                                          *

  '*': true,

  QuoteController: {
    getProtectedQuote: 'isAuthenticated'


So, the isAuthenticated policy will run just before the getProtectedQuote action of the QuoteController is invoked.

Now, try to access /api/protected/random-quote without a valid token!

Note: Open up config/cors.js and make sure you it is configured like this to avoid CORS errors & preflight request errors.

Aha!, We were denied access. Auth0 has secured our API. Next, let’s build the frontend to communicate with the API.

Build The Frontend

We’ll use Angular 1.x to quickly build out the frontend to communicate with our quotes API.

Within the project directory, create a folder called angular. Move into the directory & run npm init to create a package.json file. Now, go ahead and install the following dependencies

# Angular related modules
npm install angular angular-material angular-ui-router angular-aria angular-animate

# Auth0 related modules
npm install angular-jwt angular-storage auth0-angular

# To serve the app (if not already installed)
npm install -g http-server

Next, let’s set up our app.js and index.html files to bootstrap the application. At this time we can let Angular know which modules we need access to from the dependencies we installed.

(function() {

  'use strict';

    .module('authApp', ['auth0', 'angular-storage', 'angular-jwt', 'ngMaterial', 'ui.router'])
    .config(function($provide, authProvider, $urlRouterProvider, $stateProvider, $httpProvider, jwtInterceptorProvider) {

        domain: 'YOUR_AUTH0_DOMAIN',
        clientID: 'YOUR_AUTH0_CLIENT_ID'

Here we’ve configured authProvider from auth0-angular with our credentials from the dashboard. Of course, you’ll want to replace the values in the sample with your own credentials.

<!DOCTYPE html>
<html lang="en" ng-app="authApp">
  <meta charset="UTF-8">
  <title>Get Chuck Norris Quotes</title>
  <link rel="stylesheet" href="node_modules/angular-material/angular-material.css">
  <!-- Auth0 Lock script and AngularJS module -->
  <script src="//"></script>

  <!-- Setting the right viewport -->
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />

  <!-- TODO: create the toolbar and ui-view -->

  <script src="node_modules/angular/angular.js"></script>
  <script src="node_modules/angular-animate/angular-animate.js"></script>
  <script src="node_modules/angular-aria/angular-aria.js"></script>
  <script src="node_modules/angular-material/angular-material.js"></script>
  <script src="node_modules/angular-jwt/dist/angular-jwt.js"></script>
  <script src="node_modules/angular-storage/dist/angular-storage.js"></script>
  <script src="node_modules/auth0-angular/build/auth0-angular.js"></script>
  <script src="node_modules/angular-ui-router/release/angular-ui-router.js"></script>

Notice here that we’re bringing in the Auth0Lock widget script from Auth0’s CDN. This is the script that we’ll need to show the login box that Auth0 provides. By setting the viewport like we have, we make sure that the widget shows up properly on mobile devices.

Set Up the Toolbar

We will create a top navbar with Angular Material’s toolbar component. We’ll wrap this up into its own directive to keep things clean.

// components/toolbar/toolbar.dir.js

(function() {

  'use strict';

    .directive('toolbar', toolbar);

  function toolbar() {
    return {
      templateUrl: 'components/toolbar/toolbar.tpl.html',
      controller: toolbarController,
      controllerAs: 'toolbar'

  function toolbarController(auth, store, $location) {
    var vm = this;
    vm.login = login;
    vm.logout = logout;
    vm.auth = auth;

    function login() {
      // The auth service has a signin method that
      // makes use of Auth0Lock. If authentication
      // is successful, the user's profile and token
      // are saved in local storage with the store service
      auth.signin({}, function(profile, token) {
        store.set('profile', profile);
        store.set('token', token);
      }, function(error) {

    function logout() {
      // The signout method on the auth service
      // sets isAuthenticated to false but we
      // also need to remove the profile and
      // token from local storage


In the login method, auth.signin is responsible for opening Auth0’s Lock widget. This is awesome–with a single method, we have a fully-functioning login box! The callback gives us access to the user’s profile and JWT on a successful login or signup, and we need to store these in local storage for use later. The logout method simply removes the profile and token from local storage and uses auth.signout to set the user’s authentication state to false.

We now need a template for this directive.

  <div class="md-toolbar-tools">
      <span>Get Chuck Norris Quotes</span>
    <span flex></span>





Notice here that we are conditionally showing and hiding the three buttons based on the user’s isAuthenticated state. Let’s now drop this directive into our index.html file to see it at work. At the same time, we’ll drop in some other scripts that we’ll need later.

<!--- index.html -->



  <div layout-padding>


  <script src="app.js"></script>
  <script src="components/toolbar/toolbar.dir.js"></script>
  <script src="components/profile/profile.ctr.js"></script>


Start the server and navigate to http://localhost:8080/ . We can now test this out by clicking the Login button and either signing up for an account, or signing in with an account we create in our Auth0 dashboard.

Once authentication is complete, we can see that the profile and JWT for the user have been saved in localStorage. Now we can use these for the user’s profile area and to make authenticated requests.

Set Up Routing

We’ll use UI Router. Let’s make a /profile route that will take the user to their profile page and simply display some of the data that is saved in localStorage. We want this route to be protected so that if the user isn’t authenticated, they aren’t able to navigate to it. We’ll also set up a /home route so that the user is redirected to somewhere meaningful if they are logged out. First, let’s set up our home and profile components.

<!-- components/home/home.tpl.html -->

  <h1>Welcome to the Angular Auth app!</h1>
  <h3>Login from the toolbar above to access your profile.</h3>

We’ll also set up the profile view and controller with methods to make HTTP calls to the SailsJS API.

<!-- components/profile/profile.tpl.html -->
<md-content class="md-padding" layout="column">
        <div class="md-media-lg card-media" layout-padding><img ng-src="{{ user.profile.picture }}"></div>
        <md-card-actions layout="column" layout-align="end center">
          <md-button ng-click="user.getPublicQuote()">Get Public Quote </md-button>
        <md-button ng-click="user.getSecretQuote()">Get Secret Quote</md-button>
      <md-card-title-text layout-padding>
        <span class="md-headline">{{ user.profile.nickname }}</span>
        <span class="md-subhead">{{ }}</span>
        <h3>{{ user.message }}</h3>
// components/profile/profile.ctr.js

(function() {

  'use strict';

    .controller('profileController', profileController);

  function profileController($http) {

    var vm = this;
    vm.getPublicQuote = getPublicQuote;
    vm.getSecretQuote = getSecretQuote;

    vm.profile = JSON.parse(localStorage.getItem('profile'));

    // Makes a call to a public API route that
    // does not require authentication. We can
    // avoid sending the JWT as an Authorization
    // header with skipAuthorization: true
    function getPublicQuote() {
      $http.get('http://localhost:1337/api/random-quote', {
        skipAuthorization: true
      }).then(function(response) {
        vm.message =;

    // Makes a call to a private endpoint that does
    // require authentication. The JWT is automatically
    // sent with HTTP calls using jwtInterceptorProvider in app.js
    function getSecretQuote() {
      $http.get('http://localhost:1337/api/protected/random-quote').then(function(response) {
        vm.message =;



The next thing we need to do is set up our routing. At the same time, we’ll set some configuration that will automatically attach the JWT as an Authorization header when making HTTP calls.

// app.js


.config(function(...) {



    .state('home', {
      url: '/home',
      templateUrl: 'components/home/home.tpl.html'
    .state('profile', {
      url: '/profile',
      templateUrl: 'components/profile/profile.tpl.html',
      controller: 'profileController as user'

  jwtInterceptorProvider.tokenGetter = function(store) {
    return store.get('token');



Here we have provided some routing configuration for $stateProvider and have defaulted to the home state when the profile state isn’t matched. The jwtInterceptorProvider is the HTTP interceptor that takes care of attaching the user’s JWT as an Authorization header on each request. For any HTTP request that is made, Angular will intercept it before it goes out and attach whatever is returned from the tokenGetter function which, in this case, is the user’s JWT from local storage.

Getting the Quotes

Now, open a new console and make sure you are in the root directory of the sails project. Run sails lift to ensure the SailsJs server is running.

Log in, and click on the Profile page, then click on the Get Public Quotes and Get Private Quotes buttons. We can retrieve both set of quotes successfully.

At the bottom of the picture above, you can see how the token is attached with the Bearer to the Authorization header. It is sent on every request.

Redirect User on Unauthorized Request

If a user makes a request to our API with an expired or invalid JWT,a blank screen will be presented to the user. Let’s use an HTTP interceptor to look for any 401 errors returned in responses and then redirect the user to the /home route if it finds any.

// app.js


.config(function(...) {


function redirect($q, $injector, auth, store, $location) {
  return {
    responseError: function(rejection) {

      if (rejection.status === 401) {
      return $q.reject(rejection);
$provide.factory('redirect', redirect);


The redirect function is used to check for a rejection.status of 401 on any responses that come back from HTTP requests. If one is found, we use auth.signout to set isAuthenticated to false, remove the user’s profile and JWT, and take them to the home state.

Set Up Page Refreshing

The last thing we need to take care of is page refreshing. Because everything we’ve done thus far relies on some state being persisted in the user’s browser, things will get messed up if they refresh the page. For example, if we log in and then refresh the page, the isAuthenticated boolean value that gets set on login isn’t persisted, and thus our Login button comes back, even though we are actually authenticated.

We can fix this by doing a check for the user’s JWT on $locationChangeStart. // app.js


.run(function($rootScope, $state, auth, store, jwtHelper, $location) {

  $rootScope.$on('$locationChangeStart', function() {
    // Get the JWT that is saved in localStorage
    // and if it is there, check whether it is expired.
    // If it isn't, set the user's auth state
    var token = store.get('token');
    if (token) {
      if (!jwtHelper.isTokenExpired(token)) {
        if (!auth.isAuthenticated) {
          auth.authenticate(store.get('profile'), token);
    else {
      // Otherwise, redirect to the home route



The callback in $locationChangeStart gets evaluated every time the page is refreshed, or when a new URL is reached. Inside the callback we are looking for a saved JWT, and if there is one, we check whether it is expired. If the JWT isn’t expired, we set the user’s auth state with their profile and token. If the JWT is expired, we redirect to the home route. Normally, we would be able to test out these redirections by going to the /profile route and removing the JWT from localStorage. If we were to then send a request to the protected API endpoint, we would be redirected to the home route because no JWT would be sent with the request, resulting in a 401.


With the help of SailsJS, AngularJS and Auth0, we have been able to spin up a functional and secure API. We have also been able to build a complete app with authentication. When trying to flesh out APIs with ease, SailsJS is the go-to Nodejs framework.

Prosper Otemuyiwa

Food Ninja. Code Slinger and Self-Acclaimed Developer Evangelist