part-7.jpg

Learn Sails.js - Part 7

🛈

The locks and coffers are in your charge. A ship carrying valuable cargo in open waters without guards is not a warship; it’s a target. It’s time to post the watch and secure this vessel.


Part 7: Guarding the Gangway (Authentication & Policies)

  • Current Rank: Quartermaster
  • Objective: To secure your application by implementing a robust user authentication system (login/logout) and using Policies to enforce access control rules.

The Captain’s Briefing (Background and Theory)

So far, our ship has an open-gangway policy. Anyone can wander aboard, read the Captain’s Log (/logbook), and even create new treasure using our API. This is untenable. We need to distinguish between an anonymous visitor and a verified member of the crew. This process involves two distinct concepts: Authentication and Authorization.

Authentication: “Show Me Your Papers”

Authentication is the process of verifying who someone is. On the web, this is the classic login form. The user provides credentials (like a name and password), and the server checks if they are valid.

How does the server remember the user after they log in? Through a Session. When a user logs in successfully, the server gives their browser a special, secret cookie that acts like a temporary crew pass. On every subsequent request, the browser sends this cookie back, and the server uses it to retrieve that user’s session data (e.g., their user ID), confirming they are still “logged in.”

A critical part of authentication is securely storing passwords. You must never, ever store plain-text passwords in your database. We will use a one-way cryptographic hashing algorithm (bcrypt) to turn passwords into a long, irreversible string of characters. When a user tries to log in, we hash the password they provided and compare it to the hash in our database. The passwords are never stored in a readable format.

Authorization with Policies: “Are You on the List?”

Authorization happens after authentication. Now that we know who the user is, we must decide what they are allowed to do. An Able Seaman can access the mess hall, but only the Quartermaster can access the cargo manifest.

In Sails, authorization is handled by Policies. A Policy is a small middleware function that you place in api/policies/. It’s a guard who stands at the gangway of a specific controller action. The guard checks the request (e.g., “Does this person have a valid session ID?”) and decides to either: 1. Let them pass by calling next(). 2. Deny them access by sending a response like res.forbidden().

You assign these guards to their posts in the config/policies.js file. sailsjs.com explains that this file acts as your application’s master Access Control List (ACL). You create a map that says, “To access this action, you must first pass through these policies.” This provides a powerful, centralized place to manage your entire application’s security rules. blog.sailscasts.com highlights the most common and powerful pattern: deny access to everything by default, then explicitly grant access to specific actions.


Key Concepts Checklist

  • Authentication: Verifying a user’s identity (e.g., login).
  • Authorization: Determining what an authenticated user is permitted to do.
  • Session: Server-side data linked to a browser cookie, used to “remember” a logged-in user.
  • Password Hashing: The non-reversible process of scrambling a password for secure storage. We’ll use the bcryptjs library.
  • Policy: A middleware function in api/policies/ that guards a route.
  • config/policies.js: The file where you assign policies to controllers and actions.
  • req.session: The object where you can store data for the current user’s session.
  • sails generate policy: The command to create a new policy file.

Mission Log: Quest - “Secure the Quarterdeck”

We will now lock down the Captain’s Log (/logbook). It will only be accessible to a logged-in Captain. We will create signup, login, and logout functions to manage this access.

  • Task 1: Prepare for Secure Passwords

    1. We need the bcryptjs library. From your terminal, inside your project folder, run:

      npm install bcryptjs --save
      
    2. Update your Captain model to store a secure password. Open api/models/Captain.js and add the encryptedPassword attribute.

      module.exports = {
        attributes: {
          name: { type: 'string', required: true, unique: true },
          rank: { type: 'string', defaultsTo: 'Captain' },
          encryptedPassword: { type: 'string', required: true },
      
          treasures: {
            collection: 'treasure',
            via: 'owner'
          }
        }
      };
      
  • Task 2: Assemble the Guard (Create the Policy)

    1. Generate the policy file:

      sails generate policy isLoggedIn
      
    2. Open api/policies/isLoggedIn.js and add this logic. This guard checks for a valid session.

      module.exports = async function (req, res, next) {
        // If the user isn't logged in...
        if (!req.session.userId) {
          // ...then forbid access and stop.
          return res.forbidden('You are not logged in.');
        }
        // Otherwise, if they are logged in, continue to the requested action.
        return next();
      };
      
  • Task 3: Post the Guard Duty Roster (Configure Policies)

    1. Open config/policies.js. This is where we tell our ship which areas need guarding.
    2. Replace the entire contents with this “default deny” configuration. We lock everything down, then open up only the specific routes needed for authentication.

      module.exports.policies = {
        // By default, require users to be logged in to access any action.
        '*': 'isLoggedIn',
      
        // Allow visitors to access the login, signup, and logout actions
        // in the AuthController.
        AuthController: {
          'login': true,
          'signup': true,
          'logout': true
        },
      
        // The default homepage should be accessible to all.
        // We target the specific route action for the homepage.
        'view-pages-homepage': true
      };
      
  • Task 4: Build the Authentication System

    1. Generate a new controller to handle login/logout/signup: sails generate controller AuthController.
    2. Open api/controllers/AuthController.js and fill it with these actions:

      const bcrypt = require('bcryptjs');
      
      module.exports = {
      
        signup: async function (req, res) {
          const { name, password } = req.allParams();
          if (!name || !password) {
            return res.badRequest('Name and password are required.');
          }
      
          const encryptedPassword = await bcrypt.hash(password, 10);
          const captain = await Captain.create({ name, encryptedPassword }).fetch();
      
          // Log the user in immediately after signup
          req.session.userId = captain.id;
      
          return res.ok(captain);
        },
      
        login: async function (req, res) {
          const { name, password } = req.allParams();
          if (!name || !password) {
            return res.badRequest('Name and password are required.');
          }
      
          const captain = await Captain.findOne({ name });
          if (!captain) {
            return res.unauthorized('Invalid credentials.');
          }
      
          const isPasswordMatch = await bcrypt.compare(password, captain.encryptedPassword);
          if (!isPasswordMatch) {
            return res.unauthorized('Invalid credentials.');
          }
      
          // If we made it here, the credentials are valid. Log the user in.
          req.session.userId = captain.id;
      
          return res.ok('Login successful.');
        },
      
        logout: async function (req, res) {
          req.session.destroy(function (err) {
            if (err) { return res.serverError(err); }
            return res.ok('Logout successful.');
          });
        }
      
      };
      
  • Task 5: Chart the Authentication Routes

    1. Open config/routes.js and add the new routes, preferably near the top. We use POST as it’s the correct verb for sending data to create sessions or users.

      'POST /signup': 'AuthController.signup',
      'POST /login': 'AuthController.login',
      'GET /logout': 'AuthController.logout',
      
  • Task 6: Test Your Security

    1. Restart your server with sails lift.
    2. In your browser, first try to access http://localhost:1337/logbook. You will be blocked! The isLoggedIn policy is working.
    3. This time, we need to use curl or Postman to test the POST routes properly. From a new terminal:
    4. Sign up a new Captain:

      curl -X POST http://localhost:1337/signup --data "name=AnneBonny&password=secretpassword" -c cookies.txt
      

      The -c cookies.txt command saves the session cookie from the server.

    5. Attempt to access the logbook with your credentials:

      curl http://localhost:1337/logbook -b cookies.txt
      

      Success! Because you sent the cookie back (-b cookies.txt), the server knew who you were and the policy let you pass. You should see the HTML for the logbook page.


Mission Debrief (Review & Outcomes)

Excellent work, Quartermaster. The gangway is secure. The ship is no longer open to just any port visitor.

You have successfully implemented a professional-grade authentication and authorization system. You learned how to securely handle passwords, manage user sessions, and use policies as intelligent guards for your application’s sensitive areas. By adopting the “default deny” strategy in config/policies.js, you have established a robust security posture that will serve you well as your application grows in complexity. The ship and its cargo are now safe under your watch.


Rewards

  • +300 Doubloons
  • Achievement Unlocked: You have mastered the arts of authentication and authorization, securing the ship from unauthorized access.
    • Badge Earned: The Gatekeeper 🔑