An open API service indexing awesome lists of open source software.

https://github.com/paulsmithkc/express-auth

Collection of authentication and authorization middleware functions for Express apps.
https://github.com/paulsmithkc/express-auth

auth authentication authorization expressjs nodejs

Last synced: 4 months ago
JSON representation

Collection of authentication and authorization middleware functions for Express apps.

Awesome Lists containing this project

README

          

# express-auth

Collection of authentication and authorization middleware functions.

## Install

Install this package.

```bash
npm install @merlin4/express-auth
```

Install peer dependencies, if needed.

```bash
npm install express cookie-parser jsonwebtoken config
```

## Table of Contents

- Middleware
- [authMiddleware](#authmiddleware)
- [installation](#installation)
- [configuration](#configuration)
- [req.auth](#reqauth)
- [isLoggedIn](#isloggedin-middleware)
- [hasAnyRole](#hasanyrole-middleware)
- [hasRole](#hasrole-middleware)
- [hasPermission](#haspermission-middleware)
- Utility Functions
- [fetchRoles](#fetchroles-utility-function)
- [mergePermissions](#mergepermissions-utility-function)

## 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):

```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.)

```js
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()](https://expressjs.com/en/4x/api.html#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](https://www.npmjs.com/package/config) package to help set these options.

```js
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.

```js
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.
```js
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.
```js
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.
```js
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.
```js
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.
```js
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.
```js
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.
```js
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:**
```js
{ ...userData, role: null }
```

User has **a role:**
```js
{ ...userData, role: "admin" }
```
User has **an array of roles:**
```js
{ ...userData, role: ["admin", "manager"] }
```

User has **a map of roles:**
```js
{ ...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.
```js
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:

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

It will also read any permissions granted by a role, such as:
```js
[
{ name: 'Technical Manager', permissions: { manageUsers: true } },
{ name: 'Product Manager', permissions: { manageProducts: true } }
}
```

The combined permissions map would be as follows, for the data above:
```js
{
canLogin: true,
manageUsers: true,
manageProducts: true
}
```