0.0.5 • Published 2 years ago

@merlin4/express-auth v0.0.5

Weekly downloads
-
License
MIT
Repository
github
Last release
2 years ago

express-auth

Collection of authentication and authorization middleware functions.

Install

Install this package.

npm install @merlin4/express-auth

Install peer dependencies, if needed.

npm install express cookie-parser jsonwebtoken config

Table of Contents

authMiddleware

installation

The authentication middleware will parse a Json Web Token (JWT) provided by the client, via either an "Authorization" header with type "Bearer", or optionally via a cookie.

Install this middleware in your application entry point (index.js/server.js/app.js):

const { authMiddleware } = require('@merlin4/express-auth');
app.use(
  authMiddleware('my super secret key', 'authToken', {
    httpOnly: true,
    maxAge: 1000 * 60 * 15,
  })
);

configuration

The constructor for this middleware accepts 3 arguments:

  1. secret the secret used to sign the JWT token (required)
  2. cookieName the name of the cookie that holds the token (optional)
  3. cookieOptions the options to use for refreshing the token (optional)

The secret is required to verify the token. This middleware will throw an exception on startup, if it not provided.

The cookieName will be used to parse the token from a cookie, when no "Authorization" header is provided. Note that you will need to install a cookie parser beforehand to parse the cookies. (Example shown below.)

const cookieParser = require('cookie-parser');
const { authMiddleware } = require('@merlin4/express-auth');
app.use(cookieParser());
app.use(authMiddleware('my super secret key', 'authToken'));

If cookieName is not provided, then this middleware will ignore cookies and a cookie parser is not required.

If cookieOptions are also provided, then the auth cookie will be refreshed on every request, using the provided options. For security it is recommended that you provide the following options:

  • maxAge, will set an expiration time in milliseconds for the auth cookie.
  • httpOnly: true, will prevent the cookie from being read by front-end javascript code.
  • secure: true, will prevent the cookie from being sent over HTTP.

See the res.cookie() for a full list of the available options.

If cookieName or cookieOptions are not provided, then the cookie will not be automatically refreshed.

We recommend using the config package to help set these options.

const config = require('config');
const cookieParser = require('cookie-parser');
const { authMiddleware } = require('@merlin4/express-auth');
app.use(cookieParser());
app.use(
  authMiddleware(
    config.get('auth.secret'), 
    config.get('auth.cookie.name'), 
    {
      maxAge: parseInt(config.get('auth.cookie.maxAge')),
      httpOnly: config.get('auth.cookie.httpOnly'),
      secure: config.get('auth.cookie.secure'),
    }
  )
);

req.auth

Once the middleware is installed and configured in the application. A new field will be accessible on the Request object to your other routes.

app.get('/api/auth/me', (req, res) => {
  const auth = req.auth;
  // If a valid auth token is provided, then req.auth will be the token's payload.
  // Otherwise req.auth, will be be undefined.
  // ...
});

isLoggedIn middleware

Requires authMiddleware to be installed first.

The isLoggedIn middleware checks that the user is logged in. It will send a 401 error if the user is not authenticated.

Example: Allow authenticated users to access the route.

const { isLoggedIn } = require('@merlin4/express-auth');

app.get('/api/auth/me', isLoggedIn(), (req, res) => {
  const auth = req.auth;
  // ...
});

hasAnyRole middleware

Requires authMiddleware to be installed first.

The hasAnyRole middleware checks that the user is logged in and has at least one role. It will send a 401 error if the user is not authenticated. It will send a 403 error if the user has no roles.

Example: Allow users with any role to access the route.

const { hasAnyRole } = require('@merlin4/express-auth');

app.get('/api/auth/me', hasAnyRole(), (req, res) => {
  const auth = req.auth;
  const authRole = req.auth.role;
  // ...
});

hasRole middleware

Requires authMiddleware to be installed first.

The hasRole middleware checks that the user is logged in and has one of the allowed roles. It will send a 401 error if the user is not authenticated. It will send a 403 error if the user does not have one of the allowed roles.

Example: Allow admins to access this route.

const { hasRole } = require('@merlin4/express-auth');

app.get('/api/admin', hasRole('admin'), (req, res) => {
  const auth = req.auth;
  const authRole = req.auth.role;
  // ...
});

Example: Allow users with either the admin or moderator role to access this route.

const { hasRole } = require('@merlin4/express-auth');

app.get('/api/mod', hasRole('admin', 'moderator'), (req, res) => {
  const auth = req.auth;
  const authRole = req.auth.role;
  // ...
});

hasPermission middleware

Requires authMiddleware to be installed first.

The hasPermission middleware checks the user has at least one of the listed permissions. It will send a 401 error if the user is not authenticated. It will send a 403 error if the user does not have one of the listed permissions.

Example: Allow users with the manageUsers permission to access this route.

const { hasRole } = require('@merlin4/express-auth');

app.get('/api/user/list', hasPermission('manageUsers'), (req, res) => {
  const auth = req.auth;
  const permissions = req.auth.permissions;
  // ...
});

Example: Allow users with either the viewUsers or editUsers permission to access this route.

const { hasRole } = require('@merlin4/express-auth');

app.get('/api/user/list', hasPermission('viewUsers', 'editUsers'), (req, res) => {
  const auth = req.auth;
  const permissions = req.auth.permissions;
  // ...
});

fetchRoles utility function

The fetchRoles function will read the assigned roles from a user document and fetch all of roles from a data source, in parallel.

Example: Fetch the user from the database, and then fetch all of the assigned roles.

const { fetchRoles } = require('@merlin4/express-auth');
const user = await db.findUserById(userId);
const roles = await fetchRoles(user, role => db.findRoleByName(role));

This function assumes that the user has a "role" field which specifies their role(s). The user object may be in any of the following states:

User has no role:

{ ...userData, role: null }

User has a role:

{ ...userData, role: "admin" }

User has an array of roles:

{ ...userData, role: ["admin", "manager"] }

User has a map of roles:

{ ...userData, role: { admin: true, manager: true, editor: false } }

All of the above data structures are supported by fetchRoles

mergePermissions utility function

The mergePermissions function will merge the permissions of the user and their assigned roles into a combined permissions map.

Example: Fetch the user from the database, fetch all of the assigned roles, and then merge all of the permission maps.

const { fetchRoles, mergePermissions } = require('@merlin4/express-auth');
const user = await db.findUserById(userId);
const roles = await fetchRoles(user, role => db.findRoleByName(role));
const permissions = mergePermissions(user, roles);

This will read any user specific permissions from the user document, such as:

{ ...userData, permissions: { canLogin: true } }

It will also read any permissions granted by a role, such as:

[
  { name: 'Technical Manager', permissions: { manageUsers: true } },
  { name: 'Product Manager', permissions: { manageProducts: true } }
}

The combined permissions map would be as follows, for the data above:

{
  canLogin: true,
  manageUsers: true,
  manageProducts: true
}