npm package: @sapphirecode/auth-server-helper
Description: authentication middleware for node http and express
Author: Timo Hocker
Latest version: 4.1.1

# auth-server-helper

version: 4.1.x

customizable and simple authentication

## Installation

npm:

> npm i --save auth-server-helper

yarn:

> yarn add auth-server-helper

## Usage

### 1. put a gateway in front of the routes you want to secure

```js
const {create_gateway} = require('@sapphirecode/auth-server-helper');

const gateway = create_gateway({
  redirect_url: '/auth', // if defined, unauthorized requests will be redirected
  cookie: { name: 'auth_cookie' }, // if defined, access tokens will be read from or written to this cookie,
  refresh_cookie: { name: 'refresh_cookie' }, // if defined, refresh tokens will be read and used to automatically refresh client tokens (requires the refresh_settings attribute)
  refresh_settings: {
    // same as settings for allow_access under section 2
    // the options data, redirect_to and leave_open are not supported here
  }
});

// express
app.use(gateway);

// node http
http.createServer((main_req, main_res) =>
  gateway(main_req, main_res, (req, res) => {
    // your request handler
  });
);
```

the gateway will forward any authorized requests to the next handler and
redirect all others to the specified url

#### 1.1. Creating a gateway for manual processing of requests

```js
const {GatewayClass} = require('@sapphirecode/auth-server-helper');

const gateway = new GatewayClass({ /* options */ }); // options are the same as for create_gateway above

// process a request
if (gateway.authenticate(http_request)) { // returns true if request is valid and sets req.connection.token_id and .token_data
  console.log('access granted');
} else {
  gateway.redirect(response); // redirects the client, triggers deny if no redirect_url was set in options
  // or
  gateway.deny(response); // sends status 403
}
```

### 2. creating the auth endpoint

```js
const {create_auth_handler} = require('@sapphirecode/auth-server-helper');

const handler = create_auth_handler(
  async (req) => {
    if (req.user === 'foo' && req.password === 'bar')
      const {access_token_id, refresh_token_id} = await req.allow_access({
        access_token_expires_in: 600, // seconds until access tokens expire
        include_refresh_token: true, // should the answer include a refresh token? default: false
        refresh_token_expires_in: 3600, // seconds until refresh tokens expire (required if refresh tokens are generated)
        data: {user: 'foo'}, // additional custom data to include in the token
      });

    if (req.user === 'part' && req.password === 'baz')
      const part_id = await req.allow_part(
        60, // seconds until part_token expires
        'some_module', // next module handler (defined below)
        {foo: 'bar'} // custom data to attach to the token
      );

    // all allow_ functions return a token id, which can later be used to invalidate specific tokens from the server side

    req.deny();
  },
  {
    refresh: {
      /*...same options as allow_access */
    }, // define the behaviour of refresh tokens. Refresh tokens will not be accepted if this option is undefined
    modules: {
      some_module(req) {
        // request handlers for part_tokens

        // access custom data:
        const auth_data = req.request.connection.auth;
        auth_data.token_id; // token id
        auth_data.token_data; // custom data
        // the same works in handlers after the gateway, information is always stored in request.connection.auth
      },
    },
    cookie: { name: 'auth_cookie' }, // if defined, access tokens will be stored in this cookie,
    refresh_cookie: { name: 'refresh_cookie' }, // if defined, refresh tokens will be stored in this cookie
    parse_body: true // read the request body into a string (default false)
  }
);

// express
app.use(handler);

// node http
// ... create server, on path /auth run the handler
handler(req, res); // the handler will also return true if allow_access or allow_part was called
```

after the auth handler, the request will be completed, no additional content
should be served here. (Read 2.1 for info on disabling this)

#### 2.1. Processing Auth Requests without closing the response object

to prevent the auth handler from closing the response object you can provide
additional options on each of the allow/deny functions.

```js
allow_access({leave_open: true, ...});
allow_part(
  60,
  'some_module',
  {foo: 'bar'},
  true // additional flag to leave request open
);
invalid('error description', true);
deny(true);
```

if this flag is set, no data will be written to the response body and no data
will be sent. Status code and Headers will still be set.

### Defining Custom Cookie Settings

By default all cookies will be sent with 'Secure; HttpOnly; SameSite=Strict'
Attributes

In the appropriate settings object, you can set the following options:

```js
{
  name: 'foo', // name of the cookies
  secure: true, // option to enable or disable the Secure option default: true
  http_only: true, // option to enable or disable HttpOnly default: true
  same_site: 'Strict', // SameSite property (Strict, Lax or None) default: 'Strict'. Set this to null to disable
  expires: 'Mon, 10 Jan 2022 09:28:00 GMT', // Expiry date of the cookie
  max_age: 600, // Maximum age in Seconds
  domain: 'example.com', // Domain property
  path: '/cookies_here' // Path property
}
```

For Documentation on the different Cookie Attributes see
<https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#creating_cookies>

### Invalidating tokens after they are delivered to the client

```js
const {blacklist} = require('@sapphirecode/auth-server-helper');

await blacklist.add_signature(token_id); // the token id is returned from any function that creates tokens
```

#### Logout function

```js
const {GatewayClass} = require('@sapphirecode/auth-server-helper');

const gateway = new GatewayClass({ /* options */ });

// create a new express route
app.get('logout', (req, res) => {
  // call the gateway's logout function
  gateway.logout(req);

  // respond ok
  res.status(200);
  res.end();
});
```

### Exporting and importing public keys to validate tokens across server instances

```js
const {keystore} = require('@sapphirecode/auth-server-helper');

const export = keystore.export_verification_data();

// second instance

keystore.import_verification_data(export);
```

These keys can also be live synchronized with redis to allow sessions to be
shared between servers

```js
const {keystore} = require('@sapphirecode/auth-server-helper');

keystore.sync_redis('redis://localhost');
```

### Exporting and importing blacklist entries across server instances

```js
const {blacklist} = require('@sapphirecode/auth-server-helper');

const export = blacklist.export_blacklist();

// second instance

blacklist.import_blacklist(export);
```

### Clearing Keystore and Blacklist

Resetting the Keystore instance generates a new instance id and deletes all
imported or generated keys.

```js
const {keystore, blacklist} = require('@sapphirecode/auth-server-helper');

// clear keystore
keystore.reset_instance();

// clear blacklist
await blacklist.clear();

// clear blacklist items older than 10 seconds
await blacklist.clear(Date.now() - 10000);
```

### Setting and checking permissions

When allowing access to a client a list of permissions can be added. Permissions
are case sensitive.

```js
allow_access({permissions: ['foo','bar'], ...})
```

The gateway can be told to check those permissions before forwarding a request.

```js
const gateway = new GatewayClass({
  require_permissions: ['foo'], // Only clients with the 'foo' permission will be granted access
});
```

additional checks can be run later

```js
(req, res) => {
  const has_both = gateway.check_permissions(req, ['foo', 'bar']); // returns true if both permissions are set
  const has_bar = gateway.has_permission(req, 'bar'); // returns true if permission 'bar' is set
};
```

### Reading connection info

Data like the used token id, custom data and permissions can be read from
`req.connection.auth` or using the function `gateway.get_info(req)`

```js
const info = gateway.get_info(req);

console.log(info);

/*
{
  token_id: 'foo',
  data: {}, // custom data
  permissions: ['foo','bar']
}
*/
```

## License

MIT © Timo Hocker <timo@scode.ovh>